server: report HTTP handler panics to Sentry (closes #107)
check / check (push) Failing after 35s
check / check (push) Failing after 35s
DNSWATCHER_SENTRY_DSN was read but never used. This ports the Sentry integration from gohttpserver with sentry-go v0.49.0. The server's start hook calls sentry.Init when the DSN is set; a DSN Sentry cannot parse fails the hook, so startup stops with the parse error. sentryhttp, with Repanic, reports handler panics and passes them on to chi's Recoverer. Shutdown sends queued reports once the HTTP server has stopped. The client uses the older transport (DisableTelemetryBuffer): with the default one, Flush can return before sending a report made just before it. The DSN is checked at server start, not in config, so the config test is unchanged. sentry-go raises several golang.org/x modules. Model: opus-5-5
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/fx"
|
||||
|
||||
@@ -33,6 +34,10 @@ type Params struct {
|
||||
// shutdownTimeout is how long to wait for graceful shutdown.
|
||||
const shutdownTimeout = 30 * time.Second
|
||||
|
||||
// sentryFlushTimeout is how long shutdown waits for Sentry to send the
|
||||
// error reports it still holds.
|
||||
const sentryFlushTimeout = 2 * time.Second
|
||||
|
||||
// Socket-level timeouts for the HTTP server.
|
||||
//
|
||||
// These bound time spent on the connection itself and are a distinct
|
||||
@@ -82,14 +87,15 @@ const (
|
||||
|
||||
// Server is the HTTP server.
|
||||
type Server struct {
|
||||
startupTime time.Time
|
||||
port int
|
||||
log *slog.Logger
|
||||
router *chi.Mux
|
||||
httpServer *http.Server
|
||||
params Params
|
||||
mw *middleware.Middleware
|
||||
handlers *handlers.Handlers
|
||||
startupTime time.Time
|
||||
port int
|
||||
sentryEnabled bool
|
||||
log *slog.Logger
|
||||
router *chi.Mux
|
||||
httpServer *http.Server
|
||||
params Params
|
||||
mw *middleware.Middleware
|
||||
handlers *handlers.Handlers
|
||||
}
|
||||
|
||||
// New creates a new Server instance.
|
||||
@@ -108,6 +114,12 @@ func New(
|
||||
lifecycle.Append(fx.Hook{
|
||||
OnStart: func(_ context.Context) error {
|
||||
srv.startupTime = time.Now()
|
||||
|
||||
err := srv.enableSentry()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go srv.Run()
|
||||
|
||||
return nil
|
||||
@@ -120,6 +132,45 @@ func New(
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
// enableSentry turns on Sentry error reporting when
|
||||
// DNSWATCHER_SENTRY_DSN is set, and does nothing when it is not. A DSN
|
||||
// that Sentry cannot parse is an error, so that startup stops instead
|
||||
// of running without the error reporting the operator asked for.
|
||||
func (s *Server) enableSentry() error {
|
||||
if s.params.Config.SentryDSN == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: s.params.Config.SentryDSN,
|
||||
Release: s.params.Globals.Appname + "-" + s.params.Globals.Version,
|
||||
// Use the transport that queues each report as it is made. With
|
||||
// the default one, Flush can return before sending a report made
|
||||
// just before it, such as one from the last request at shutdown.
|
||||
DisableTelemetryBuffer: true,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid DNSWATCHER_SENTRY_DSN: %w", err)
|
||||
}
|
||||
|
||||
s.log.Info("sentry error reporting activated")
|
||||
s.sentryEnabled = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// flushSentry sends the error reports Sentry still holds, waiting at
|
||||
// most sentryFlushTimeout.
|
||||
func (s *Server) flushSentry() {
|
||||
if !s.sentryEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
if !sentry.Flush(sentryFlushTimeout) {
|
||||
s.log.Warn("sentry flush timed out; some error reports were not sent")
|
||||
}
|
||||
}
|
||||
|
||||
// newHTTPServer builds the listening http.Server with every
|
||||
// socket-level timeout set. All four are set deliberately: a zero
|
||||
// value in net/http means "no limit", not "some default".
|
||||
@@ -152,8 +203,11 @@ func (s *Server) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown gracefully shuts down the server.
|
||||
// Shutdown gracefully shuts down the server, then sends the error
|
||||
// reports Sentry still holds.
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
defer s.flushSentry()
|
||||
|
||||
if s.httpServer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user