github.com/jxskiss/gopkg@v0.17.3/easy/recover.go (about) 1 package easy 2 3 import ( 4 "context" 5 "fmt" 6 "runtime" 7 "runtime/debug" 8 "strings" 9 ) 10 11 // PanicError represents an captured panic error. 12 type PanicError struct { 13 Exception interface{} 14 Location string 15 Stacktrace []byte 16 } 17 18 func (p *PanicError) Error() string { 19 return fmt.Sprintf("panic: %v, location: %v", p.Exception, p.Location) 20 } 21 22 // Go calls the given function with panic recover, in case of panic happens, 23 // the panic message, location and the calling stack will be logged using 24 // the default logger configured by `ConfigLog` in this package. 25 func Go(f func()) { 26 go func() { 27 defer Recover(nil, nil) 28 f() 29 }() 30 } 31 32 // Go1 calls the given function with panic recover, in case an error is returned, 33 // or panic happens, the error message or panic information will be logged 34 // using the default logger configured by `ConfigLog` in this package. 35 func Go1(f func() error) { 36 go func() { 37 defer Recover(nil, nil) 38 err := f() 39 if err == nil { 40 return 41 } 42 _logcfg.getLogger(nil).Errorf("catch error: %v", err) 43 }() 44 } 45 46 // Safe returns an wrapped function with panic recover. 47 // 48 // Note that if panic happens, the wrapped function does not log messages, 49 // instead it will be returned as a `*PanicError`, the caller take 50 // responsibility to log the panic messages. 51 func Safe(f func()) func() error { 52 return func() (err error) { 53 defer func() { 54 e := recover() 55 if e == nil { 56 return 57 } 58 panicLoc := IdentifyPanic() 59 stack := debug.Stack() 60 err = &PanicError{ 61 Exception: e, 62 Location: panicLoc, 63 Stacktrace: stack, 64 } 65 }() 66 f() 67 return nil 68 } 69 } 70 71 // Safe1 returns an wrapped function with panic recover. 72 // 73 // Note that if panic or error happens, the wrapped function does not log 74 // messages, instead it will be returned as an error, the caller take 75 // responsibility to log the panic or error messages. 76 func Safe1(f func() error) func() error { 77 return func() (err error) { 78 defer func() { 79 e := recover() 80 if e == nil { 81 return 82 } 83 panicLoc := IdentifyPanic() 84 stack := debug.Stack() 85 err = &PanicError{ 86 Exception: e, 87 Location: panicLoc, 88 Stacktrace: stack, 89 } 90 }() 91 err = f() 92 return 93 } 94 } 95 96 // Recover recovers unexpected panic, and log error messages using 97 // logger associated with the given context, if `err` is not nil, 98 // an wrapped `PanicError` will be assigned to it. 99 // 100 // Note that this function should not be wrapped be another function, 101 // instead it should be called directly by the `defer` statement, 102 // or it won't work as you may expect. 103 func Recover(ctx context.Context, err *error) { 104 e := recover() 105 if e == nil { 106 return 107 } 108 109 panicLoc := IdentifyPanic() 110 stack := debug.Stack() 111 pErr := &PanicError{ 112 Exception: e, 113 Location: panicLoc, 114 Stacktrace: stack, 115 } 116 117 // If the caller receives the error, we don't log it here, 118 // else we log the panic error with stack information. 119 if err != nil { 120 *err = pErr 121 return 122 } 123 _logcfg.getLogger(&ctx).Errorf("catch %v\n%s", pErr, stack) 124 } 125 126 // IdentifyPanic reports the panic location when a panic happens. 127 func IdentifyPanic() string { 128 var name, file string 129 var line int 130 var pc [16]uintptr 131 132 n := runtime.Callers(3, pc[:]) 133 for _, pc := range pc[:n] { 134 fn := runtime.FuncForPC(pc) 135 if fn == nil { 136 continue 137 } 138 file, line = fn.FileLine(pc) 139 name = fn.Name() 140 if !strings.HasPrefix(name, "runtime.") { 141 break 142 } 143 } 144 switch { 145 case name != "": 146 return fmt.Sprintf("%v:%v", name, line) 147 case file != "": 148 return fmt.Sprintf("%v:%v", file, line) 149 } 150 151 return fmt.Sprintf("pc:%x", pc) 152 } 153 154 // EnsureError ensures the given value (should be non-nil) is an error. 155 // If it's not an error, `fmt.Errorf("%v", v)` will be used to convert it. 156 func EnsureError(v interface{}) error { 157 if v == nil { 158 return nil 159 } 160 err, ok := v.(error) 161 if !ok { 162 err = fmt.Errorf("%v", v) 163 } 164 return err 165 } 166 167 // PanicOnError fires a panic if any of the args is non-nil error. 168 func PanicOnError(args ...interface{}) { 169 for _, arg := range args { 170 if err, ok := arg.(error); ok && err != nil { 171 panic(err) 172 } 173 } 174 }