package log import ( "fmt" "os" "time" "github.com/go-stack/stack" ) const timeKey = "t" const lvlKey = "lvl" const msgKey = "msg" const ctxKey = "ctx" const errorKey = "LOG15_ERROR" const skipLevel = 2 type Lvl int const ( LvlCrit Lvl = iota LvlError LvlWarn LvlInfo LvlDebug LvlTrace ) // AlignedString returns a 5-character string containing the name of a Lvl. func (l Lvl) AlignedString() string { switch l { case LvlTrace: return "TRACE" case LvlDebug: return "DEBUG" case LvlInfo: return "INFO " case LvlWarn: return "WARN " case LvlError: return "ERROR" case LvlCrit: return "CRIT " default: panic("bad level") } } // String returns the name of a Lvl. func (l Lvl) String() string { switch l { case LvlTrace: return "trce" case LvlDebug: return "dbug" case LvlInfo: return "info" case LvlWarn: return "warn" case LvlError: return "eror" case LvlCrit: return "crit" default: panic("bad level") } } // LvlFromString returns the appropriate Lvl from a string name. // Useful for parsing command line args and configuration files. func LvlFromString(lvlString string) (Lvl, error) { switch lvlString { case "trace", "trce": return LvlTrace, nil case "debug", "dbug": return LvlDebug, nil case "info": return LvlInfo, nil case "warn": return LvlWarn, nil case "error", "eror": return LvlError, nil case "crit": return LvlCrit, nil default: return LvlDebug, fmt.Errorf("unknown level: %v", lvlString) } } // A Record is what a Logger asks its handler to write type Record struct { Time time.Time Lvl Lvl Msg string Ctx []interface{} Call stack.Call KeyNames RecordKeyNames } // RecordKeyNames gets stored in a Record when the write function is executed. type RecordKeyNames struct { Time string Msg string Lvl string Ctx string } // A Logger writes key/value pairs to a Handler type Logger interface { // New returns a new Logger that has this logger's context plus the given context New(ctx ...interface{}) Logger // GetHandler gets the handler associated with the logger. GetHandler() Handler // SetHandler updates the logger to write records to the specified handler. SetHandler(h Handler) // Log a message at the trace level with context key/value pairs // // # Usage // // log.Trace("msg") // log.Trace("msg", "key1", val1) // log.Trace("msg", "key1", val1, "key2", val2) Trace(msg string, ctx ...interface{}) // Log a message at the debug level with context key/value pairs // // # Usage Examples // // log.Debug("msg") // log.Debug("msg", "key1", val1) // log.Debug("msg", "key1", val1, "key2", val2) Debug(msg string, ctx ...interface{}) // Log a message at the info level with context key/value pairs // // # Usage Examples // // log.Info("msg") // log.Info("msg", "key1", val1) // log.Info("msg", "key1", val1, "key2", val2) Info(msg string, ctx ...interface{}) // Log a message at the warn level with context key/value pairs // // # Usage Examples // // log.Warn("msg") // log.Warn("msg", "key1", val1) // log.Warn("msg", "key1", val1, "key2", val2) Warn(msg string, ctx ...interface{}) // Log a message at the error level with context key/value pairs // // # Usage Examples // // log.Error("msg") // log.Error("msg", "key1", val1) // log.Error("msg", "key1", val1, "key2", val2) Error(msg string, ctx ...interface{}) // Log a message at the crit level with context key/value pairs, and then exit. // // # Usage Examples // // log.Crit("msg") // log.Crit("msg", "key1", val1) // log.Crit("msg", "key1", val1, "key2", val2) Crit(msg string, ctx ...interface{}) } type logger struct { ctx []interface{} h *swapHandler } func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) { l.h.Log(&Record{ Time: time.Now(), Lvl: lvl, Msg: msg, Ctx: newContext(l.ctx, ctx), Call: stack.Caller(skip), KeyNames: RecordKeyNames{ Time: timeKey, Msg: msgKey, Lvl: lvlKey, Ctx: ctxKey, }, }) } func (l *logger) New(ctx ...interface{}) Logger { child := &logger{newContext(l.ctx, ctx), new(swapHandler)} child.SetHandler(l.h) return child } func newContext(prefix []interface{}, suffix []interface{}) []interface{} { normalizedSuffix := normalize(suffix) newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) n := copy(newCtx, prefix) copy(newCtx[n:], normalizedSuffix) return newCtx } func (l *logger) Trace(msg string, ctx ...interface{}) { l.write(msg, LvlTrace, ctx, skipLevel) } func (l *logger) Debug(msg string, ctx ...interface{}) { l.write(msg, LvlDebug, ctx, skipLevel) } func (l *logger) Info(msg string, ctx ...interface{}) { l.write(msg, LvlInfo, ctx, skipLevel) } func (l *logger) Warn(msg string, ctx ...interface{}) { l.write(msg, LvlWarn, ctx, skipLevel) } func (l *logger) Error(msg string, ctx ...interface{}) { l.write(msg, LvlError, ctx, skipLevel) } func (l *logger) Crit(msg string, ctx ...interface{}) { l.write(msg, LvlCrit, ctx, skipLevel) os.Exit(1) } func (l *logger) GetHandler() Handler { return l.h.Get() } func (l *logger) SetHandler(h Handler) { l.h.Swap(h) } func normalize(ctx []interface{}) []interface{} { // if the caller passed a Ctx object, then expand it if len(ctx) == 1 { if ctxMap, ok := ctx[0].(Ctx); ok { ctx = ctxMap.toArray() } } // ctx needs to be even because it's a series of key/value pairs // no one wants to check for errors on logging functions, // so instead of erroring on bad input, we'll just make sure // that things are the right length and users can fix bugs // when they see the output looks wrong if len(ctx)%2 != 0 { ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") } return ctx } // Lazy allows you to defer calculation of a logged value that is expensive // to compute until it is certain that it must be evaluated with the given filters. // // Lazy may also be used in conjunction with a Logger's New() function // to generate a child logger which always reports the current value of changing // state. // // You may wrap any function which takes no arguments to Lazy. It may return any // number of values of any type. type Lazy struct { Fn interface{} } // Ctx is a map of key/value pairs to pass as context to a log function // Use this only if you really need greater safety around the arguments you pass // to the logging functions. type Ctx map[string]interface{} func (c Ctx) toArray() []interface{} { arr := make([]interface{}, len(c)*2) i := 0 for k, v := range c { arr[i] = k arr[i+1] = v i += 2 } return arr } func NewFileLvlHandler(logPath string, maxBytesSize uint, level string, rotateHours int) Handler { rfh, err := RotatingFileHandler(logPath, maxBytesSize, LogfmtFormat(), rotateHours) if err != nil { panic(err) } logLevel, err := LvlFromString(level) if err != nil { panic(err) } return LvlFilterHandler(logLevel, rfh) }