gitlab.com/ignitionrobotics/web/ign-go@v1.0.0-rc4/logger.go (about) 1 package ign 2 3 import ( 4 "context" 5 "fmt" 6 "github.com/rollbar/rollbar-go" 7 "log" 8 "net/http" 9 ) 10 11 // VerbosityDebug - Debug verbosity level 12 // Output will include Critical + Error + Warning + Info + Debug 13 const VerbosityDebug = 4 14 15 // VerbosityInfo - Info verbosity level 16 // Output will include Critical + Error + Warning + Info 17 const VerbosityInfo = 3 18 19 // VerbosityWarning - Warning verbosity level 20 // Output will include Critical + Error + Warning 21 const VerbosityWarning = 2 22 23 // VerbosityError - Error verbosity level 24 // Output will include Critical + Error 25 const VerbosityError = 1 26 27 // VerbosityCritical - Critical verbosity level 28 // Output will include Critical 29 const VerbosityCritical = 0 30 31 // Logger - interface for any ign logger. 32 type Logger interface { 33 // Output when verbosity => 4 34 Debug(interfaces ...interface{}) 35 // Output when verbosity => 3 36 Info(interfaces ...interface{}) 37 // Output when verbosity => 2 38 Warning(interfaces ...interface{}) 39 // Output when verbosity => 1 40 Error(interfaces ...interface{}) 41 // Output when verbosity => 0 42 Critical(interfaces ...interface{}) 43 // Clone this logger and returns a copy. 44 Clone(reqID string) Logger 45 } 46 47 // default empty logger implementation. To be returned when there is no logger 48 // in the context. This logger just logs to the stdout. 49 type defaultLogImpl struct { 50 } 51 52 // Debug - debug log 53 func (l *defaultLogImpl) Debug(interfaces ...interface{}) { 54 fmt.Println("[Debug][IGN EMPTY LOGGER]", interfaces) 55 } 56 57 // Info - info log 58 func (l *defaultLogImpl) Info(interfaces ...interface{}) { 59 fmt.Println("[Info][IGN EMPTY LOGGER]", interfaces) 60 } 61 62 // Warning - logs a warning message. 63 func (l *defaultLogImpl) Warning(interfaces ...interface{}) { 64 fmt.Println("[Warning][IGN EMPTY LOGGER]", interfaces) 65 } 66 67 // Error - logs an error message. 68 func (l *defaultLogImpl) Error(interfaces ...interface{}) { 69 fmt.Println("[Error][IGN EMPTY LOGGER]", interfaces) 70 } 71 72 // Critical - logs a critical message. 73 func (l *defaultLogImpl) Critical(interfaces ...interface{}) { 74 fmt.Println("[Critical][IGN EMPTY LOGGER]", interfaces) 75 } 76 77 // Clone - clone this logger 78 func (l *defaultLogImpl) Clone(reqID string) Logger { 79 return l 80 } 81 82 var emptyLogger *defaultLogImpl 83 84 func getEmptyLogger() Logger { 85 if emptyLogger == nil { 86 l := defaultLogImpl{} 87 emptyLogger = &l 88 } 89 return emptyLogger 90 } 91 92 // ignLogger - internal implementation for the ign logger interface. 93 // The ignLogger will log to terminal and also to rollbar, if configured. 94 // The ignLogger will prefix all logs with a configured request Id. 95 type ignLogger struct { 96 reqID string 97 rollbar bool 98 logToStd bool 99 // Controls the level output 100 // 0 = Critical 101 // 1 = Critical + Error 102 // 2 = Critical + Error + Warning 103 // 3 = Critical + Error + Warning + Info 104 // 4 = Critical + Error + Warning + Info + Debug 105 verbosity int 106 // Controls the level to output to rollbar 107 RollbarVerbosity int 108 } 109 110 type ctxLoggerType int 111 112 const loggerKey ctxLoggerType = iota 113 114 // NewLogger - creates a new logger implementation associated to the given 115 // request ID. 116 func NewLogger(reqID string, std bool, verbosity int) Logger { 117 logger := ignLogger{reqID, true, std, verbosity, verbosity} 118 return &logger 119 } 120 121 // NewLoggerWithRollbarVerbosity - creates a new logger implementation associated 122 // to the given request ID and also configures a minimum verbosity to send logs 123 // to Rollbar. 124 func NewLoggerWithRollbarVerbosity(reqID string, std bool, verbosity, rollbarVerbosity int) Logger { 125 logger := ignLogger{reqID, true, std, verbosity, rollbarVerbosity} 126 return &logger 127 } 128 129 // NewLoggerNoRollbar - creates a new logger implementation associated to the given 130 // request ID, which does not log to rollbar 131 func NewLoggerNoRollbar(reqID string, verbosity int) Logger { 132 logger := ignLogger{reqID, false, true, verbosity, verbosity} 133 return &logger 134 } 135 136 // NewContextWithLogger - configures the context with a new ign Logger, 137 func NewContextWithLogger(ctx context.Context, logger Logger) context.Context { 138 return context.WithValue(ctx, loggerKey, logger) 139 } 140 141 // LoggerFromContext - gets an ign logger from the given context. 142 func LoggerFromContext(ctx context.Context) Logger { 143 if ctx == nil { 144 return emptyLogger 145 } 146 if logger, ok := ctx.Value(loggerKey).(*ignLogger); ok { 147 return logger 148 } 149 return emptyLogger 150 } 151 152 // LoggerFromRequest - gets an ign logger from the given http request. 153 func LoggerFromRequest(r *http.Request) Logger { 154 return LoggerFromContext(r.Context()) 155 } 156 157 //////////////////////////////////////////////////////// 158 159 // Clone creates a new logger based on the current logger and sets a new reqID. 160 // This is typically used to customize a logger and still honor the original logger 161 // configuration. 162 func (l *ignLogger) Clone(reqID string) Logger { 163 logger := ignLogger{reqID, true, l.logToStd, l.verbosity, l.RollbarVerbosity} 164 return &logger 165 } 166 167 // Debug sends a debug message to rollbar. The valid types are: 168 // 169 // *http.Request 170 // error 171 // string 172 // map[string]interface{} 173 // int 174 // ignerr.ErrMsg 175 func (l *ignLogger) Debug(interfaces ...interface{}) { 176 if l.verbosity < VerbosityDebug { 177 return 178 } 179 logMsg, msg := processLogInterfaces(l.reqID, interfaces...) 180 if l.RollbarVerbosity >= VerbosityDebug && l.shouldSendToRollbar(msg) { 181 rollbar.Debug(msg...) 182 } 183 if l.logToStd { 184 log.Println(logMsg) 185 } 186 } 187 188 // Info sends an info message to rollbar. The valid types are: 189 // 190 // *http.Request 191 // error 192 // string 193 // map[string]interface{} 194 // int 195 // ignerr.ErrMsg 196 func (l *ignLogger) Info(interfaces ...interface{}) { 197 if l.verbosity < VerbosityInfo { 198 return 199 } 200 logMsg, msg := processLogInterfaces(l.reqID, interfaces...) 201 if l.RollbarVerbosity >= VerbosityInfo && l.shouldSendToRollbar(msg) { 202 rollbar.Info(msg...) 203 } 204 if l.logToStd { 205 log.Println(logMsg) 206 } 207 } 208 209 // Warning sends a warning message to rollbar. The valid types are: 210 // 211 // *http.Request 212 // error 213 // string 214 // map[string]interface{} 215 // int 216 // ign.ErrMsg 217 func (l *ignLogger) Warning(interfaces ...interface{}) { 218 if l.verbosity < VerbosityWarning { 219 return 220 } 221 logMsg, msg := processLogInterfaces(l.reqID, interfaces...) 222 if l.RollbarVerbosity >= VerbosityWarning && l.shouldSendToRollbar(msg) { 223 rollbar.Warning(msg...) 224 } 225 if l.logToStd { 226 log.Println(logMsg) 227 } 228 } 229 230 // Error sends an error message to rollbar. The valid types are: 231 // 232 // *http.Request 233 // error 234 // string 235 // map[string]interface{} 236 // int 237 // ignerr.ErrMsg 238 // If an error is present then a stack trace is captured. If an int is also present then we skip 239 // that number of stack frames. If the map is present it is used as extra custom data in the 240 // item. If a string is present without an error, then we log a message without a stack 241 // trace. If a request is present we extract as much relevant information from it as we can. 242 func (l *ignLogger) Error(interfaces ...interface{}) { 243 if l.verbosity < VerbosityError { 244 return 245 } 246 logMsg, msg := processLogInterfaces(l.reqID, interfaces...) 247 if l.RollbarVerbosity >= VerbosityError && l.shouldSendToRollbar(msg) { 248 rollbar.Error(msg...) 249 } else { 250 } 251 if l.logToStd { 252 log.Println(logMsg) 253 } 254 } 255 256 // Critical sends a critical message to rollbar. The valid types are: 257 // 258 // *http.Request 259 // error 260 // string 261 // map[string]interface{} 262 // int 263 // ignerr.ErrMsg 264 func (l *ignLogger) Critical(interfaces ...interface{}) { 265 logMsg, msg := processLogInterfaces(l.reqID, interfaces...) 266 if l.shouldSendToRollbar(msg) { 267 rollbar.Critical(msg...) 268 } 269 270 if l.logToStd { 271 log.Println(logMsg) 272 } 273 } 274 275 // shouldSendToRollbar is a helper function that validates if a processed 276 // msg can be sent to rollbar. 277 // The 'msg' argument is expected to be the result from a previous call to 278 // processLogInterfaces func. 279 func (l *ignLogger) shouldSendToRollbar(msg []interface{}) bool { 280 if !l.rollbar || rollbar.Token() == "" { 281 return false 282 } 283 284 // Lastly, check if the rollbar msg includes an ign.ErrMsg. If yes, then check for 285 // blacklisted error codes. 286 if len(msg) > 0 { 287 el := msg[len(msg)-1] 288 if data, ok := el.(map[string]interface{}); ok { 289 if statusCode, ok := data["Status Code"]; ok { 290 // change this when having more blacklisted codes 291 if statusCode == http.StatusNotFound { 292 return false 293 } 294 } 295 } 296 } 297 return true 298 } 299 300 // processLogInterfaces is a helper function for log reporting that constructs 301 // a final message to send to rollbar 302 func processLogInterfaces(prefix string, interfaces ...interface{}) (string, []interface{}) { 303 var finalMsg []interface{} 304 str := "[" + prefix + "]" 305 logMsg := "[" + prefix + "]" 306 var hasError bool 307 data := map[string]interface{}{} 308 309 // Iterate over each interface 310 for index, element := range interfaces { 311 312 var errMsg *ErrMsg 313 314 // The element could be a pointer to ErrMsg or an instance of ErrMsg 315 if e, ok := element.(*ErrMsg); ok { 316 errMsg = e 317 } else if e, ok := element.(ErrMsg); ok { 318 errMsg = &e 319 } 320 321 // Create a special log message if the type is ErrMsg 322 if errMsg != nil { 323 // Append the message to the string 324 str += errMsg.LogString() 325 logMsg += errMsg.LogString() 326 finalMsg = append(finalMsg, fmt.Sprintf("[ErrCode:%d] %s. Extra: %v. [Route:%s]", 327 errMsg.ErrCode, errMsg.Msg, errMsg.Extra, errMsg.Route)) 328 329 // The map of data (Error Code, Status Code, etc) can be viewed by 330 // select an occurance of this log message. 331 data["Error Code"] = errMsg.ErrCode 332 data["Status Code"] = errMsg.StatusCode 333 data["Error ID"] = errMsg.ErrID 334 data["Route"] = errMsg.Route 335 336 if errMsg.RemoteAddress != "" { 337 data["Remote Address"] = errMsg.RemoteAddress 338 } 339 340 if errMsg.UserAgent != "" { 341 data["User-Agent"] = errMsg.UserAgent 342 } 343 344 // Append the base error if one exists. 345 if errMsg.BaseError != nil { 346 hasError = true 347 logMsg = fmt.Sprintf("%s. Base error: %+v\n", logMsg, errMsg.BaseError) 348 data["Trace"] = logMsg 349 finalMsg = append(finalMsg, errMsg.BaseError) 350 } 351 352 } else if e, ok := element.(string); ok { 353 // Concatentate strings together 354 str += e 355 logMsg += e 356 } else if e, ok := element.(error); ok { 357 // Append an error if it is present 358 hasError = true 359 // Get a full trace using %+v because we can't be sure of the correct depth 360 logMsg = fmt.Sprintf("%s. Base Error: %+v", logMsg, e) 361 data["Trace"] = logMsg 362 finalMsg = append(finalMsg, e) 363 } else { 364 finalMsg = append(finalMsg, interfaces[index]) 365 } 366 } 367 368 // If there is error, then rollbar will override a string value with the 369 // error data. We can save any string value by putting the string in the 370 // data map. 371 if hasError { 372 data["Message"] = str 373 } else { 374 finalMsg = append(finalMsg, str) 375 } 376 377 data["Prefix"] = prefix 378 finalMsg = append(finalMsg, data) 379 380 return logMsg, finalMsg 381 }