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