Skip to content

Commit b9a93f5

Browse files
maintenance/errors: Switch to sentry-go package
1 parent 93121a2 commit b9a93f5

File tree

5 files changed

+150
-157
lines changed

5 files changed

+150
-157
lines changed

internal/sentry/sentry.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package sentry
2+
3+
import (
4+
"log"
5+
"os"
6+
7+
"github.com/getsentry/sentry-go"
8+
)
9+
10+
func init() {
11+
err := sentry.Init(sentry.ClientOptions{
12+
Dsn: os.Getenv("SENTRY_DSN"),
13+
Environment: os.Getenv("ENVIRONMENT"),
14+
EnableTracing: true,
15+
TracesSampleRate: 1.0,
16+
BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
17+
// Drop request body.
18+
if event.Request != nil {
19+
event.Request.Data = ""
20+
}
21+
22+
return event
23+
},
24+
})
25+
if err != nil {
26+
log.Fatalf("sentry.Init: %v", err)
27+
}
28+
}

maintenance/errors/error.go

Lines changed: 103 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,54 @@ import (
1111
"os"
1212
"time"
1313

14+
"github.com/getsentry/sentry-go"
15+
"github.com/prometheus/client_golang/prometheus"
16+
"github.com/rs/zerolog"
17+
1418
"github.com/pace/bricks/http/jsonapi/runtime"
1519
"github.com/pace/bricks/http/oauth2"
16-
"github.com/pace/bricks/maintenance/errors/raven"
20+
_ "github.com/pace/bricks/internal/sentry"
1721
"github.com/pace/bricks/maintenance/log"
18-
"github.com/prometheus/client_golang/prometheus"
19-
"github.com/rs/zerolog"
2022
)
2123

2224
var paceHTTPPanicCounter = prometheus.NewGauge(prometheus.GaugeOpts{
2325
Name: "pace_http_panic_total",
2426
Help: "A counter for panics intercepted while handling a request",
2527
})
2628

29+
var DefaultClient *sentry.Client
30+
2731
func init() {
2832
prometheus.MustRegister(paceHTTPPanicCounter)
2933
}
3034

