2017-02-20 18:39:36 +03:00
|
|
|
package log
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-02-20 19:05:15 +03:00
|
|
|
"os"
|
2017-02-20 18:39:36 +03:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/go-stack/stack"
|
|
|
|
)
|
|
|
|
|
|
|
|
const timeKey = "t"
|
|
|
|
const lvlKey = "lvl"
|
|
|
|
const msgKey = "msg"
|
cmd, dashboard, log: log collection and exploration (#17097)
* cmd, dashboard, internal, log, node: logging feature
* cmd, dashboard, internal, log: requested changes
* dashboard, vendor: gofmt, govendor, use vendored file watcher
* dashboard, log: gofmt -s -w, goimports
* dashboard, log: gosimple
2018-07-11 10:59:04 +03:00
|
|
|
const ctxKey = "ctx"
|
2017-02-20 18:39:36 +03:00
|
|
|
const errorKey = "LOG15_ERROR"
|
2018-06-14 12:21:17 +03:00
|
|
|
const skipLevel = 2
|
2017-02-20 18:39:36 +03:00
|
|
|
|
|
|
|
type Lvl int
|
|
|
|
|
|
|
|
const (
|
|
|
|
LvlCrit Lvl = iota
|
|
|
|
LvlError
|
|
|
|
LvlWarn
|
|
|
|
LvlInfo
|
|
|
|
LvlDebug
|
2017-02-20 19:05:15 +03:00
|
|
|
LvlTrace
|
2017-02-20 18:39:36 +03:00
|
|
|
)
|
|
|
|
|
2018-05-22 10:28:43 +03:00
|
|
|
// AlignedString returns a 5-character string containing the name of a Lvl.
|
2017-02-23 20:30:32 +03:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-30 16:33:03 +03:00
|
|
|
// String returns the name of a Lvl.
|
2017-02-20 18:39:36 +03:00
|
|
|
func (l Lvl) String() string {
|
|
|
|
switch l {
|
2017-02-20 19:05:15 +03:00
|
|
|
case LvlTrace:
|
|
|
|
return "trce"
|
2017-02-20 18:39:36 +03:00
|
|
|
case LvlDebug:
|
|
|
|
return "dbug"
|
|
|
|
case LvlInfo:
|
|
|
|
return "info"
|
|
|
|
case LvlWarn:
|
|
|
|
return "warn"
|
|
|
|
case LvlError:
|
|
|
|
return "eror"
|
|
|
|
case LvlCrit:
|
|
|
|
return "crit"
|
|
|
|
default:
|
|
|
|
panic("bad level")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-22 10:28:43 +03:00
|
|
|
// LvlFromString returns the appropriate Lvl from a string name.
|
2017-02-20 18:39:36 +03:00
|
|
|
// Useful for parsing command line args and configuration files.
|
|
|
|
func LvlFromString(lvlString string) (Lvl, error) {
|
|
|
|
switch lvlString {
|
2017-02-20 19:05:15 +03:00
|
|
|
case "trace", "trce":
|
|
|
|
return LvlTrace, nil
|
2017-02-20 18:39:36 +03:00
|
|
|
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:
|
2019-11-28 12:53:06 +03:00
|
|
|
return LvlDebug, fmt.Errorf("unknown level: %v", lvlString)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2018-05-22 10:28:43 +03:00
|
|
|
// RecordKeyNames gets stored in a Record when the write function is executed.
|
2017-02-20 18:39:36 +03:00
|
|
|
type RecordKeyNames struct {
|
|
|
|
Time string
|
|
|
|
Msg string
|
|
|
|
Lvl string
|
cmd, dashboard, log: log collection and exploration (#17097)
* cmd, dashboard, internal, log, node: logging feature
* cmd, dashboard, internal, log: requested changes
* dashboard, vendor: gofmt, govendor, use vendored file watcher
* dashboard, log: gofmt -s -w, goimports
* dashboard, log: gosimple
2018-07-11 10:59:04 +03:00
|
|
|
Ctx string
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
2023-02-22 15:39:41 +03:00
|
|
|
// 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)
|
2017-02-20 19:05:15 +03:00
|
|
|
Trace(msg string, ctx ...interface{})
|
2023-02-22 15:39:41 +03:00
|
|
|
|
|
|
|
// 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)
|
2017-02-20 18:39:36 +03:00
|
|
|
Debug(msg string, ctx ...interface{})
|
2023-02-22 15:39:41 +03:00
|
|
|
|
|
|
|
// 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)
|
2017-02-20 18:39:36 +03:00
|
|
|
Info(msg string, ctx ...interface{})
|
2023-02-22 15:39:41 +03:00
|
|
|
|
|
|
|
// 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)
|
2017-02-20 18:39:36 +03:00
|
|
|
Warn(msg string, ctx ...interface{})
|
2023-02-22 15:39:41 +03:00
|
|
|
|
|
|
|
// 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)
|
2017-02-20 18:39:36 +03:00
|
|
|
Error(msg string, ctx ...interface{})
|
2023-02-22 15:39:41 +03:00
|
|
|
|
|
|
|
// 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)
|
2017-02-20 18:39:36 +03:00
|
|
|
Crit(msg string, ctx ...interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
type logger struct {
|
|
|
|
ctx []interface{}
|
|
|
|
h *swapHandler
|
|
|
|
}
|
|
|
|
|
2018-06-14 12:21:17 +03:00
|
|
|
func (l *logger) write(msg string, lvl Lvl, ctx []interface{}, skip int) {
|
2023-09-07 15:48:49 +03:00
|
|
|
record := &Record{
|
2017-02-20 18:39:36 +03:00
|
|
|
Time: time.Now(),
|
|
|
|
Lvl: lvl,
|
|
|
|
Msg: msg,
|
|
|
|
Ctx: newContext(l.ctx, ctx),
|
|
|
|
KeyNames: RecordKeyNames{
|
|
|
|
Time: timeKey,
|
|
|
|
Msg: msgKey,
|
|
|
|
Lvl: lvlKey,
|
cmd, dashboard, log: log collection and exploration (#17097)
* cmd, dashboard, internal, log, node: logging feature
* cmd, dashboard, internal, log: requested changes
* dashboard, vendor: gofmt, govendor, use vendored file watcher
* dashboard, log: gofmt -s -w, goimports
* dashboard, log: gosimple
2018-07-11 10:59:04 +03:00
|
|
|
Ctx: ctxKey,
|
2017-02-20 18:39:36 +03:00
|
|
|
},
|
2023-09-07 15:48:49 +03:00
|
|
|
}
|
|
|
|
if stackEnabled.Load() {
|
|
|
|
record.Call = stack.Caller(skip)
|
|
|
|
}
|
|
|
|
l.h.Log(record)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-02-20 19:05:15 +03:00
|
|
|
func (l *logger) Trace(msg string, ctx ...interface{}) {
|
2018-06-14 12:21:17 +03:00
|
|
|
l.write(msg, LvlTrace, ctx, skipLevel)
|
2017-02-20 19:05:15 +03:00
|
|
|
}
|
|
|
|
|
2017-02-20 18:39:36 +03:00
|
|
|
func (l *logger) Debug(msg string, ctx ...interface{}) {
|
2018-06-14 12:21:17 +03:00
|
|
|
l.write(msg, LvlDebug, ctx, skipLevel)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *logger) Info(msg string, ctx ...interface{}) {
|
2018-06-14 12:21:17 +03:00
|
|
|
l.write(msg, LvlInfo, ctx, skipLevel)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *logger) Warn(msg string, ctx ...interface{}) {
|
2018-06-14 12:21:17 +03:00
|
|
|
l.write(msg, LvlWarn, ctx, skipLevel)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *logger) Error(msg string, ctx ...interface{}) {
|
2018-06-14 12:21:17 +03:00
|
|
|
l.write(msg, LvlError, ctx, skipLevel)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *logger) Crit(msg string, ctx ...interface{}) {
|
2018-06-14 12:21:17 +03:00
|
|
|
l.write(msg, LvlCrit, ctx, skipLevel)
|
2017-02-20 19:05:15 +03:00
|
|
|
os.Exit(1)
|
2017-02-20 18:39:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|