github.com/ssgreg/logf@v1.4.1/logger.go (about)

     1  package logf
     2  
     3  import (
     4  	"sync/atomic"
     5  	"time"
     6  )
     7  
     8  // NewLogger returns a new Logger with a given Level and EntryWriter.
     9  func NewLogger(level LevelCheckerGetter, w EntryWriter) *Logger {
    10  	return &Logger{
    11  		level: level.LevelChecker(),
    12  		id:    atomic.AddInt32(&nextID, 1),
    13  		w:     w,
    14  	}
    15  }
    16  
    17  // NewDisabledLogger return a new Logger that logs nothing as fast as
    18  // possible.
    19  func NewDisabledLogger() *Logger {
    20  	return NewLogger(
    21  		LevelCheckerGetterFunc(func() LevelChecker {
    22  			return func(Level) bool {
    23  				return false
    24  			}
    25  		}), nil)
    26  }
    27  
    28  var defaultDisabledLogger = NewDisabledLogger()
    29  
    30  // DisabledLogger returns a default instance of a Logger that logs nothing
    31  // as fast as possible.
    32  func DisabledLogger() *Logger {
    33  	return defaultDisabledLogger
    34  }
    35  
    36  // Logger is the fast, asynchronous, structured logger.
    37  //
    38  // The Logger wraps EntryWriter to check logging level and provide a bit of
    39  // syntactic sugar.
    40  type Logger struct {
    41  	level LevelChecker
    42  	id    int32
    43  	w     EntryWriter
    44  
    45  	fields     []Field
    46  	name       string
    47  	addCaller  bool
    48  	callerSkip int
    49  }
    50  
    51  // LogFunc allows to log a message with a bound level.
    52  type LogFunc func(string, ...Field)
    53  
    54  // AtLevel calls the given fn if logging a message at the specified level
    55  // is enabled, passing a LogFunc with the bound level.
    56  func (l *Logger) AtLevel(lvl Level, fn func(LogFunc)) {
    57  	if !l.level(lvl) {
    58  		return
    59  	}
    60  
    61  	fn(func(text string, fs ...Field) {
    62  		l.write(lvl, text, fs)
    63  	})
    64  }
    65  
    66  // WithLevel returns a new logger with the given additional level checker.
    67  func (l *Logger) WithLevel(level LevelCheckerGetter) *Logger {
    68  	cc := l.clone()
    69  	cc.level = func(lvl Level) bool {
    70  		return level.LevelChecker()(lvl) && l.level(lvl)
    71  	}
    72  
    73  	return cc
    74  }
    75  
    76  // WithName returns a new Logger adding the given name to the calling one.
    77  // Name separator is a period.
    78  //
    79  // Loggers have no name by default.
    80  func (l *Logger) WithName(n string) *Logger {
    81  	if n == "" {
    82  		return l
    83  	}
    84  
    85  	cc := l.clone()
    86  	if cc.name == "" {
    87  		cc.name = n
    88  	} else {
    89  		cc.name += "." + n
    90  	}
    91  
    92  	return cc
    93  }
    94  
    95  // WithCaller returns a new Logger that adds a special annotation parameters
    96  // to each logging message, such as the filename and line number of a caller.
    97  func (l *Logger) WithCaller() *Logger {
    98  	cc := l.clone()
    99  	cc.addCaller = true
   100  
   101  	return cc
   102  }
   103  
   104  // WithCallerSkip returns a new Logger with increased number of skipped
   105  // frames. It's usable to build a custom wrapper for the Logger.
   106  func (l *Logger) WithCallerSkip(skip int) *Logger {
   107  	cc := l.clone()
   108  	cc.callerSkip = skip
   109  
   110  	return cc
   111  }
   112  
   113  // With returns a new Logger with the given additional fields.
   114  func (l *Logger) With(fs ...Field) *Logger {
   115  	// This code attempts to archive optimum performance with minimum
   116  	// allocations count. Do not change it unless the following benchmarks
   117  	// will show a better performance:
   118  	// - BenchmarkAccumulateFields
   119  	// - BenchmarkAccumulateFieldsWithAccumulatedFields
   120  
   121  	var cc *Logger
   122  	if len(l.fields) == 0 {
   123  		// The fastest way. Use passed 'fs' as is.
   124  		cc = l.clone()
   125  		cc.fields = fs
   126  	} else {
   127  		// The less efficient path forces us to copy parent's fields.
   128  		c := make([]Field, 0, len(l.fields)+len(fs))
   129  		c = append(c, l.fields...)
   130  		c = append(c, fs...)
   131  
   132  		cc = l.clone()
   133  		cc.fields = c
   134  	}
   135  
   136  	for i := range cc.fields[len(l.fields):] {
   137  		snapshotField(&cc.fields[i])
   138  	}
   139  
   140  	return cc
   141  }
   142  
   143  // Debug logs a debug message with the given text, optional fields and
   144  // fields passed to the Logger using With function.
   145  func (l *Logger) Debug(text string, fs ...Field) {
   146  	if !l.level(LevelDebug) {
   147  		return
   148  	}
   149  
   150  	l.write(LevelDebug, text, fs)
   151  }
   152  
   153  // Info logs an info message with the given text, optional fields and
   154  // fields passed to the Logger using With function.
   155  func (l *Logger) Info(text string, fs ...Field) {
   156  	if !l.level(LevelInfo) {
   157  		return
   158  	}
   159  
   160  	l.write(LevelInfo, text, fs)
   161  }
   162  
   163  // Warn logs a warning message with the given text, optional fields and
   164  // fields passed to the Logger using With function.
   165  func (l *Logger) Warn(text string, fs ...Field) {
   166  	if !l.level(LevelWarn) {
   167  		return
   168  	}
   169  
   170  	l.write(LevelWarn, text, fs)
   171  }
   172  
   173  // Error logs an error message with the given text, optional fields and
   174  // fields passed to the Logger using With function.
   175  func (l *Logger) Error(text string, fs ...Field) {
   176  	if !l.level(LevelError) {
   177  		return
   178  	}
   179  
   180  	l.write(LevelError, text, fs)
   181  }
   182  
   183  func (l *Logger) write(lv Level, text string, fs []Field) {
   184  	// Snapshot non-const fields.
   185  	for i := range fs {
   186  		snapshotField(&fs[i])
   187  	}
   188  
   189  	e := Entry{l.id, l.name, l.fields, fs, lv, time.Now(), text, EntryCaller{}}
   190  	if l.addCaller {
   191  		e.Caller = NewEntryCaller(2 + l.callerSkip)
   192  	}
   193  
   194  	l.w.WriteEntry(e)
   195  }
   196  
   197  func (l *Logger) clone() *Logger {
   198  	// Field names should be omitted in order not to forget the new fields.
   199  	return &Logger{
   200  		l.level,
   201  		atomic.AddInt32(&nextID, 1),
   202  		l.w,
   203  		l.fields,
   204  		l.name,
   205  		l.addCaller,
   206  		l.callerSkip,
   207  	}
   208  }
   209  
   210  var nextID int32