@@ -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
2224var 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+
2731func 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
3664type 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
90118func 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