Report handler panics through the logger and answer 500 (closes #187)
All checks were successful
check / check (push) Successful in 3m2s
All checks were successful
check / check (push) Successful in 3m2s
chi v1.5.5's middleware.Recoverer neither logged a handler panic nor answered 500. Its pretty-printer scans the stack for a frame beginning "panic(0x", which the runtime no longer emits, so the scan never terminates early and every line reaches decorateFuncCallLine, which slices pkg[strings.Index(pkg, "."):] without checking for -1. That second panic escaped chi's own deferred function, so its WriteHeader(500) never ran: net/http closed the connection and reported its own crash, losing the original panic value entirely. Middleware.Recoverer replaces it. It writes one ERROR record through internal/logger carrying the panic value, the stack and the request id, and answers 500. http.ErrAbortHandler is re-panicked rather than swallowed, and a response the handler already committed is left alone rather than overwritten. It is registered inside every middleware that observes the response, so the 500 is the status the access log records and the metrics count, and outside the sentryhttp handler, whose Repanic option needs something further out to catch what it re-raises. Both fields are bounded in encoded bytes, through the same internal/logfield budget the access log spends: 512 for the panic value, since a handler may build one out of the request, and 8192 for the stack, cut at its far end so the panic site survives. MaxPanicLogLineBytes states the resulting ceiling at 10240; measured, the widest line either handler produces is 8898, a figure that carries no source paths and reproduces across checkouts. The real case through the shipped chain measures roughly 3960 bytes; that one moves with the checkout, because debug.Stack() embeds absolute source paths, so it is stated as a measurement rather than as an invariant and no test asserts it. Because the panic record no longer reaches net/http's error log, the carve-outs in README.md and in the MaxAccessLogLineBytes doc comment that described that path are removed rather than reworded. What replaces them states the ceiling the record is now written under, and internal/server/recoverer_test.go asserts that "http: panic serving" appears in neither of the process's streams.
This commit is contained in:
@@ -51,7 +51,6 @@ func (s *Server) SetupRoutes() {
|
||||
}
|
||||
|
||||
func (s *Server) setupGlobalMiddleware() {
|
||||
s.router.Use(middleware.Recoverer)
|
||||
s.router.Use(middleware.RequestID)
|
||||
s.router.Use(s.mw.SecurityHeaders())
|
||||
s.router.Use(s.mw.Logging())
|
||||
@@ -64,8 +63,21 @@ func (s *Server) setupGlobalMiddleware() {
|
||||
s.router.Use(s.mw.CORS())
|
||||
s.router.Use(middleware.Timeout(requestTimeout))
|
||||
|
||||
// Panic recovery, deliberately here rather than first. It has to
|
||||
// run inside every middleware that observes the response, so the
|
||||
// 500 it writes is the status the access log records and the
|
||||
// metrics count, and outside the sentryhttp handler below, whose
|
||||
// Repanic option needs something further out to catch what it
|
||||
// re-raises. chi's own middleware.Recoverer held the first slot
|
||||
// until it was measured: on a current Go release it crashes
|
||||
// inside its stack pretty-printer instead of recovering, so the
|
||||
// connection dropped and the original panic was never reported.
|
||||
// See https://git.eeqj.de/sneak/webhooker/issues/187.
|
||||
s.router.Use(s.mw.Recoverer())
|
||||
|
||||
// Sentry error reporting (if SENTRY_DSN is set). Repanic is
|
||||
// true so panics still bubble up to the Recoverer middleware.
|
||||
// true so panics still bubble up to the Recoverer middleware
|
||||
// registered immediately above.
|
||||
if s.sentryEnabled {
|
||||
sentryHandler := sentryhttp.New(sentryhttp.Options{
|
||||
Repanic: true,
|
||||
|
||||
Reference in New Issue
Block a user