github.com/sixexorg/magnetic-ring@v0.0.0-20191119090307-31705a21e419/log/logger.go (about)

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/go-stack/stack"
     9  )
    10  
    11  const timeKey = "t"
    12  const lvlKey = "lvl"
    13  const msgKey = "msg"
    14  const errorKey = "LOG15_ERROR"
    15  
    16  type Lvl int
    17  
    18  const (
    19  	LvlCrit Lvl = iota
    20  	LvlError
    21  	LvlWarn
    22  	LvlInfo
    23  	LvlDebug
    24  	LvlTrace
    25  )
    26  
    27  // Aligned returns a 5-character string containing the name of a Lvl.
    28  func (l Lvl) AlignedString() string {
    29  	switch l {
    30  	case LvlTrace:
    31  		return "TRACE"
    32  	case LvlDebug:
    33  		return "DEBUG"
    34  	case LvlInfo:
    35  		return "INFO "
    36  	case LvlWarn:
    37  		return "WARN "
    38  	case LvlError:
    39  		return "ERROR"
    40  	case LvlCrit:
    41  		return "CRIT "
    42  	default:
    43  		panic("bad level")
    44  	}
    45  }
    46  
    47  // Strings returns the name of a Lvl.
    48  func (l Lvl) String() string {
    49  	switch l {
    50  	case LvlTrace:
    51  		return "trce"
    52  	case LvlDebug:
    53  		return "dbug"
    54  	case LvlInfo:
    55  		return "info"
    56  	case LvlWarn:
    57  		return "warn"
    58  	case LvlError:
    59  		return "eror"
    60  	case LvlCrit:
    61  		return "crit"
    62  	default:
    63  		panic("bad level")
    64  	}
    65  }
    66  
    67  // Returns the appropriate Lvl from a string name.
    68  // Useful for parsing command line args and configuration files.
    69  func LvlFromString(lvlString string) (Lvl, error) {
    70  	switch lvlString {
    71  	case "trace", "trce":
    72  		return LvlTrace, nil
    73  	case "debug", "dbug":
    74  		return LvlDebug, nil
    75  	case "info":
    76  		return LvlInfo, nil
    77  	case "warn":
    78  		return LvlWarn, nil
    79  	case "error", "eror":
    80  		return LvlError, nil
    81  	case "crit":
    82  		return LvlCrit, nil
    83  	default:
    84  		return LvlDebug, fmt.Errorf("Unknown level: %v", lvlString)
    85  	}
    86  }
    87  
    88  // A Record is what a Logger asks its handler to write
    89  type Record struct {
    90  	Time     time.Time
    91  	Lvl      Lvl
    92  	Msg      string
    93  	Ctx      []interface{}
    94  	Call     stack.Call
    95  	KeyNames RecordKeyNames
    96  }
    97  
    98  type RecordKeyNames struct {
    99  	Time string
   100  	Msg  string
   101  	Lvl  string
   102  }
   103  
   104  // A Logger writes key/value pairs to a Handler
   105  type Logger interface {
   106  	// New returns a new Logger that has this logger's context plus the given context
   107  	New(ctx ...interface{}) Logger
   108  
   109  	// GetHandler gets the handler associated with the logger.
   110  	GetHandler() Handler
   111  
   112  	// SetHandler updates the logger to write records to the specified handler.
   113  	SetHandler(h Handler)
   114  
   115  	// Log a message at the given level with context key/value pairs
   116  	Trace(msg string, ctx ...interface{})
   117  	Debug(msg string, ctx ...interface{})
   118  	Info(msg string, ctx ...interface{})
   119  	Warn(msg string, ctx ...interface{})
   120  	Error(msg string, ctx ...interface{})
   121  	Crit(msg string, ctx ...interface{})
   122  }
   123  
   124  type logger struct {
   125  	ctx []interface{}
   126  	h   *swapHandler
   127  }
   128  
   129  func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) {
   130  	l.h.Log(&Record{
   131  		Time: time.Now(),
   132  		Lvl:  lvl,
   133  		Msg:  msg,
   134  		Ctx:  newContext(l.ctx, ctx),
   135  		Call: stack.Caller(2),
   136  		KeyNames: RecordKeyNames{
   137  			Time: timeKey,
   138  			Msg:  msgKey,
   139  			Lvl:  lvlKey,
   140  		},
   141  	})
   142  }
   143  
   144  func (l *logger) New(ctx ...interface{}) Logger {
   145  	child := &logger{newContext(l.ctx, ctx), new(swapHandler)}
   146  	child.SetHandler(l.h)
   147  	return child
   148  }
   149  
   150  func newContext(prefix []interface{}, suffix []interface{}) []interface{} {
   151  	normalizedSuffix := normalize(suffix)
   152  	newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix))
   153  	n := copy(newCtx, prefix)
   154  	copy(newCtx[n:], normalizedSuffix)
   155  	return newCtx
   156  }
   157  
   158  func (l *logger) Trace(msg string, ctx ...interface{}) {
   159  	l.write(msg, LvlTrace, ctx)
   160  }
   161  
   162  func (l *logger) Debug(msg string, ctx ...interface{}) {
   163  	l.write(msg, LvlDebug, ctx)
   164  }
   165  
   166  func (l *logger) Info(msg string, ctx ...interface{}) {
   167  	l.write(msg, LvlInfo, ctx)
   168  }
   169  
   170  func (l *logger) Warn(msg string, ctx ...interface{}) {
   171  	l.write(msg, LvlWarn, ctx)
   172  }
   173  
   174  func (l *logger) Error(msg string, ctx ...interface{}) {
   175  	l.write(msg, LvlError, ctx)
   176  }
   177  
   178  func (l *logger) Crit(msg string, ctx ...interface{}) {
   179  	l.write(msg, LvlCrit, ctx)
   180  	os.Exit(1)
   181  }
   182  
   183  func (l *logger) GetHandler() Handler {
   184  	return l.h.Get()
   185  }
   186  
   187  func (l *logger) SetHandler(h Handler) {
   188  	l.h.Swap(h)
   189  }
   190  
   191  func normalize(ctx []interface{}) []interface{} {
   192  	// if the caller passed a Ctx object, then expand it
   193  	if len(ctx) == 1 {
   194  		if ctxMap, ok := ctx[0].(Ctx); ok {
   195  			ctx = ctxMap.toArray()
   196  		}
   197  	}
   198  
   199  	// ctx needs to be even because it's a series of key/value pairs
   200  	// no one wants to check for errors on logging functions,
   201  	// so instead of erroring on bad input, we'll just make sure
   202  	// that things are the right length and users can fix bugs
   203  	// when they see the output looks wrong
   204  	if len(ctx)%2 != 0 {
   205  		ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil")
   206  	}
   207  
   208  	return ctx
   209  }
   210  
   211  // Lazy allows you to defer calculation of a logged value that is expensive
   212  // to compute until it is certain that it must be evaluated with the given filters.
   213  //
   214  // Lazy may also be used in conjunction with a Logger's New() function
   215  // to generate a child logger which always reports the current value of changing
   216  // state.
   217  //
   218  // You may wrap any function which takes no arguments to Lazy. It may return any
   219  // number of values of any type.
   220  type Lazy struct {
   221  	Fn interface{}
   222  }
   223  
   224  // Ctx is a map of key/value pairs to pass as context to a log function
   225  // Use this only if you really need greater safety around the arguments you pass
   226  // to the logging functions.
   227  type Ctx map[string]interface{}
   228  
   229  func (c Ctx) toArray() []interface{} {
   230  	arr := make([]interface{}, len(c)*2)
   231  
   232  	i := 0
   233  	for k, v := range c {
   234  		arr[i] = k
   235  		arr[i+1] = v
   236  		i += 2
   237  	}
   238  
   239  	return arr
   240  }