From 4a3eb31fb15b2a2d78b4bdbe0c31a2c564b1977a Mon Sep 17 00:00:00 2001 From: DadaVinqi Date: Mon, 22 Jun 2026 20:56:28 +0800 Subject: [PATCH] fix(recovery): record recovered panics in c.Errors (#4698) * fix: record recovered panic errors * chore(deps): bump quic-go to v0.59.1 * refactor(recovery): simplify panic error recording - Collapse the if/else into a single c.Error call in defaultHandleRecovery - Assert the recorded panic error is ErrorTypePrivate in the test Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: m0_66095053 <2876430886@qq.com> Co-authored-by: Bo-Yi Wu Co-authored-by: Claude Opus 4.8 (1M context) --- recovery.go | 7 ++++++- recovery_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/recovery.go b/recovery.go index bbf1d565..2124fe5f 100644 --- a/recovery.go +++ b/recovery.go @@ -106,7 +106,12 @@ func secureRequestDump(r *http.Request) string { return strings.Join(lines, "\r\n") } -func defaultHandleRecovery(c *Context, _ any) { +func defaultHandleRecovery(c *Context, err any) { + e, ok := err.(error) + if !ok { + e = fmt.Errorf("%v", err) + } + c.Error(e) //nolint: errcheck c.AbortWithStatus(http.StatusInternalServerError) } diff --git a/recovery_test.go b/recovery_test.go index 028c4ad6..e5211790 100644 --- a/recovery_test.go +++ b/recovery_test.go @@ -5,6 +5,7 @@ package gin import ( + "errors" "net" "net/http" "os" @@ -152,6 +153,49 @@ func TestPanicWithAbortHandler(t *testing.T) { assert.NotContains(t, out, "panic recovered") } +func TestPanicInHandlerRecordsError(t *testing.T) { + tests := []struct { + name string + recoveredErr any + expectedErr string + }{ + { + name: "string panic", + recoveredErr: "Oops, Houston, we have a problem", + expectedErr: "Oops, Houston, we have a problem", + }, + { + name: "error panic", + recoveredErr: errors.New("recovered error"), + expectedErr: "recovered error", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router := New() + + var recoveredErrors errorMsgs + router.Use(func(c *Context) { + c.Next() + recoveredErrors = c.Errors + }) + router.Use(RecoveryWithWriter(nil)) + router.GET("/recovery", func(_ *Context) { + panic(tt.recoveredErr) + }) + + w := PerformRequest(router, http.MethodGet, "/recovery") + + assert.Equal(t, http.StatusInternalServerError, w.Code) + if assert.Len(t, recoveredErrors, 1) { + assert.EqualError(t, recoveredErrors[0], tt.expectedErr) + assert.Equal(t, ErrorTypePrivate, recoveredErrors[0].Type) + } + }) + } +} + func TestCustomRecoveryWithWriter(t *testing.T) { errBuffer := new(strings.Builder) buffer := new(strings.Builder)