forked from cerc-io/plugeth
28e7371701
This PR replaces Geth's logger package (a fork of [log15](https://github.com/inconshreveable/log15)) with an implementation using slog, a logging library included as part of the Go standard library as of Go1.21. Main changes are as follows: * removes any log handlers that were unused in the Geth codebase. * Json, logfmt, and terminal formatters are now slog handlers. * Verbosity level constants are changed to match slog constant values. Internal translation is done to make this opaque to the user and backwards compatible with existing `--verbosity` and `--vmodule` options. * `--log.backtraceat` and `--log.debug` are removed. The external-facing API is largely the same as the existing Geth logger. Logger method signatures remain unchanged. A small semantic difference is that a `Handler` can only be set once per `Logger` and not changed dynamically. This just means that a new logger must be instantiated every time the handler of the root logger is changed. ---- For users of the `go-ethereum/log` module. If you were using this module for your own project, you will need to change the initialization. If you previously did ```golang log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) ``` You now instead need to do ```golang log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) ``` See more about reasoning here: https://github.com/ethereum/go-ethereum/issues/28558#issuecomment-1820606613
224 lines
5.8 KiB
Go
224 lines
5.8 KiB
Go
package log
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"reflect"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/holiman/uint256"
|
|
"golang.org/x/exp/slog"
|
|
)
|
|
|
|
// Lazy allows you to defer calculation of a logged value that is expensive
|
|
// to compute until it is certain that it must be evaluated with the given filters.
|
|
//
|
|
// You may wrap any function which takes no arguments to Lazy. It may return any
|
|
// number of values of any type.
|
|
type Lazy struct {
|
|
Fn interface{}
|
|
}
|
|
|
|
func evaluateLazy(lz Lazy) (interface{}, error) {
|
|
t := reflect.TypeOf(lz.Fn)
|
|
|
|
if t.Kind() != reflect.Func {
|
|
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn)
|
|
}
|
|
|
|
if t.NumIn() > 0 {
|
|
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn)
|
|
}
|
|
|
|
if t.NumOut() == 0 {
|
|
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn)
|
|
}
|
|
|
|
value := reflect.ValueOf(lz.Fn)
|
|
results := value.Call([]reflect.Value{})
|
|
if len(results) == 1 {
|
|
return results[0].Interface(), nil
|
|
}
|
|
values := make([]interface{}, len(results))
|
|
for i, v := range results {
|
|
values[i] = v.Interface()
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
type discardHandler struct{}
|
|
|
|
// DiscardHandler returns a no-op handler
|
|
func DiscardHandler() slog.Handler {
|
|
return &discardHandler{}
|
|
}
|
|
|
|
func (h *discardHandler) Handle(_ context.Context, r slog.Record) error {
|
|
return nil
|
|
}
|
|
|
|
func (h *discardHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
return false
|
|
}
|
|
|
|
func (h *discardHandler) WithGroup(name string) slog.Handler {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (h *discardHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
return &discardHandler{}
|
|
}
|
|
|
|
type TerminalHandler struct {
|
|
mu sync.Mutex
|
|
wr io.Writer
|
|
lvl slog.Level
|
|
useColor bool
|
|
attrs []slog.Attr
|
|
// fieldPadding is a map with maximum field value lengths seen until now
|
|
// to allow padding log contexts in a bit smarter way.
|
|
fieldPadding map[string]int
|
|
}
|
|
|
|
// NewTerminalHandler returns a handler which formats log records at all levels optimized for human readability on
|
|
// a terminal with color-coded level output and terser human friendly timestamp.
|
|
// This format should only be used for interactive programs or while developing.
|
|
//
|
|
// [LEVEL] [TIME] MESSAGE key=value key=value ...
|
|
//
|
|
// Example:
|
|
//
|
|
// [DBUG] [May 16 20:58:45] remove route ns=haproxy addr=127.0.0.1:50002
|
|
func NewTerminalHandler(wr io.Writer, useColor bool) *TerminalHandler {
|
|
return NewTerminalHandlerWithLevel(wr, levelMaxVerbosity, useColor)
|
|
}
|
|
|
|
// NewTerminalHandlerWithLevel returns the same handler as NewTerminalHandler but only outputs
|
|
// records which are less than or equal to the specified verbosity level.
|
|
func NewTerminalHandlerWithLevel(wr io.Writer, lvl slog.Level, useColor bool) *TerminalHandler {
|
|
return &TerminalHandler{
|
|
wr: wr,
|
|
lvl: lvl,
|
|
useColor: useColor,
|
|
fieldPadding: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
func (h *TerminalHandler) Handle(_ context.Context, r slog.Record) error {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
h.wr.Write(h.TerminalFormat(r, h.useColor))
|
|
return nil
|
|
}
|
|
|
|
func (h *TerminalHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|
return level >= h.lvl
|
|
}
|
|
|
|
func (h *TerminalHandler) WithGroup(name string) slog.Handler {
|
|
panic("not implemented")
|
|
}
|
|
|
|
func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
return &TerminalHandler{
|
|
wr: h.wr,
|
|
lvl: h.lvl,
|
|
useColor: h.useColor,
|
|
attrs: append(h.attrs, attrs...),
|
|
fieldPadding: make(map[string]int),
|
|
}
|
|
}
|
|
|
|
// ResetFieldPadding zeroes the field-padding for all attribute pairs.
|
|
func (t *TerminalHandler) ResetFieldPadding() {
|
|
t.mu.Lock()
|
|
t.fieldPadding = make(map[string]int)
|
|
t.mu.Unlock()
|
|
}
|
|
|
|
type leveler struct{ minLevel slog.Level }
|
|
|
|
func (l *leveler) Level() slog.Level {
|
|
return l.minLevel
|
|
}
|
|
|
|
func JSONHandler(wr io.Writer) slog.Handler {
|
|
return slog.NewJSONHandler(wr, &slog.HandlerOptions{
|
|
ReplaceAttr: builtinReplaceJSON,
|
|
})
|
|
}
|
|
|
|
// LogfmtHandler returns a handler which prints records in logfmt format, an easy machine-parseable but human-readable
|
|
// format for key/value pairs.
|
|
//
|
|
// For more details see: http://godoc.org/github.com/kr/logfmt
|
|
func LogfmtHandler(wr io.Writer) slog.Handler {
|
|
return slog.NewTextHandler(wr, &slog.HandlerOptions{
|
|
ReplaceAttr: builtinReplaceLogfmt,
|
|
})
|
|
}
|
|
|
|
// LogfmtHandlerWithLevel returns the same handler as LogfmtHandler but it only outputs
|
|
// records which are less than or equal to the specified verbosity level.
|
|
func LogfmtHandlerWithLevel(wr io.Writer, level slog.Level) slog.Handler {
|
|
return slog.NewTextHandler(wr, &slog.HandlerOptions{
|
|
ReplaceAttr: builtinReplaceLogfmt,
|
|
Level: &leveler{level},
|
|
})
|
|
}
|
|
|
|
func builtinReplaceLogfmt(_ []string, attr slog.Attr) slog.Attr {
|
|
return builtinReplace(nil, attr, true)
|
|
}
|
|
|
|
func builtinReplaceJSON(_ []string, attr slog.Attr) slog.Attr {
|
|
return builtinReplace(nil, attr, false)
|
|
}
|
|
|
|
func builtinReplace(_ []string, attr slog.Attr, logfmt bool) slog.Attr {
|
|
switch attr.Key {
|
|
case slog.TimeKey:
|
|
if attr.Value.Kind() == slog.KindTime {
|
|
if logfmt {
|
|
return slog.String("t", attr.Value.Time().Format(timeFormat))
|
|
} else {
|
|
return slog.Attr{Key: "t", Value: attr.Value}
|
|
}
|
|
}
|
|
case slog.LevelKey:
|
|
if l, ok := attr.Value.Any().(slog.Level); ok {
|
|
attr = slog.Any("lvl", LevelString(l))
|
|
return attr
|
|
}
|
|
}
|
|
|
|
switch v := attr.Value.Any().(type) {
|
|
case time.Time:
|
|
if logfmt {
|
|
attr = slog.String(attr.Key, v.Format(timeFormat))
|
|
}
|
|
case *big.Int:
|
|
if v == nil {
|
|
attr.Value = slog.StringValue("<nil>")
|
|
} else {
|
|
attr.Value = slog.StringValue(v.String())
|
|
}
|
|
case *uint256.Int:
|
|
if v == nil {
|
|
attr.Value = slog.StringValue("<nil>")
|
|
} else {
|
|
attr.Value = slog.StringValue(v.Dec())
|
|
}
|
|
case fmt.Stringer:
|
|
if v == nil || (reflect.ValueOf(v).Kind() == reflect.Pointer && reflect.ValueOf(v).IsNil()) {
|
|
attr.Value = slog.StringValue("<nil>")
|
|
} else {
|
|
attr.Value = slog.StringValue(v.String())
|
|
}
|
|
}
|
|
return attr
|
|
}
|