fix(api): log the panic value and stack behind the opaque 500
withRecover turned a panicking handler into a 500 envelope and threw the panic away. The client is meant to get an opaque "internal error" — that part is right — but nothing was written server-side, so a recovered panic was an untraceable 500: an operator holding "internal error" has no message, no stack and no request to grep for, and diagnosis degrades into guessing against a live install. That is what it cost during the email-OTP report. The panic value, a stack and the method+path are now logged first, keyed by the same request_id writeError already stamps on unmapped errors, so the client envelope and the server log can be joined. The test pins all three markers plus the unchanged 500/"panic" response, because a silent recover looks exactly like a working one from the outside.
This commit is contained in:
2 files changed
+42
No files matched your search
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// withRequestID assigns a request id (honoring a WELL-FORMED inbound X-Request-Id)
|
||||
@@ -57,10 +59,18 @@ func validRequestID(id string) bool {
|
||||
|
||||
// withRecover turns a panicking handler into a 500 envelope instead of a
|
||||
// dropped connection.
|
||||
//
|
||||
// The client gets an opaque "internal error", but the panic value and stack are
|
||||
// logged FIRST, keyed by the same request_id — the same contract writeError keeps
|
||||
// for unmapped errors (see errors.go). Without it a recovered panic is an
|
||||
// untraceable 500: an operator holding "internal error" has nothing to grep for,
|
||||
// and diagnosis degrades into guessing against a live install.
|
||||
func withRecover(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
log.Printf("api: %s %s: panic (request_id=%s): %v\n%s",
|
||||
r.Method, r.URL.Path, requestIDFromContext(r.Context()), rec, debug.Stack())
|
||||
writeError(w, r, newError(http.StatusInternalServerError, "panic", "internal error"))
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in new issue
Block a user