CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
(out io.Writer, handle RecoveryFunc)
| 51 | |
| 52 | // CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it. |
| 53 | func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc { |
| 54 | var logger *log.Logger |
| 55 | if out != nil { |
| 56 | logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags) |
| 57 | } |
| 58 | return func(c *Context) { |
| 59 | defer func() { |
| 60 | if rec := recover(); rec != nil { |
| 61 | // Check for a broken connection, as it is not really a |
| 62 | // condition that warrants a panic stack trace. |
| 63 | var isBrokenPipe bool |
| 64 | err, ok := rec.(error) |
| 65 | if ok { |
| 66 | isBrokenPipe = errors.Is(err, syscall.EPIPE) || |
| 67 | errors.Is(err, syscall.ECONNRESET) || |
| 68 | errors.Is(err, http.ErrAbortHandler) |
| 69 | } |
| 70 | if logger != nil { |
| 71 | if isBrokenPipe { |
| 72 | logger.Printf("%s\n%s%s", rec, secureRequestDump(c.Request), reset) |
| 73 | } else if IsDebugging() { |
| 74 | logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", |
| 75 | timeFormat(time.Now()), secureRequestDump(c.Request), rec, stack(stackSkip), reset) |
| 76 | } else { |
| 77 | logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s", |
| 78 | timeFormat(time.Now()), rec, stack(stackSkip), reset) |
| 79 | } |
| 80 | } |
| 81 | if isBrokenPipe { |
| 82 | // If the connection is dead, we can't write a status to it. |
| 83 | c.Error(err) //nolint: errcheck |
| 84 | c.Abort() |
| 85 | } else { |
| 86 | handle(c, rec) |
| 87 | } |
| 88 | } |
| 89 | }() |
| 90 | c.Next() |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // secureRequestDump returns a sanitized HTTP request dump where the Authorization header, |
| 95 | // if present, is replaced with a masked value ("Authorization: *") to avoid leaking sensitive credentials. |