aboutsummaryrefslogblamecommitdiffstats
path: root/log/logger.go
blob: 15c83a9b25d1f3b76bbce20922f875f2c6d417f6 (plain) (tree)
1
2
3
4
5



             
            

















                                   
                

 




















                                                                     

                              

                             


















                                                                

                                    










































                                                                                         
                                             








































                                                                           



                                                        

















                                                        
                  



























































                                                                                                    
package log

import (
    "fmt"
    "os"
    "time"

    "github.com/go-stack/stack"
)

const timeKey = "t"
const lvlKey = "lvl"
const msgKey = "msg"
const errorKey = "LOG15_ERROR"

type Lvl int

const (
    LvlCrit Lvl = iota
    LvlError
    LvlWarn
    LvlInfo
    LvlDebug
    LvlTrace
)

// Aligned 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")
    }
}

// Strings 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")
    }
}

// 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
}

type RecordKeyNames struct {
    Time string
    Msg  string
    Lvl  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 given level with context key/value pairs
    Trace(msg string, ctx ...interface{})
    Debug(msg string, ctx ...interface{})
    Info(msg string, ctx ...interface{})
    Warn(msg string, ctx ...interface{})
    Error(msg string, ctx ...interface{})
    Crit(msg string, ctx ...interface{})
}

type logger struct {
    ctx []interface{}
    h   *swapHandler
}

func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) {
    l.h.Log(&Record{
        Time: time.Now(),
        Lvl:  lvl,
        Msg:  msg,
        Ctx:  newContext(l.ctx, ctx),
        Call: stack.Caller(2),
        KeyNames: RecordKeyNames{
            Time: timeKey,
            Msg:  msgKey,
            Lvl:  lvlKey,
        },
    })
}

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)
}

func (l *logger) Debug(msg string, ctx ...interface{}) {
    l.write(msg, LvlDebug, ctx)
}

func (l *logger) Info(msg string, ctx ...interface{}) {
    l.write(msg, LvlInfo, ctx)
}

func (l *logger) Warn(msg string, ctx ...interface{}) {
    l.write(msg, LvlWarn, ctx)
}

func (l *logger) Error(msg string, ctx ...interface{}) {
    l.write(msg, LvlError, ctx)
}

func (l *logger) Crit(msg string, ctx ...interface{}) {
    l.write(msg, LvlCrit, ctx)
    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
}