diff --git a/middleware/recover.go b/middleware/recover.go index c18032847..01fde5152 100644 --- a/middleware/recover.go +++ b/middleware/recover.go @@ -77,7 +77,7 @@ func (config RecoverConfig) ToMiddleware() (echo.MiddlewareFunc, error) { if !config.DisablePrintStack { stack := make([]byte, config.StackSize) length := runtime.Stack(stack, !config.DisableStackAll) - tmpErr = fmt.Errorf("[PANIC RECOVER] %w %s", tmpErr, stack[:length]) + tmpErr = &PanicStackError{Stack: stack[:length], Err: tmpErr} } err = tmpErr } @@ -86,3 +86,18 @@ func (config RecoverConfig) ToMiddleware() (echo.MiddlewareFunc, error) { } }, nil } + +// PanicStackError is an error type that wraps an error along with its stack trace. +// It is returned when config.DisablePrintStack is set to false. +type PanicStackError struct { + Stack []byte + Err error +} + +func (e *PanicStackError) Error() string { + return fmt.Sprintf("[PANIC RECOVER] %s %s", e.Err.Error(), e.Stack) +} + +func (e *PanicStackError) Unwrap() error { + return e.Err +} diff --git a/middleware/recover_test.go b/middleware/recover_test.go index bf0d16531..719e0cc3d 100644 --- a/middleware/recover_test.go +++ b/middleware/recover_test.go @@ -5,6 +5,7 @@ package middleware import ( "bytes" + "errors" "log/slog" "net/http" "net/http/httptest" @@ -26,6 +27,14 @@ func TestRecover(t *testing.T) { }) err := h(c) assert.Contains(t, err.Error(), "[PANIC RECOVER] test goroutine") + + var pse *PanicStackError + if errors.As(err, &pse) { + assert.Contains(t, string(pse.Stack), "middleware/recover.go") + } else { + assert.Fail(t, "not of type PanicStackError") + } + assert.Equal(t, http.StatusOK, rec.Code) // status is still untouched. err is returned from middleware chain assert.Contains(t, buf.String(), "") // nothing is logged }