github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/log/handler.go (about) 1 package log 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "log/slog" 8 "math/big" 9 "reflect" 10 "sync" 11 "time" 12 13 "github.com/holiman/uint256" 14 ) 15 16 type discardHandler struct{} 17 18 // DiscardHandler returns a no-op handler 19 func DiscardHandler() slog.Handler { 20 return &discardHandler{} 21 } 22 23 func (h *discardHandler) Handle(_ context.Context, r slog.Record) error { 24 return nil 25 } 26 27 func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool { 28 return false 29 } 30 31 func (h *discardHandler) WithGroup(name string) slog.Handler { 32 panic("not implemented") 33 } 34 35 func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 36 return &discardHandler{} 37 } 38 39 type TerminalHandler struct { 40 mu sync.Mutex 41 wr io.Writer 42 lvl slog.Level 43 useColor bool 44 attrs []slog.Attr 45 // fieldPadding is a map with maximum field value lengths seen until now 46 // to allow padding log contexts in a bit smarter way. 47 fieldPadding map[string]int 48 49 buf []byte 50 } 51 52 // NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on 53 // a terminal with color-coded level output and terser human friendly timestamp. 54 // This format should only be used for interactive programs or while developing. 55 // 56 // [LEVEL] [TIME] MESSAGE key=value key=value ... 57 // 58 // Example: 59 // 60 // [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002 61 func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler { 62 return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor) 63 } 64 65 // NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs 66 // records which are less than or equal to the specified verbosity level. 67 func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler { 68 return &TerminalHandler{ 69 wr: wr, 70 lvl: lvl, 71 useColor: useColor, 72 fieldPadding: make(map[string]int), 73 } 74 } 75 76 func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error { 77 h.mu.Lock() 78 defer h.mu.Unlock() 79 buf := h.format(h.buf, r, h.useColor) 80 h.wr.Write(buf) 81 h.buf = buf[:0] 82 return nil 83 } 84 85 func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool { 86 return level >= h.lvl 87 } 88 89 func (h *TerminalHandler) WithGroup(name string) slog.Handler { 90 panic("not implemented") 91 } 92 93 func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler { 94 return &TerminalHandler{ 95 wr: h.wr, 96 lvl: h.lvl, 97 useColor: h.useColor, 98 attrs: append(h.attrs, attrs...), 99 fieldPadding: make(map[string]int), 100 } 101 } 102 103 // ResetFieldPadding zeroes the field-padding for all attribute pairs. 104 func (t *TerminalHandler) ResetFieldPadding() { 105 t.mu.Lock() 106 t.fieldPadding = make(map[string]int) 107 t.mu.Unlock() 108 } 109 110 type leveler struct{ minLevel slog.Level } 111 112 func (l *leveler) Level() slog.Level { 113 return l.minLevel 114 } 115 116 // JSONHandler returns a handler which prints records in JSON format. 117 func JSONHandler(wr io.Writer) slog.Handler { 118 return JSONHandlerWithLevel(wr, levelMaxVerbosity) 119 } 120 121 // JSONHandlerWithLevel returns a handler which prints records in JSON format that are less than or equal to 122 // the specified verbosity level. 123 func JSONHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler { 124 return slog.NewJSONHandler(wr, &slog.HandlerOptions{ 125 ReplaceAttr: builtinReplaceJSON, 126 Level: &leveler{level}, 127 }) 128 } 129 130 // LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable 131 // format for key/value pairs. 132 // 133 // For more details see: http://godoc.org/github.com/kr/logfmt 134 func LogfmtHandler(wr io.Writer) slog.Handler { 135 return slog.NewTextHandler(wr, &slog.HandlerOptions{ 136 ReplaceAttr: builtinReplaceLogfmt, 137 }) 138 } 139 140 // LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs 141 // records which are less than or equal to the specified verbosity level. 142 func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler { 143 return slog.NewTextHandler(wr, &slog.HandlerOptions{ 144 ReplaceAttr: builtinReplaceLogfmt, 145 Level: &leveler{level}, 146 }) 147 } 148 149 func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr { 150 return builtinReplace(nil, attr, true) 151 } 152 153 func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr { 154 return builtinReplace(nil, attr, false) 155 } 156 157 func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr { 158 switch attr.Key { 159 case slog.TimeKey: 160 if attr.Value.Kind() == slog.KindTime { 161 if logfmt { 162 return slog.String("t", attr.Value.Time().Format(timeFormat)) 163 } else { 164 return slog.Attr{Key: "t", Value: attr.Value} 165 } 166 } 167 case slog.LevelKey: 168 if l, ok := attr.Value.Any().(slog.Level); ok { 169 attr = slog.Any("lvl", LevelString(l)) 170 return attr 171 } 172 } 173 174 switch v := attr.Value.Any().(type) { 175 case time.Time: 176 if logfmt { 177 attr = slog.String(attr.Key, v.Format(timeFormat)) 178 } 179 case *big.Int: 180 if v == nil { 181 attr.Value = slog.StringValue("<nil>") 182 } else { 183 attr.Value = slog.StringValue(v.String()) 184 } 185 case *uint256.Int: 186 if v == nil { 187 attr.Value = slog.StringValue("<nil>") 188 } else { 189 attr.Value = slog.StringValue(v.Dec()) 190 } 191 case fmt.Stringer: 192 if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) { 193 attr.Value = slog.StringValue("<nil>") 194 } else { 195 attr.Value = slog.StringValue(v.String()) 196 } 197 } 198 return attr 199 }