/
sentryfasthttp.go
136 lines (113 loc) · 3.25 KB
/
sentryfasthttp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package sentryhttp
import (
"context"
"fmt"
"net"
"strings"
"time"
"github.com/getsentry/sentry-go"
"github.com/valyala/fasthttp"
)
type contextKey int
const ContextKey = contextKey(1)
const valuesKey = "sentry"
type Handler struct {
repanic bool
waitForDelivery bool
timeout time.Duration
}
type Options struct {
// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to false,
// as fasthttp doesn't include it's own Recovery handler.
Repanic bool
// WaitForDelivery configures whether you want to block the request before moving forward with the response.
// Because fasthttp doesn't include it's own `Recovery` handler, it will restart the application,
// and event won't be delivered otherwise.
WaitForDelivery bool
// Timeout for the event delivery requests.
Timeout time.Duration
}
// New returns a struct that provides Handle method
// that satisfy fasthttp.RequestHandler interface.
func New(options Options) *Handler {
handler := Handler{
repanic: false,
timeout: time.Second * 2,
waitForDelivery: false,
}
if options.Repanic {
handler.repanic = true
}
if options.Timeout != 0 {
handler.timeout = options.Timeout
}
if options.WaitForDelivery {
handler.waitForDelivery = true
}
return &handler
}
// Handle wraps fasthttp.RequestHandler and recovers from caught panics.
func (h *Handler) Handle(handler fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
hub := sentry.CurrentHub().Clone()
hub.Scope().SetRequest(extractRequestData(ctx))
ctx.SetUserValue(valuesKey, hub)
defer h.recoverWithSentry(hub, ctx)
handler(ctx)
}
}
func (h *Handler) recoverWithSentry(hub *sentry.Hub, ctx *fasthttp.RequestCtx) {
if err := recover(); err != nil {
eventID := hub.RecoverWithContext(
context.WithValue(context.Background(), sentry.RequestContextKey, ctx),
err,
)
if eventID != nil && h.waitForDelivery {
hub.Flush(h.timeout)
}
if h.repanic {
panic(err)
}
}
}
// GetHubFromContext retrieves attached *sentry.Hub instance from fasthttp.RequestCtx.
func GetHubFromContext(ctx *fasthttp.RequestCtx) *sentry.Hub {
hub := ctx.UserValue(valuesKey)
if hub, ok := hub.(*sentry.Hub); ok {
return hub
}
return nil
}
func extractRequestData(ctx *fasthttp.RequestCtx) sentry.Request {
defer func() {
if err := recover(); err != nil {
sentry.Logger.Printf("%v", err)
}
}()
r := sentry.Request{}
r.Method = string(ctx.Method())
uri := ctx.URI()
r.URL = fmt.Sprintf("%s://%s%s", uri.Scheme(), uri.Host(), uri.Path())
// Headers
headers := make(map[string]string)
ctx.Request.Header.VisitAll(func(key, value []byte) {
headers[string(key)] = string(value)
})
headers["Host"] = string(ctx.Host())
r.Headers = headers
// Cookies
cookies := []string{}
ctx.Request.Header.VisitAllCookie(func(key, value []byte) {
cookies = append(cookies, fmt.Sprintf("%s=%s", key, value))
})
r.Cookies = strings.Join(cookies, "; ")
// Env
if addr, port, err := net.SplitHostPort(ctx.RemoteAddr().String()); err == nil {
r.Env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
}
// QueryString
r.QueryString = string(ctx.URI().QueryString())
// Body
r.Data = string(ctx.Request.Body())
return r
}