github.com/palisadeinc/bor@v0.0.0-20230615125219-ab7196213d15/log/handler.go (about) 1 package log 2 3 import ( 4 "fmt" 5 "io" 6 "net" 7 "os" 8 "reflect" 9 "sync" 10 11 "github.com/go-stack/stack" 12 ) 13 14 // Handler defines where and how log records are written. 15 // A Logger prints its log records by writing to a Handler. 16 // Handlers are composable, providing you great flexibility in combining 17 // them to achieve the logging structure that suits your applications. 18 type Handler interface { 19 Log(r *Record) error 20 Level() Lvl 21 } 22 23 // FuncHandler returns a Handler that logs records with the given 24 // function. 25 func FuncHandler(fn func(r *Record) error, lvl Lvl) Handler { 26 return funcHandler{fn, lvl} 27 } 28 29 type funcHandler struct { 30 log func(r *Record) error 31 lvl Lvl 32 } 33 34 func (h funcHandler) Log(r *Record) error { 35 return h.log(r) 36 } 37 38 func (h funcHandler) Level() Lvl { 39 return h.lvl 40 } 41 42 // StreamHandler writes log records to an io.Writer 43 // with the given format. StreamHandler can be used 44 // to easily begin writing log records to other 45 // outputs. 46 // 47 // StreamHandler wraps itself with LazyHandler and SyncHandler 48 // to evaluate Lazy objects and perform safe concurrent writes. 49 func StreamHandler(wr io.Writer, fmtr Format) Handler { 50 h := FuncHandler(func(r *Record) error { 51 _, err := wr.Write(fmtr.Format(r)) 52 return err 53 }, LvlTrace) 54 return LazyHandler(SyncHandler(h)) 55 } 56 57 // SyncHandler can be wrapped around a handler to guarantee that 58 // only a single Log operation can proceed at a time. It's necessary 59 // for thread-safe concurrent writes. 60 func SyncHandler(h Handler) Handler { 61 var mu sync.Mutex 62 return FuncHandler(func(r *Record) error { 63 defer mu.Unlock() 64 mu.Lock() 65 return h.Log(r) 66 }, h.Level()) 67 } 68 69 // FileHandler returns a handler which writes log records to the give file 70 // using the given format. If the path 71 // already exists, FileHandler will append to the given file. If it does not, 72 // FileHandler will create the file with mode 0644. 73 func FileHandler(path string, fmtr Format) (Handler, error) { 74 f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) 75 if err != nil { 76 return nil, err 77 } 78 return closingHandler{f, StreamHandler(f, fmtr)}, nil 79 } 80 81 // NetHandler opens a socket to the given address and writes records 82 // over the connection. 83 func NetHandler(network, addr string, fmtr Format) (Handler, error) { 84 conn, err := net.Dial(network, addr) 85 if err != nil { 86 return nil, err 87 } 88 89 return closingHandler{conn, StreamHandler(conn, fmtr)}, nil 90 } 91 92 // XXX: closingHandler is essentially unused at the moment 93 // it's meant for a future time when the Handler interface supports 94 // a possible Close() operation 95 type closingHandler struct { 96 io.WriteCloser 97 Handler 98 } 99 100 func (h *closingHandler) Close() error { 101 return h.WriteCloser.Close() 102 } 103 104 // CallerFileHandler returns a Handler that adds the line number and file of 105 // the calling function to the context with key "caller". 106 func CallerFileHandler(h Handler) Handler { 107 return FuncHandler(func(r *Record) error { 108 r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) 109 return h.Log(r) 110 }, h.Level()) 111 } 112 113 // CallerFuncHandler returns a Handler that adds the calling function name to 114 // the context with key "fn". 115 func CallerFuncHandler(h Handler) Handler { 116 return FuncHandler(func(r *Record) error { 117 r.Ctx = append(r.Ctx, "fn", formatCall("%+n", r.Call)) 118 return h.Log(r) 119 }, h.Level()) 120 } 121 122 // This function is here to please go vet on Go < 1.8. 123 func formatCall(format string, c stack.Call) string { 124 return fmt.Sprintf(format, c) 125 } 126 127 // CallerStackHandler returns a Handler that adds a stack trace to the context 128 // with key "stack". The stack trace is formatted as a space separated list of 129 // call sites inside matching []'s. The most recent call site is listed first. 130 // Each call site is formatted according to format. See the documentation of 131 // package github.com/go-stack/stack for the list of supported formats. 132 func CallerStackHandler(format string, h Handler) Handler { 133 return FuncHandler(func(r *Record) error { 134 s := stack.Trace().TrimBelow(r.Call).TrimRuntime() 135 if len(s) > 0 { 136 r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) 137 } 138 return h.Log(r) 139 }, h.Level()) 140 } 141 142 // FilterHandler returns a Handler that only writes records to the 143 // wrapped Handler if the given function evaluates true. For example, 144 // to only log records where the 'err' key is not nil: 145 // 146 // logger.SetHandler(FilterHandler(func(r *Record) bool { 147 // for i := 0; i < len(r.Ctx); i += 2 { 148 // if r.Ctx[i] == "err" { 149 // return r.Ctx[i+1] != nil 150 // } 151 // } 152 // return false 153 // }, h)) 154 func FilterHandler(fn func(r *Record) bool, h Handler) Handler { 155 return FuncHandler(func(r *Record) error { 156 if fn(r) { 157 return h.Log(r) 158 } 159 return nil 160 }, h.Level()) 161 } 162 163 // MatchFilterHandler returns a Handler that only writes records 164 // to the wrapped Handler if the given key in the logged 165 // context matches the value. For example, to only log records 166 // from your ui package: 167 // 168 // log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler) 169 func MatchFilterHandler(key string, value interface{}, h Handler) Handler { 170 return FilterHandler(func(r *Record) (pass bool) { 171 switch key { 172 case r.KeyNames.Lvl: 173 return r.Lvl == value 174 case r.KeyNames.Time: 175 return r.Time == value 176 case r.KeyNames.Msg: 177 return r.Msg == value 178 } 179 180 for i := 0; i < len(r.Ctx); i += 2 { 181 if r.Ctx[i] == key { 182 return r.Ctx[i+1] == value 183 } 184 } 185 return false 186 }, h) 187 } 188 189 // LvlFilterHandler returns a Handler that only writes 190 // records which are less than the given verbosity 191 // level to the wrapped Handler. For example, to only 192 // log Error/Crit records: 193 // 194 // log.LvlFilterHandler(log.LvlError, log.StdoutHandler) 195 func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { 196 return FilterHandler(func(r *Record) (pass bool) { 197 return r.Lvl <= maxLvl 198 }, h) 199 } 200 201 // MultiHandler dispatches any write to each of its handlers. 202 // This is useful for writing different types of log information 203 // to different locations. For example, to log to a file and 204 // standard error: 205 // 206 // log.MultiHandler( 207 // log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), 208 // log.StderrHandler) 209 func MultiHandler(hs ...Handler) Handler { 210 return FuncHandler(func(r *Record) error { 211 for _, h := range hs { 212 // what to do about failures? 213 h.Log(r) 214 } 215 return nil 216 }, LvlDebug) 217 } 218 219 // FailoverHandler writes all log records to the first handler 220 // specified, but will failover and write to the second handler if 221 // the first handler has failed, and so on for all handlers specified. 222 // For example you might want to log to a network socket, but failover 223 // to writing to a file if the network fails, and then to 224 // standard out if the file write fails: 225 // 226 // log.FailoverHandler( 227 // log.Must.NetHandler("tcp", ":9090", log.JSONFormat()), 228 // log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()), 229 // log.StdoutHandler) 230 // 231 // All writes that do not go to the first handler will add context with keys of 232 // the form "failover_err_{idx}" which explain the error encountered while 233 // trying to write to the handlers before them in the list. 234 func FailoverHandler(hs ...Handler) Handler { 235 return FuncHandler(func(r *Record) error { 236 var err error 237 for i, h := range hs { 238 err = h.Log(r) 239 if err == nil { 240 return nil 241 } 242 r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) 243 } 244 245 return err 246 }, LvlTrace) 247 } 248 249 // ChannelHandler writes all records to the given channel. 250 // It blocks if the channel is full. Useful for async processing 251 // of log messages, it's used by BufferedHandler. 252 func ChannelHandler(recs chan<- *Record, lvl Lvl) Handler { 253 return FuncHandler(func(r *Record) error { 254 recs <- r 255 return nil 256 }, lvl) 257 } 258 259 // BufferedHandler writes all records to a buffered 260 // channel of the given size which flushes into the wrapped 261 // handler whenever it is available for writing. Since these 262 // writes happen asynchronously, all writes to a BufferedHandler 263 // never return an error and any errors from the wrapped handler are ignored. 264 func BufferedHandler(bufSize int, h Handler) Handler { 265 recs := make(chan *Record, bufSize) 266 go func() { 267 for m := range recs { 268 _ = h.Log(m) 269 } 270 }() 271 272 return ChannelHandler(recs, h.Level()) 273 } 274 275 // LazyHandler writes all values to the wrapped handler after evaluating 276 // any lazy functions in the record's context. It is already wrapped 277 // around StreamHandler and SyslogHandler in this library, you'll only need 278 // it if you write your own Handler. 279 func LazyHandler(h Handler) Handler { 280 return FuncHandler(func(r *Record) error { 281 // go through the values (odd indices) and reassign 282 // the values of any lazy fn to the result of its execution 283 hadErr := false 284 for i := 1; i < len(r.Ctx); i += 2 { 285 lz, ok := r.Ctx[i].(Lazy) 286 if ok { 287 v, err := evaluateLazy(lz) 288 if err != nil { 289 hadErr = true 290 r.Ctx[i] = err 291 } else { 292 if cs, ok := v.(stack.CallStack); ok { 293 v = cs.TrimBelow(r.Call).TrimRuntime() 294 } 295 r.Ctx[i] = v 296 } 297 } 298 } 299 300 if hadErr { 301 r.Ctx = append(r.Ctx, errorKey, "bad lazy") 302 } 303 304 return h.Log(r) 305 }, h.Level()) 306 } 307 308 func evaluateLazy(lz Lazy) (interface{}, error) { 309 t := reflect.TypeOf(lz.Fn) 310 311 if t.Kind() != reflect.Func { 312 return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) 313 } 314 315 if t.NumIn() > 0 { 316 return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) 317 } 318 319 if t.NumOut() == 0 { 320 return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) 321 } 322 323 value := reflect.ValueOf(lz.Fn) 324 results := value.Call([]reflect.Value{}) 325 if len(results) == 1 { 326 return results[0].Interface(), nil 327 } 328 values := make([]interface{}, len(results)) 329 for i, v := range results { 330 values[i] = v.Interface() 331 } 332 return values, nil 333 } 334 335 // DiscardHandler reports success for all writes but does nothing. 336 // It is useful for dynamically disabling logging at runtime via 337 // a Logger's SetHandler method. 338 func DiscardHandler() Handler { 339 return FuncHandler(func(r *Record) error { 340 return nil 341 }, LvlDiscard) 342 } 343 344 // Must provides the following Handler creation functions 345 // which instead of returning an error parameter only return a Handler 346 // and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler 347 var Must muster 348 349 func must(h Handler, err error) Handler { 350 if err != nil { 351 panic(err) 352 } 353 return h 354 } 355 356 type muster struct{} 357 358 func (m muster) FileHandler(path string, fmtr Format) Handler { 359 return must(FileHandler(path, fmtr)) 360 } 361 362 func (m muster) NetHandler(network, addr string, fmtr Format) Handler { 363 return must(NetHandler(network, addr, fmtr)) 364 }