log.go (11120B)
1 package log 2 3 import ( 4 "bytes" 5 e "errors" 6 "fmt" 7 "strings" 8 "sync" 9 10 "text/template" 11 12 "github.com/go-errors/errors" 13 "golang.org/x/net/context" 14 ) 15 16 // TODO(dustin): Finish symbol documentation 17 18 // Config severity integers. 19 const ( 20 LevelDebug = iota 21 LevelInfo = iota 22 LevelWarning = iota 23 LevelError = iota 24 ) 25 26 // Config severity names. 27 const ( 28 LevelNameDebug = "debug" 29 LevelNameInfo = "info" 30 LevelNameWarning = "warning" 31 LevelNameError = "error" 32 ) 33 34 // Seveirty name->integer map. 35 var ( 36 LevelNameMap = map[string]int{ 37 LevelNameDebug: LevelDebug, 38 LevelNameInfo: LevelInfo, 39 LevelNameWarning: LevelWarning, 40 LevelNameError: LevelError, 41 } 42 43 LevelNameMapR = map[int]string{ 44 LevelDebug: LevelNameDebug, 45 LevelInfo: LevelNameInfo, 46 LevelWarning: LevelNameWarning, 47 LevelError: LevelNameError, 48 } 49 ) 50 51 // Errors 52 var ( 53 ErrAdapterAlreadyRegistered = e.New("adapter already registered") 54 ErrFormatEmpty = e.New("format is empty") 55 ErrExcludeLevelNameInvalid = e.New("exclude bypass-level is invalid") 56 ErrNoAdapterConfigured = e.New("no default adapter configured") 57 ErrAdapterIsNil = e.New("adapter is nil") 58 ErrConfigurationNotLoaded = e.New("can not configure because configuration is not loaded") 59 ) 60 61 // Other 62 var ( 63 includeFilters = make(map[string]bool) 64 useIncludeFilters = false 65 excludeFilters = make(map[string]bool) 66 useExcludeFilters = false 67 68 adapters = make(map[string]LogAdapter) 69 70 // TODO(dustin): !! Finish implementing this. 71 excludeBypassLevel = -1 72 ) 73 74 // Add global include filter. 75 func AddIncludeFilter(noun string) { 76 includeFilters[noun] = true 77 useIncludeFilters = true 78 } 79 80 // Remove global include filter. 81 func RemoveIncludeFilter(noun string) { 82 delete(includeFilters, noun) 83 if len(includeFilters) == 0 { 84 useIncludeFilters = false 85 } 86 } 87 88 // Add global exclude filter. 89 func AddExcludeFilter(noun string) { 90 excludeFilters[noun] = true 91 useExcludeFilters = true 92 } 93 94 // Remove global exclude filter. 95 func RemoveExcludeFilter(noun string) { 96 delete(excludeFilters, noun) 97 if len(excludeFilters) == 0 { 98 useExcludeFilters = false 99 } 100 } 101 102 func AddAdapter(name string, la LogAdapter) { 103 if _, found := adapters[name]; found == true { 104 Panic(ErrAdapterAlreadyRegistered) 105 } 106 107 if la == nil { 108 Panic(ErrAdapterIsNil) 109 } 110 111 adapters[name] = la 112 113 if GetDefaultAdapterName() == "" { 114 SetDefaultAdapterName(name) 115 } 116 } 117 118 func ClearAdapters() { 119 adapters = make(map[string]LogAdapter) 120 SetDefaultAdapterName("") 121 } 122 123 type LogAdapter interface { 124 Debugf(lc *LogContext, message *string) error 125 Infof(lc *LogContext, message *string) error 126 Warningf(lc *LogContext, message *string) error 127 Errorf(lc *LogContext, message *string) error 128 } 129 130 // TODO(dustin): !! Also populate whether we've bypassed an exception so that 131 // we can add a template macro to prefix an exclamation of 132 // some sort. 133 type MessageContext struct { 134 Level *string 135 Noun *string 136 Message *string 137 ExcludeBypass bool 138 } 139 140 type LogContext struct { 141 Logger *Logger 142 Ctx context.Context 143 } 144 145 type Logger struct { 146 isConfigured bool 147 an string 148 la LogAdapter 149 t *template.Template 150 systemLevel int 151 noun string 152 } 153 154 func NewLoggerWithAdapterName(noun string, adapterName string) (l *Logger) { 155 l = &Logger{ 156 noun: noun, 157 an: adapterName, 158 } 159 160 return l 161 } 162 163 func NewLogger(noun string) (l *Logger) { 164 l = NewLoggerWithAdapterName(noun, "") 165 166 return l 167 } 168 169 func (l *Logger) Noun() string { 170 return l.noun 171 } 172 173 func (l *Logger) Adapter() LogAdapter { 174 return l.la 175 } 176 177 var ( 178 configureMutex sync.Mutex 179 ) 180 181 func (l *Logger) doConfigure(force bool) { 182 configureMutex.Lock() 183 defer configureMutex.Unlock() 184 185 if l.isConfigured == true && force == false { 186 return 187 } 188 189 if IsConfigurationLoaded() == false { 190 Panic(ErrConfigurationNotLoaded) 191 } 192 193 if l.an == "" { 194 l.an = GetDefaultAdapterName() 195 } 196 197 // If this is empty, then no specific adapter was given or no system 198 // default was configured (which implies that no adapters were registered). 199 // All of our logging will be skipped. 200 if l.an != "" { 201 la, found := adapters[l.an] 202 if found == false { 203 Panic(fmt.Errorf("adapter is not valid: %s", l.an)) 204 } 205 206 l.la = la 207 } 208 209 // Set the level. 210 211 systemLevel, found := LevelNameMap[levelName] 212 if found == false { 213 Panic(fmt.Errorf("log-level not valid: [%s]", levelName)) 214 } 215 216 l.systemLevel = systemLevel 217 218 // Set the form. 219 220 if format == "" { 221 Panic(ErrFormatEmpty) 222 } 223 224 if t, err := template.New("logItem").Parse(format); err != nil { 225 Panic(err) 226 } else { 227 l.t = t 228 } 229 230 l.isConfigured = true 231 } 232 233 func (l *Logger) flattenMessage(lc *MessageContext, format *string, args []interface{}) (string, error) { 234 m := fmt.Sprintf(*format, args...) 235 236 lc.Message = &m 237 238 var b bytes.Buffer 239 if err := l.t.Execute(&b, *lc); err != nil { 240 return "", err 241 } 242 243 return b.String(), nil 244 } 245 246 func (l *Logger) allowMessage(noun string, level int) bool { 247 if _, found := includeFilters[noun]; found == true { 248 return true 249 } 250 251 // If we didn't hit an include filter and we *had* include filters, filter 252 // it out. 253 if useIncludeFilters == true { 254 return false 255 } 256 257 if _, found := excludeFilters[noun]; found == true { 258 return false 259 } 260 261 return true 262 } 263 264 func (l *Logger) makeLogContext(ctx context.Context) *LogContext { 265 return &LogContext{ 266 Ctx: ctx, 267 Logger: l, 268 } 269 } 270 271 type LogMethod func(lc *LogContext, message *string) error 272 273 func (l *Logger) log(ctx context.Context, level int, lm LogMethod, format string, args []interface{}) error { 274 if l.systemLevel > level { 275 return nil 276 } 277 278 // Preempt the normal filter checks if we can unconditionally allow at a 279 // certain level and we've hit that level. 280 // 281 // Notice that this is only relevant if the system-log level is letting 282 // *anything* show logs at the level we came in with. 283 canExcludeBypass := level >= excludeBypassLevel && excludeBypassLevel != -1 284 didExcludeBypass := false 285 286 n := l.Noun() 287 288 if l.allowMessage(n, level) == false { 289 if canExcludeBypass == false { 290 return nil 291 } else { 292 didExcludeBypass = true 293 } 294 } 295 296 levelName, found := LevelNameMapR[level] 297 if found == false { 298 Panic(fmt.Errorf("level not valid: (%d)", level)) 299 } 300 301 levelName = strings.ToUpper(levelName) 302 303 lc := &MessageContext{ 304 Level: &levelName, 305 Noun: &n, 306 ExcludeBypass: didExcludeBypass, 307 } 308 309 if s, err := l.flattenMessage(lc, &format, args); err != nil { 310 return err 311 } else { 312 lc := l.makeLogContext(ctx) 313 if err := lm(lc, &s); err != nil { 314 panic(err) 315 } 316 317 return e.New(s) 318 } 319 } 320 321 func (l *Logger) Debugf(ctx context.Context, format string, args ...interface{}) { 322 l.doConfigure(false) 323 324 if l.la != nil { 325 l.log(ctx, LevelDebug, l.la.Debugf, format, args) 326 } 327 } 328 329 func (l *Logger) Infof(ctx context.Context, format string, args ...interface{}) { 330 l.doConfigure(false) 331 332 if l.la != nil { 333 l.log(ctx, LevelInfo, l.la.Infof, format, args) 334 } 335 } 336 337 func (l *Logger) Warningf(ctx context.Context, format string, args ...interface{}) { 338 l.doConfigure(false) 339 340 if l.la != nil { 341 l.log(ctx, LevelWarning, l.la.Warningf, format, args) 342 } 343 } 344 345 func (l *Logger) mergeStack(err interface{}, format string, args []interface{}) (string, []interface{}) { 346 if format != "" { 347 format += "\n%s" 348 } else { 349 format = "%s" 350 } 351 352 var stackified *errors.Error 353 stackified, ok := err.(*errors.Error) 354 if ok == false { 355 stackified = errors.Wrap(err, 2) 356 } 357 358 args = append(args, stackified.ErrorStack()) 359 360 return format, args 361 } 362 363 func (l *Logger) Errorf(ctx context.Context, errRaw interface{}, format string, args ...interface{}) { 364 l.doConfigure(false) 365 366 var err interface{} 367 368 if errRaw != nil { 369 _, ok := errRaw.(*errors.Error) 370 if ok == true { 371 err = errRaw 372 } else { 373 err = errors.Wrap(errRaw, 1) 374 } 375 } 376 377 if l.la != nil { 378 if errRaw != nil { 379 format, args = l.mergeStack(err, format, args) 380 } 381 382 l.log(ctx, LevelError, l.la.Errorf, format, args) 383 } 384 } 385 386 func (l *Logger) ErrorIff(ctx context.Context, errRaw interface{}, format string, args ...interface{}) { 387 if errRaw == nil { 388 return 389 } 390 391 var err interface{} 392 393 _, ok := errRaw.(*errors.Error) 394 if ok == true { 395 err = errRaw 396 } else { 397 err = errors.Wrap(errRaw, 1) 398 } 399 400 l.Errorf(ctx, err, format, args...) 401 } 402 403 func (l *Logger) Panicf(ctx context.Context, errRaw interface{}, format string, args ...interface{}) { 404 l.doConfigure(false) 405 406 var err interface{} 407 408 _, ok := errRaw.(*errors.Error) 409 if ok == true { 410 err = errRaw 411 } else { 412 err = errors.Wrap(errRaw, 1) 413 } 414 415 if l.la != nil { 416 format, args = l.mergeStack(err, format, args) 417 err = l.log(ctx, LevelError, l.la.Errorf, format, args) 418 } 419 420 Panic(err.(error)) 421 } 422 423 func (l *Logger) PanicIff(ctx context.Context, errRaw interface{}, format string, args ...interface{}) { 424 if errRaw == nil { 425 return 426 } 427 428 var err interface{} 429 430 _, ok := errRaw.(*errors.Error) 431 if ok == true { 432 err = errRaw 433 } else { 434 err = errors.Wrap(errRaw, 1) 435 } 436 437 l.Panicf(ctx, err.(error), format, args...) 438 } 439 440 func Wrap(err interface{}) *errors.Error { 441 es, ok := err.(*errors.Error) 442 if ok == true { 443 return es 444 } else { 445 return errors.Wrap(err, 1) 446 } 447 } 448 449 func Errorf(message string, args ...interface{}) *errors.Error { 450 err := fmt.Errorf(message, args...) 451 return errors.Wrap(err, 1) 452 } 453 454 func Panic(err interface{}) { 455 _, ok := err.(*errors.Error) 456 if ok == true { 457 panic(err) 458 } else { 459 panic(errors.Wrap(err, 1)) 460 } 461 } 462 463 func Panicf(message string, args ...interface{}) { 464 err := Errorf(message, args...) 465 Panic(err) 466 } 467 468 func PanicIf(err interface{}) { 469 if err == nil { 470 return 471 } 472 473 _, ok := err.(*errors.Error) 474 if ok == true { 475 panic(err) 476 } else { 477 panic(errors.Wrap(err, 1)) 478 } 479 } 480 481 // Is checks if the left ("actual") error equals the right ("against") error. 482 // The right must be an unwrapped error (the kind that you'd initialize as a 483 // global variable). The left can be a wrapped or unwrapped error. 484 func Is(actual, against error) bool { 485 // If it's an unwrapped error. 486 if _, ok := actual.(*errors.Error); ok == false { 487 return actual == against 488 } 489 490 return errors.Is(actual, against) 491 } 492 493 // Print is a utility function to prevent the caller from having to import the 494 // third-party library. 495 func PrintError(err error) { 496 wrapped := Wrap(err) 497 fmt.Printf("Stack:\n\n%s\n", wrapped.ErrorStack()) 498 } 499 500 // PrintErrorf is a utility function to prevent the caller from having to 501 // import the third-party library. 502 func PrintErrorf(err error, format string, args ...interface{}) { 503 wrapped := Wrap(err) 504 505 fmt.Printf(format, args...) 506 fmt.Printf("\n") 507 fmt.Printf("Stack:\n\n%s\n", wrapped.ErrorStack()) 508 } 509 510 func init() { 511 if format == "" { 512 format = defaultFormat 513 } 514 515 if levelName == "" { 516 levelName = defaultLevelName 517 } 518 519 if includeNouns != "" { 520 for _, noun := range strings.Split(includeNouns, ",") { 521 AddIncludeFilter(noun) 522 } 523 } 524 525 if excludeNouns != "" { 526 for _, noun := range strings.Split(excludeNouns, ",") { 527 AddExcludeFilter(noun) 528 } 529 } 530 531 if excludeBypassLevelName != "" { 532 var found bool 533 if excludeBypassLevel, found = LevelNameMap[excludeBypassLevelName]; found == false { 534 panic(ErrExcludeLevelNameInvalid) 535 } 536 } 537 }