github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/recovery/recovery.go (about) 1 // Package recovery provides a number of grip-integrated panic 2 // handling tools for capturing and responding to panics using grip 3 // loggers. 4 // 5 // These handlers are very useful for capturing panic messages that 6 // might otherwise be lost, as well as providing implementations for 7 // several established panic handling practices. Nevertheless, this 8 // assumes that the panic, or an underlying system issue does not 9 // affect the logging system or its dependencies. For example, panics 10 // caused by disk-full or out of memory situations are challenging to 11 // handle with this approach. 12 // 13 // All log message are logged with the default standard logger in the 14 // grip package. 15 package recovery 16 17 import ( 18 "os" 19 "strings" 20 21 "github.com/mongodb/grip" 22 "github.com/mongodb/grip/level" 23 "github.com/mongodb/grip/logging" 24 "github.com/mongodb/grip/message" 25 ) 26 27 const killOverrideVarName = "__GRIP_EXIT_OVERRIDE" 28 29 // LogStackTraceAndExit captures a panic, captures and logs a stack 30 // trace at the Emergency level and then exits. 31 // 32 // This operation also attempts to close the underlying log sender. 33 func LogStackTraceAndExit(opDetails ...string) { 34 if p := recover(); p != nil { 35 logAndExit(p, logging.MakeGrip(grip.GetSender()), message.MakeFields(getMessage(opDetails))) 36 } 37 } 38 39 // LogStackTraceAndContinue recovers from a panic, and then logs the 40 // captures a stack trace and logs a structured message at "Alert" 41 // level without further action. 42 // 43 // The "opDetails" argument is optional, and is joined as an 44 // "operation" field in the log message for providing additional 45 // context. 46 // 47 // Use in a common defer statement, such as: 48 // 49 // defer recovery.LogStackTraceAndContinue("operation") 50 func LogStackTraceAndContinue(opDetails ...string) { 51 if p := recover(); p != nil { 52 logAndContinue(p, logging.MakeGrip(grip.GetSender()), message.MakeFields(getMessage(opDetails))) 53 } 54 } 55 56 // HandlePanicWithError is used to convert a panic to an error. 57 // 58 // The "opDetails" argument is optional, and is joined as an 59 // "operation" field in the log message for providing additional 60 // context. 61 // 62 // You must construct a recovery function as in the following example: 63 // 64 // defer func() { err = recovery.HandlePanicWithError(recover(), err, "op") }() 65 // 66 // This defer statement must occur in a function that declares a 67 // default error return value as in: 68 // 69 // func operation() (err error) {} 70 func HandlePanicWithError(p interface{}, err error, opDetails ...string) error { 71 catcher := grip.NewSimpleCatcher() 72 catcher.Add(err) 73 74 if p != nil { 75 perr := panicError(p) 76 catcher.Add(perr) 77 78 handleWithError(perr, err, logging.MakeGrip(grip.GetSender()), message.MakeFields(getMessage(opDetails))) 79 } 80 81 return catcher.Resolve() 82 } 83 84 // AnnotateMessageWithStackTraceAndContinue logs panics and continues 85 // and is meant to be used in defer statements like 86 // LogStackTraceAndContinue. 87 // 88 // It takes an interface which it converts to a message.Composer using 89 // the same rules as logging methods, and annotates those messages 90 // with the stack trace and panic information. 91 func AnnotateMessageWithStackTraceAndContinue(m interface{}) { 92 if p := recover(); p != nil { 93 logAndContinue(p, logging.MakeGrip(grip.GetSender()), message.ConvertToComposer(level.Critical, m)) 94 } 95 } 96 97 // SendStackTraceAndContinue is similar to 98 // AnnotateMessageWithStackTraceAndContinue, but allows you to inject a 99 // grip.Journaler interface to receive the log message. 100 func SendStackTraceAndContinue(logger grip.Journaler, m interface{}) { 101 if p := recover(); p != nil { 102 logAndContinue(p, logger, message.ConvertToComposer(level.Critical, m)) 103 } 104 } 105 106 // AnnotateMessageWithStackTraceAndExit logs panics and calls exit 107 // like LogStackTraceAndExit. 108 // 109 // It takes an interface which it converts to a message.Composer using 110 // the same rules as logging methods, and annotates those messages 111 // with the stack trace and panic information. 112 func AnnotateMessageWithStackTraceAndExit(m interface{}) { 113 if p := recover(); p != nil { 114 logAndExit(p, logging.MakeGrip(grip.GetSender()), message.ConvertToComposer(level.Critical, m)) 115 } 116 } 117 118 // SendStackTraceMessageAndExit is similar to 119 // AnnotateMessageWithStackTraceAndExit, but allows you to inject a 120 // grip.Journaler interface. 121 func SendStackTraceMessageAndExit(logger grip.Journaler, m interface{}) { 122 if p := recover(); p != nil { 123 logAndExit(p, logger, message.ConvertToComposer(level.Critical, m)) 124 } 125 } 126 127 // AnnotateMessageWithPanicError processes a panic and converts it 128 // into an error, combining it with the value of another error. Like, 129 // HandlePanicWithError, this method is meant to be used in your own 130 // defer functions. 131 // 132 // It takes an interface which it converts to a message.Composer using 133 // the same rules as logging methods, and annotates those messages 134 // with the stack trace and panic information. 135 func AnnotateMessageWithPanicError(p interface{}, err error, m interface{}) error { 136 catcher := grip.NewSimpleCatcher() 137 catcher.Add(err) 138 139 if p != nil { 140 perr := panicError(p) 141 catcher.Add(perr) 142 143 handleWithError(perr, err, logging.MakeGrip(grip.GetSender()), message.ConvertToComposer(level.Critical, m)) 144 } 145 146 return catcher.Resolve() 147 } 148 149 // SendMessageWithPanicError is similar to 150 // AnnotateMessageWithPanicError, but allows you to inject a custom 151 // grip.Jounaler interface to receive the log message. 152 func SendMessageWithPanicError(p interface{}, err error, logger grip.Journaler, m interface{}) error { 153 catcher := grip.NewSimpleCatcher() 154 catcher.Add(err) 155 156 if p != nil { 157 perr := panicError(p) 158 catcher.Add(perr) 159 160 handleWithError(perr, err, logger, message.ConvertToComposer(level.Critical, m)) 161 } 162 163 return catcher.Resolve() 164 } 165 166 //////////////////////////////////////////////////////////////////////// 167 // 168 // helpers 169 170 func getMessage(details []string) message.Fields { 171 m := message.Fields{} 172 173 if len(details) > 0 { 174 m["operation"] = strings.Join(details, " ") 175 } 176 177 return m 178 } 179 180 func logAndContinue(p interface{}, logger grip.Journaler, msg message.Composer) { 181 _ = msg.Annotate("panic", panicString(p)) 182 _ = msg.Annotate("stack", message.NewStack(3, "").Raw().(message.StackTrace).Frames) 183 _ = msg.Annotate(message.FieldsMsgName, "hit panic; recovering") 184 185 logger.Alert(msg) 186 } 187 188 func logAndExit(p interface{}, logger grip.Journaler, msg message.Composer) { 189 _ = msg.Annotate("panic", panicString(p)) 190 _ = msg.Annotate("stack", message.NewStack(3, "").Raw().(message.StackTrace).Frames) 191 _ = msg.Annotate(message.FieldsMsgName, "hit panic; exiting") 192 193 // check this env var so that we can avoid exiting in the test. 194 if os.Getenv(killOverrideVarName) == "" { 195 logger.EmergencyFatal(msg) 196 } else { 197 logger.Emergency(msg) 198 } 199 } 200 201 func handleWithError(p error, err error, logger grip.Journaler, msg message.Composer) { 202 _ = msg.Annotate("panic", p.Error()) 203 _ = msg.Annotate("stack", message.NewStack(3, "").Raw().(message.StackTrace).Frames) 204 _ = msg.Annotate(message.FieldsMsgName, "hit panic; adding error") 205 206 if err != nil { 207 _ = msg.Annotate("error", err.Error()) 208 } 209 210 logger.Alert(msg) 211 }