package sentryecho import ( "context" "net/http" "time" "github.com/getsentry/sentry-go" "github.com/labstack/echo/v4" ) 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 true, // as echo includes it's own Recover middleware what handles http responses. Repanic bool // WaitForDelivery configures whether you want to block the request before moving forward with the response. // Because Echo's `Recover` handler doesn't restart the application, // it's safe to either skip this option or set it to `false`. WaitForDelivery bool // Timeout for the event delivery requests. Timeout time.Duration } // New returns a function that satisfies echo.HandlerFunc interface // It can be used with Use() methods. func New(options Options) echo.MiddlewareFunc { 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 } func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc { return func(ctx echo.Context) error { hub := sentry.CurrentHub().Clone() hub.Scope().SetRequest(ctx.Request()) ctx.Set(valuesKey, hub) defer h.recoverWithSentry(hub, ctx.Request()) return next(ctx) } } 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) } } } // GetHubFromContext retrieves attached *sentry.Hub instance from echo.Context. func GetHubFromContext(ctx echo.Context) *sentry.Hub { if hub, ok := ctx.Get(valuesKey).(*sentry.Hub); ok { return hub } return nil }