From 9f266ee1a85cbe6a9f7dfbbd572ae52132dbddd5 Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 20 Sep 2023 03:40:07 +0200 Subject: [PATCH] feat(backend): also add sentry tracing --- backend/main.go | 8 +++- backend/server/errors.go | 15 ++----- backend/server/sentry.go | 89 ++++++++++++++++++++++++++++++++++++++++ backend/server/server.go | 3 ++ 4 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 backend/server/sentry.go diff --git a/backend/main.go b/backend/main.go index 0cb68a8..3d9395b 100644 --- a/backend/main.go +++ b/backend/main.go @@ -27,8 +27,12 @@ func run(c *cli.Context) error { // initialize sentry if dsn := os.Getenv("SENTRY_DSN"); dsn != "" { sentry.Init(sentry.ClientOptions{ - Dsn: dsn, - Release: server.Tag, + Dsn: dsn, + Debug: true, + Release: server.Tag, + EnableTracing: true, + TracesSampleRate: 1.0, + ProfilesSampleRate: 1.0, }) } diff --git a/backend/server/errors.go b/backend/server/errors.go index ba33050..299ea63 100644 --- a/backend/server/errors.go +++ b/backend/server/errors.go @@ -1,7 +1,6 @@ package server import ( - "context" "fmt" "net/http" @@ -15,16 +14,10 @@ import ( // The inner HandlerFunc additionally returns an error. func WrapHandler(hn func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - hub := sentry.CurrentHub().Clone() - - defer func(hub *sentry.Hub, r *http.Request) { - if err := recover(); err != nil { - hub.RecoverWithContext( - context.WithValue(r.Context(), sentry.RequestContextKey, r), - err, - ) - } - }(hub, r) + hub := sentry.GetHubFromContext(r.Context()) + if hub == nil { + hub = sentry.CurrentHub().Clone() + } err := hn(w, r) if err != nil { diff --git a/backend/server/sentry.go b/backend/server/sentry.go new file mode 100644 index 0000000..9641dec --- /dev/null +++ b/backend/server/sentry.go @@ -0,0 +1,89 @@ +package server + +import ( + "context" + "fmt" + "net/http" + + "github.com/getsentry/sentry-go" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func (s *Server) sentry(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor) + + ctx := r.Context() + hub := sentry.GetHubFromContext(ctx) + if hub == nil { + hub = sentry.CurrentHub().Clone() + ctx = sentry.SetHubOnContext(ctx, hub) + } + + options := []sentry.SpanOption{ + sentry.WithOpName("http.server"), + sentry.ContinueFromRequest(r), + sentry.WithTransactionSource(sentry.SourceURL), + } + // We don't mind getting an existing transaction back so we don't need to + // check if it is. + transaction := sentry.StartTransaction(ctx, + fmt.Sprintf("%s %s", r.Method, r.URL.Path), + options..., + ) + defer transaction.Finish() + r = r.WithContext(transaction.Context()) + hub.Scope().SetRequest(r) + defer recoverWithSentry(hub, r) + handler.ServeHTTP(ww, r) + + transaction.Status = httpStatusToSentryStatus(ww.Status()) + rctx := chi.RouteContext(r.Context()) + transaction.Name = rctx.RouteMethod + " " + rctx.RoutePattern() + }) +} + +func recoverWithSentry(hub *sentry.Hub, r *http.Request) { + if err := recover(); err != nil { + hub.RecoverWithContext( + context.WithValue(r.Context(), sentry.RequestContextKey, r), + err, + ) + } +} + +func httpStatusToSentryStatus(status int) sentry.SpanStatus { + // c.f. https://develop.sentry.dev/sdk/event-payloads/span/ + + if status >= 200 && status < 400 { + return sentry.SpanStatusOK + } + + switch status { + case 499: + return sentry.SpanStatusCanceled + case 500: + return sentry.SpanStatusInternalError + case 400: + return sentry.SpanStatusInvalidArgument + case 504: + return sentry.SpanStatusDeadlineExceeded + case 404: + return sentry.SpanStatusNotFound + case 409: + return sentry.SpanStatusAlreadyExists + case 403: + return sentry.SpanStatusPermissionDenied + case 429: + return sentry.SpanStatusResourceExhausted + case 501: + return sentry.SpanStatusUnimplemented + case 503: + return sentry.SpanStatusUnavailable + case 401: + return sentry.SpanStatusUnauthenticated + default: + return sentry.SpanStatusUnknown + } +} diff --git a/backend/server/server.go b/backend/server/server.go index 399ead0..bfc785d 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -50,6 +50,9 @@ func New() (*Server, error) { s.Router.Use(middleware.Logger) } s.Router.Use(middleware.Recoverer) + // add Sentry tracing handler + s.Router.Use(s.sentry) + // add CORS s.Router.Use(cors.Handler(cors.Options{ AllowedOrigins: []string{"https://*", "http://*"},