github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/recover.go (about) 1 package easy 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 8 "github.com/jxskiss/gopkg/v2/internal" 9 ) 10 11 var recoverToError = NewRecoverFunc(func(_ context.Context, _ *PanicError) {}) 12 13 // PanicError represents an captured panic error. 14 type PanicError struct { 15 Exception any 16 Location string 17 Stacktrace []byte 18 } 19 20 func newPanicError(skip int, e any) *PanicError { 21 panicLoc, frames := internal.IdentifyPanic(skip + 1) 22 stack := internal.FormatFrames(frames) 23 return &PanicError{ 24 Exception: e, 25 Location: panicLoc, 26 Stacktrace: stack, 27 } 28 } 29 30 func (p *PanicError) Unwrap() error { 31 if err, ok := p.Exception.(error); ok { 32 return err 33 } 34 return fmt.Errorf("%v", p.Exception) 35 } 36 37 func (p *PanicError) Error() string { 38 return fmt.Sprintf("panic: %v, location: %v", p.Exception, p.Location) 39 } 40 41 func (p *PanicError) Format(f fmt.State, c rune) { 42 if c == 'v' && f.Flag('+') { 43 fmt.Fprintf(f, "panic: %v, location: %v\n%s\n", p.Exception, p.Location, p.Stacktrace) 44 } else { 45 fmt.Fprint(f, p.Error()) 46 } 47 } 48 49 // Safe returns a wrapper function with panic recover. 50 // 51 // Note that if panic happens, the wrapped function does not log messages, 52 // instead it will be returned as a `*PanicError`, the caller take 53 // responsibility to log the panic messages. 54 func Safe(f func()) func() error { 55 return func() (err error) { 56 defer recoverToError(context.Background(), &err) 57 f() 58 return 59 } 60 } 61 62 // Safe1 returns a wrapper function with panic recover. 63 // 64 // Note that if panic or error happens, the wrapped function does not log 65 // messages, instead it will be returned as an error, the caller take 66 // responsibility to log the panic or error messages. 67 func Safe1(f func() error) func() error { 68 return func() (err error) { 69 defer recoverToError(context.Background(), &err) 70 err = f() 71 return 72 } 73 } 74 75 // Recover recovers panics. 76 // If panic occurred, it prints an error log. 77 // If err is not nil, it will be set to a `*PanicError`. 78 // 79 // Note that this function should not be wrapped by another function, 80 // instead it should be called directly by the `defer` statement, 81 // else it won't work as you may expect. 82 func Recover(errp *error) { 83 e := recover() 84 if e == nil { 85 return 86 } 87 pErr := newPanicError(0, e) 88 if errp != nil { 89 *errp = pErr 90 } 91 log.Printf("[Error] %+v", pErr) 92 } 93 94 // NewRecoverFunc returns a function which recovers panics. 95 // It accepts a panicErr handler function which may be used to log the 96 // panic error and context information. 97 // 98 // Note that the returned function should not be wrapped by another 99 // function, instead it should be called directly by the `defer` statement, 100 // else it won't work as you may expect. 101 // 102 // Example: 103 // 104 // var Recover = NewRecoverFunc(func(ctx context.Context, panicErr *easy.PanicError) { 105 // serviceName := getServiceName(ctx) 106 // 107 // // emit metrics 108 // metrics.Emit("panic", 1, metrics.Tag("service", serviceName)) 109 // 110 // // print log 111 // log.Printf("[Error] %+v", panicErr) 112 // 113 // // or check the panic details 114 // // mylog.Logger(ctx).Errorf("catch panic: %v\nlocation: %s\nstacktrace: %s", 115 // // panicErr.Exception, panicErr.Location, panicErr.Stacktrace) 116 // }) 117 // 118 // // Use the recover function somewhere. 119 // func SomeFunction(ctx context.Context) (err error) { 120 // defer Recover(ctx, &err) 121 // // do something ... 122 // } 123 func NewRecoverFunc[T any](f func(ctx T, panicErr *PanicError)) func(ctx T, errp *error) { 124 return func(ctx T, errp *error) { 125 e := recover() 126 if e == nil { 127 return 128 } 129 pErr := newPanicError(0, e) 130 if errp != nil { 131 *errp = pErr 132 } 133 f(ctx, pErr) 134 } 135 } 136 137 // IdentifyPanic reports the panic location when a panic happens. 138 // It should be called directly after `recover()`, not wrapped by 139 // another function, else it returns incorrect location. 140 // Use IdentifyPanicSkip for wrapping. 141 func IdentifyPanic() string { 142 loc, _ := internal.IdentifyPanic(1) 143 return loc 144 } 145 146 // IdentifyPanicSkip is similar to IdentifyPanic, except that 147 // it accepts a param skip for wrapping usecase. 148 func IdentifyPanicSkip(skip int) string { 149 loc, _ := internal.IdentifyPanic(skip + 1) 150 return loc 151 } 152 153 // EnsureError ensures the given value (should be non-nil) is an error. 154 // If it's not an error, `fmt.Errorf("%v", v)` will be used to convert it. 155 func EnsureError(v any) error { 156 if v == nil { 157 return nil 158 } 159 err, ok := v.(error) 160 if !ok { 161 err = fmt.Errorf("%v", v) 162 } 163 return err 164 } 165 166 // PanicOnError fires a panic if any of the args is non-nil error. 167 func PanicOnError(args ...any) { 168 for _, arg := range args { 169 if err, ok := arg.(error); ok && err != nil { 170 panic(err) 171 } 172 } 173 }