github.com/fff-chain/go-fff@v0.0.0-20220726032732-1c84420b8a99/log/logger.go (about)

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