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 }