Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
2.3 kB · 75 lines
Go
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576package log
import ( "context" "fmt" "runtime/debug")
// Recover wraps a function call so that a panic is recovered, logged at error// level with the provided context, and returned as an error rather than// propagating and crashing the calling goroutine.//// This is defense-in-depth for long-lived goroutines (the firehose event loop,// the task queue workers) so that one bad event or task can't take down the// whole node. Callers that already return an error should prefer wrapping// their body in this to convert panics into logged errors; callers that don't// care about the returned error can ignore it.//// The recovered value is returned as a PanicError so callers can wrap/join it// like any error. The goroutine's stack is captured and logged to aid triage.func Recover(ctx context.Context, fn func() error) (err error) { defer func() { r := recover() if r == nil { return } stack := debug.Stack() Error(ctx, "recovered panic", "panic", r, "stack", string(stack)) err = &PanicError{Recovered: r, Stack: stack} }() return fn()}
// RecoverVoid is like Recover but for functions that do not return an error.// It recovers and logs a panic without propagating it, so a panicking side// effect can't crash the calling goroutine. Use it when there is nothing// meaningful to do with a returned error anyway.func RecoverVoid(ctx context.Context, fn func()) { defer func() { r := recover() if r == nil { return } stack := debug.Stack() Error(ctx, "recovered panic", "panic", r, "stack", string(stack)) }() fn()}
// PanicError wraps a value recovered from a panic. It implements the error// interface so it can flow through normal error-handling paths.type PanicError struct { Recovered any Stack []byte}
func (e *PanicError) Error() string { return "panic recovered: " + asString(e.Recovered)}
// Unwrap is intentionally a no-op; a panic has no sentinel error to unwrap// into, but defining it keeps linters quiet about the field.func (e *PanicError) Unwrap() error { return nil }
// asString stringifies the recovered value, tolerating non-string panic// values (e.g. runtime errors, ints).func asString(v any) string { if s, ok := v.(string); ok { return s } if e, ok := v.(error); ok { return e.Error() } return fmt.Sprintf("%v", v)}