-
Notifications
You must be signed in to change notification settings - Fork 217
/
Copy pathsentryhttp.go
141 lines (123 loc) · 4.23 KB
/
sentryhttp.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
137
138
139
140
141
// Package sentryhttp provides Sentry integration for servers based on the
// net/http package.
package sentryhttp
import (
"context"
"net/http"
"time"
"github.com/getsentry/sentry-go"
"github.com/getsentry/sentry-go/internal/httputils"
"github.com/getsentry/sentry-go/internal/traceutils"
)
// The identifier of the HTTP SDK.
const sdkIdentifier = "sentry.go.http"
// A Handler is an HTTP middleware factory that provides integration with
// Sentry.
type Handler struct {
repanic bool
waitForDelivery bool
timeout time.Duration
}
// Options configure a Handler.
type Options struct {
// Repanic configures whether to panic again after recovering from a panic.
// Use this option if you have other panic handlers or want the default
// behavior from Go's http package, as documented in
// https://golang.org/pkg/net/http/#Handler.
Repanic bool
// WaitForDelivery indicates, in case of a panic, whether to block the
// current goroutine and wait until the panic event has been reported to
// Sentry before repanicking or resuming normal execution.
//
// This option is normally not needed. Unless you need different behaviors
// for different HTTP handlers, configure the SDK to use the
// HTTPSyncTransport instead.
//
// Waiting (or using HTTPSyncTransport) is useful when the web server runs
// in an environment that interrupts execution at the end of a request flow,
// like modern serverless platforms.
WaitForDelivery bool
// Timeout for the delivery of panic events. Defaults to 2s. Only relevant
// when WaitForDelivery is true.
//
// If the timeout is reached, the current goroutine is no longer blocked
// waiting, but the delivery is not canceled.
Timeout time.Duration
}
// New returns a new Handler. Use the Handle and HandleFunc methods to wrap
// existing HTTP handlers.
func New(options Options) *Handler {
if options.Timeout == 0 {
options.Timeout = 2 * time.Second
}
return &Handler{
repanic: options.Repanic,
timeout: options.Timeout,
waitForDelivery: options.WaitForDelivery,
}
}
// Handle works as a middleware that wraps an existing http.Handler. A wrapped
// handler will recover from and report panics to Sentry, and provide access to
// a request-specific hub to report messages and errors.
func (h *Handler) Handle(handler http.Handler) http.Handler {
return h.handle(handler)
}
// HandleFunc is like Handle, but with a handler function parameter for cases
// where that is convenient. In particular, use it to wrap a handler function
// literal.
//
// http.Handle(pattern, h.HandleFunc(func (w http.ResponseWriter, r *http.Request) {
// // handler code here
// }))
func (h *Handler) HandleFunc(handler http.HandlerFunc) http.HandlerFunc {
return h.handle(handler)
}
func (h *Handler) handle(handler http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
hub := sentry.GetHubFromContext(r.Context())
if hub == nil {
hub = sentry.CurrentHub().Clone()
ctx = sentry.SetHubOnContext(ctx, hub)
}
if client := hub.Client(); client != nil {
client.SetSDKIdentifier(sdkIdentifier)
}
options := []sentry.SpanOption{
sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)),
sentry.WithOpName("http.server"),
sentry.WithTransactionSource(sentry.SourceURL),
sentry.WithSpanOrigin(sentry.SpanOriginStdLib),
}
transaction := sentry.StartTransaction(ctx,
traceutils.GetHTTPSpanName(r),
options...,
)
transaction.SetData("http.request.method", r.Method)
rw := httputils.NewWrapResponseWriter(w, r.ProtoMajor)
defer func() {
status := rw.Status()
transaction.Status = sentry.HTTPtoSpanStatus(status)
transaction.SetData("http.response.status_code", status)
transaction.Finish()
}()
hub.Scope().SetRequest(r)
r = r.WithContext(transaction.Context())
defer h.recoverWithSentry(hub, r)
handler.ServeHTTP(rw, r)
}
}
func (h *Handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
if err := recover(); err != nil {
eventID := hub.RecoverWithContext(
context.WithValue(r.Context(), sentry.RequestContextKey, r),
err,
)
if eventID != nil && h.waitForDelivery {
hub.Flush(h.timeout)
}
if h.repanic {
panic(err)
}
}
}