31-
// PanicWrap wraps a panic for HandleRequest
32-
type PanicWrap struct {
33-
err interface{}
35+
type ErrWithExtra struct {
36+
err error
37+
extra map[string]any
38+
}
39+
40+
func NewErrWithExtra(err error, extra map[string]any) ErrWithExtra {
41+
return ErrWithExtra{
42+
err: err,
43+
extra: extra,
44+
}
45+
}
46+
47+
func (e ErrWithExtra) Error() string {
48+
return e.err.Error()
49+
}
50+
51+
// Panic wraps a panic for HandleRequest
52+
type Panic struct {
53+
err any
54+
}
55+
56+
func NewPanic(err any) Panic {
57+
return Panic{err: err}
58+
}
59+
60+
func (p Panic) Error() string {
61+
return fmt.Sprintf("%v", p.err)
3462
}
3563

3664
type recoveryHandler struct {
@@ -88,153 +116,123 @@ func Handler() func(http.Handler) http.Handler {
88116

89117
// HandleRequest should be called with defer to recover panics in request handlers
90118
func HandleRequest(handlerName string, w http.ResponseWriter, r *http.Request) {
91-
if rp := recover(); rp != nil {
119+
if rec := recover(); rec != nil {
92120
paceHTTPPanicCounter.Inc()
93-
HandleError(&PanicWrap{rp}, handlerName, w, r)
121+
HandleError(NewPanic(rec), handlerName, w, r)
94122
}
95123
}
96124

97125
// HandleError reports the passed error to sentry
98-
func HandleError(rp interface{}, handlerName string, w http.ResponseWriter, r *http.Request) {
126+
func HandleError(err error, handlerName string, w http.ResponseWriter, r *http.Request) {
99127
ctx := r.Context()
100-
pw, ok := rp.(*PanicWrap)
101-
if ok {
102-
log.Ctx(ctx).Error().Str("handler", handlerName).Msgf("Panic: %v", pw.err)
103-
rp = pw.err // unwrap error
104-
} else {
105-
log.Ctx(ctx).Error().Str("handler", handlerName).Msgf("Error: %v", rp)
106-
}
107-
log.Stack(ctx)
108128

109-
sentryEvent{ctx, r, rp, 1, handlerName}.Send()
129+
handle(ctx, err, handlerName)
110130

111131
runtime.WriteError(w, http.StatusInternalServerError, errors.New("Internal Server Error"))
112132
}
113133

114134
// Handle logs the given error and reports it to sentry.
115-
func Handle(ctx context.Context, rp interface{}) {
116-
pw, ok := rp.(*PanicWrap)
117-
if ok {
118-
log.Ctx(ctx).Error().Msgf("Panic: %v", pw.err)
119-
rp = pw.err // unwrap error
120-
} else {
121-
log.Ctx(ctx).Error().Msgf("Error: %v", rp)
122-
}
123-
log.Stack(ctx)
124-
125-
sentryEvent{ctx, nil, rp, 1, ""}.Send()
135+
func Handle(ctx context.Context, err error) {
136+
handle(ctx, err, "")
126137
}
127138

128-
// HandleWithCtx should be called with defer to recover panics in goroutines
129-
func HandleWithCtx(ctx context.Context, handlerName string) {
130-
if rp := recover(); rp != nil {
131-
log.Ctx(ctx).Error().Str("handler", handlerName).Msgf("Panic: %v", rp)
132-
log.Stack(ctx)
139+
func handle(ctx context.Context, err error, handlerName string) {
140+
l := log.Ctx(ctx).Error().Err(err)
133141

134-
sentryEvent{ctx, nil, rp, 2, handlerName}.Send()
142+
if handlerName != "" {
143+
l = l.Str("handler", handlerName)
135144
}
136-
}
137145

138-
func HandleErrorNoStack(ctx context.Context, err error) {
139-
log.Ctx(ctx).Info().Msgf("Received error, will not handle further: %v", err)
140-
}
141-
142-
// New returns an error that formats as the given text.
143-
func New(text string) error {
144-
return errors.New(text)
145-
}
146-
147-
// WrapWithExtra adds extra data to an error before reporting to Sentry
148-
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
149-
return raven.WrapWithExtra(err, extraInfo)
150-
}
146+
if errors.Is(err, Panic{}) {
147+
l.Msg("Panic")
148+
} else {
149+
l.Msg("Error")
150+
}
151151

152-
type sentryEvent struct {
153-
ctx context.Context
154-
req *http.Request // optional
155-
r interface{}
156-
level int
157-
handlerName string
158-
}
152+
log.Stack(ctx)
159153

160-
func (e sentryEvent) Send() {
161-
_, errCh := raven.Capture(e.build(), nil)
162-
<-errCh // ensure the message get send even if the main goroutine is about to stop
154+
sentry.CaptureEvent(getEvent(ctx, nil, err, 1, handlerName))
163155
}
164156

165-
func (e sentryEvent) build() *raven.Packet {
166-
ctx, r, rp, handlerName := e.ctx, e.req, e.r, e.handlerName
167-
157+
func getEvent(ctx context.Context, r *http.Request, err error, level int, handlerName string) *sentry.Event {
168158
// get request from context if available
169159
if r == nil {
170160
r = requestFromContext(ctx)
171161
}
172162

173-
rvalStr := fmt.Sprint(rp)
174-
var packet *raven.Packet
163+
event := sentry.NewEvent()
175164

176-
if err, ok := rp.(error); ok {
177-
stack := raven.GetOrNewStacktrace(err, 2+e.level, 3, nil)
178-
packet = raven.NewPacket(rvalStr, raven.NewException(err, stack))
179-
} else {
180-
stack := raven.NewStacktrace(2+e.level, 3, nil)
181-
packet = raven.NewPacket(rvalStr, raven.NewException(errors.New(rvalStr), stack))
182-
}
183-
184-
// extract ErrWithExtra info and append it to the packet
185-
if ee, ok := rp.(raven.ErrWithExtra); ok {
186-
for k, v := range ee.ExtraInfo() {
187-
packet.Extra[k] = v
188-
}
189-
}
165+
event.SetException(err, level)
190166

191167
// add user
192168
userID, ok := oauth2.UserID(ctx)
193-
user := raven.User{ID: userID}
194-
if r != nil {
195-
user.IP = log.ProxyAwareRemote(r)
196-
}
197-
packet.Interfaces = append(packet.Interfaces, &user)
198169
if ok {
199-
packet.Tags = append(packet.Tags, raven.Tag{Key: "user_id", Value: userID})
170+
event.User.ID = userID
171+
}
172+
173+
if r != nil {
174+
event.User.IPAddress = log.ProxyAwareRemote(r)
200175
}
201176

202177
// from context
203178
if reqID := log.RequestIDFromContext(ctx); reqID != "" {
204-
packet.Extra["req_id"] = reqID
205-
packet.Tags = append(packet.Tags, raven.Tag{Key: "req_id", Value: reqID})
179+
event.Extra["req_id"] = reqID
180+
event.Tags["req_id"] = reqID
206181
}
182+
207183
if traceID := log.TraceIDFromContext(ctx); traceID != "" {
208-
packet.Extra["uber_trace_id"] = traceID
209-
packet.Tags = append(packet.Tags, raven.Tag{Key: "trace_id", Value: traceID})
184+
event.Extra["uber_trace_id"] = traceID
185+
event.Tags["trace_id"] = traceID
210186
}
211-
packet.Extra["handler"] = handlerName
187+
188+
event.Extra["handler"] = handlerName
189+
212190
if clientID, ok := oauth2.ClientID(ctx); ok {
213-
packet.Extra["oauth2_client_id"] = clientID
214-
}
215-
if scopes := oauth2.Scopes(ctx); len(scopes) > 0 {
216-
packet.Extra["oauth2_scopes"] = scopes
191+
event.Extra["oauth2_client_id"] = clientID
217192
}
218193

219-
// from request
220-
if r != nil {
221-
packet.Interfaces = append(packet.Interfaces, raven.NewHttp(r))
194+
if scopes := oauth2.Scopes(ctx); len(scopes) > 0 {
195+
event.Extra["oauth2_scopes"] = scopes
222196
}
223197

224198
// from env
225-
packet.Extra["microservice"] = os.Getenv("JAEGER_SERVICE_NAME")
199+
event.Extra["microservice"] = os.Getenv("JAEGER_SERVICE_NAME")
226200

227201
// add breadcrumbs
228-
packet.Breadcrumbs = getBreadcrumbs(ctx)
202+
event.Breadcrumbs = getBreadcrumbs(ctx)
203+
204+
return event
205+
}
229206

230-
return packet
207+
// HandleWithCtx should be called with defer to recover panics in goroutines
208+
func HandleWithCtx(ctx context.Context, handlerName string) {
209+
if r := recover(); r != nil {
210+
log.Ctx(ctx).Error().Str("handler", handlerName).Msgf("Panic: %v", r)
211+
log.Stack(ctx)
212+
213+
sentry.CaptureEvent(getEvent(ctx, nil, NewPanic(r), 2, handlerName))
214+
}
215+
}
216+
217+
func HandleErrorNoStack(ctx context.Context, err error) {
218+
log.Ctx(ctx).Info().Msgf("Received error, will not handle further: %v", err)
219+
}
220+
221+
// New returns an error that formats as the given text.
222+
func New(text string) error {
223+
return errors.New(text)
224+
}
225+
226+
// WrapWithExtra adds extra data to an error before reporting to Sentry
227+
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
228+
return NewErrWithExtra(err, extraInfo)
231229
}
232230

233231
// getBreadcrumbs takes a context and tries to extract the logs from it if it
234232
// holds a log.Sink. If that's the case, the logs will all be translated
235233
// to valid sentry breadcrumbs if possible. In case of a failure, the
236234
// breadcrumbs will be dropped and a warning will be logged.
237-
func getBreadcrumbs(ctx context.Context) []*raven.Breadcrumb {
235+
func getBreadcrumbs(ctx context.Context) []*sentry.Breadcrumb {
238236
sink, ok := log.SinkFromContext(ctx)
239237
if !ok {
240238
return nil
@@ -246,7 +244,7 @@ func getBreadcrumbs(ctx context.Context) []*raven.Breadcrumb {
246244
return nil
247245
}
248246

249-
result := make([]*raven.Breadcrumb, len(data))
247+
result := make([]*sentry.Breadcrumb, len(data))
250248
for i, d := range data {
251249
crumb, err := createBreadcrumb(d)
252250
if err != nil {
@@ -260,7 +258,7 @@ func getBreadcrumbs(ctx context.Context) []*raven.Breadcrumb {
260258
return result
261259
}
262260

263-
func createBreadcrumb(data map[string]interface{}) (*raven.Breadcrumb, error) {
261+
func createBreadcrumb(data map[string]any) (*sentry.Breadcrumb, error) {
264262
// remove the request id if it can still be found in the logs
265263
delete(data, "req_id")
266264

@@ -318,11 +316,11 @@ func createBreadcrumb(data map[string]interface{}) (*raven.Breadcrumb, error) {
318316
typ = "error"
319317
}
320318

321-
return &raven.Breadcrumb{
319+
return &sentry.Breadcrumb{
322320
Category: category,
323321
Level: level,
324322
Message: message,
325-
Timestamp: time.Unix(),
323+
Timestamp: time,
326324
Type: typ,
327325
Data: data,
328326
}, nil
@@ -331,7 +329,7 @@ func createBreadcrumb(data map[string]interface{}) (*raven.Breadcrumb, error) {
331329
// translateZerologLevelToSentryLevel takes in a zerolog.Level as string
332330
// and returns the equivalent sentry breadcrumb level. If the given level
333331
// can't be parsed to a valid zerolog.Level an error is returned.
334-
func translateZerologLevelToSentryLevel(l string) (string, error) {
332+
func translateZerologLevelToSentryLevel(l string) (sentry.Level, error) {
335333
level, err := zerolog.ParseLevel(l)
336334
if err != nil {
337335
return "", err

0 commit comments

Comments
 (0)