Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 70 additions & 33 deletions logutils/geth_adapter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package logutils

import (
"fmt"
"context"
"log/slog"
"strings"

"go.uber.org/zap"
Expand All @@ -10,42 +11,78 @@ import (
"github.com/ethereum/go-ethereum/log"
)

// gethAdapter returns a log.Handler interface that forwards logs to a zap.Logger.
// Logs are forwarded raw as if geth were printing them.
func gethAdapter(logger *zap.Logger) log.Handler {
return log.FuncHandler(func(r *log.Record) error {
level, err := LvlFromString(r.Lvl.String())
if err != nil {
return err
}
// Skip trace logs to not clutter the output
if level == traceLevel {
return nil
}
serializedLog := string(log.TerminalFormat(false).Format(r))
logger.Check(level, fmt.Sprintf("'%s'", strings.TrimSuffix(serializedLog, "\n"))).Write()
return nil
})
type gethAdapter struct {
log.Logger
target *zap.Logger
}

func newGethAdapter(target *zap.Logger) *gethAdapter {
return &gethAdapter{
Logger: log.NewLogger(log.DiscardHandler()),
target: target,
}
}

func (g *gethAdapter) serializeLog(level slog.Level, msg string, ctx ...interface{}) string {
buf := strings.Builder{}
logger := log.NewLogger(log.LogfmtHandlerWithLevel(&buf, level))
logger.Log(level, msg, ctx...)
return buf.String()
}

func (g *gethAdapter) Log(level slog.Level, msg string, ctx ...interface{}) {
g.Write(level, msg, ctx...)
}

func (g *gethAdapter) Trace(msg string, ctx ...interface{}) {
g.Write(log.LevelTrace, msg, ctx...)
}

func (g *gethAdapter) Debug(msg string, ctx ...interface{}) {
g.Write(log.LevelDebug, msg, ctx...)
}

func (g *gethAdapter) Info(msg string, ctx ...interface{}) {
g.Write(log.LevelInfo, msg, ctx...)
}

func (g *gethAdapter) Warn(msg string, ctx ...interface{}) {
g.Write(log.LevelWarn, msg, ctx...)
}

func (g *gethAdapter) Error(msg string, ctx ...interface{}) {
g.Write(log.LevelError, msg, ctx...)
}

func (g *gethAdapter) Crit(msg string, ctx ...interface{}) {
g.Write(log.LevelCrit, msg, ctx...)
}

func (g *gethAdapter) Write(level slog.Level, msg string, attrs ...any) {
g.target.Check(zapLevelFromGeth(level), g.serializeLog(level, msg, attrs...)).Write()
}

func (g *gethAdapter) Enabled(ctx context.Context, level slog.Level) bool {
return g.target.Core().Enabled(zapLevelFromGeth(level)) && g.Logger.Enabled(ctx, level)
}

const traceLevel = zapcore.DebugLevel - 1

// LvlFromString returns the appropriate zapcore.Level from a string.
func LvlFromString(lvlString string) (zapcore.Level, error) {
switch strings.ToLower(lvlString) {
case "trace", "trce":
return traceLevel, nil // zap does not have a trace level, use custom
case "debug", "dbug":
return zapcore.DebugLevel, nil
case "info":
return zapcore.InfoLevel, nil
case "warn":
return zapcore.WarnLevel, nil
case "error", "eror":
return zapcore.ErrorLevel, nil
case "crit":
return zapcore.DPanicLevel, nil // zap does not have a crit level, using DPanicLevel as closest
func zapLevelFromGeth(lvl slog.Level) zapcore.Level {
switch lvl {
case log.LevelTrace:
return traceLevel // zap does not have a trace level, use custom
case log.LevelDebug:
return zapcore.DebugLevel
case log.LevelInfo:
return zapcore.InfoLevel
case log.LevelWarn:
return zapcore.WarnLevel
case log.LevelError:
return zapcore.ErrorLevel
case log.LevelCrit:
return zapcore.DPanicLevel // zap does not have a crit level, using DPanicLevel as closest
default:
return zapcore.InvalidLevel, fmt.Errorf("unknown level: %v", lvlString)
return zapcore.InvalidLevel
}
}
6 changes: 3 additions & 3 deletions logutils/geth_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ func TestGethAdapter(t *testing.T) {
)
logger := zap.New(core)

log.Root().SetHandler(gethAdapter(logger))
log.SetDefault(newGethAdapter(logger))

log.Debug("should not be printed, as it's below the log level")
require.Empty(t, buffer.String())

buffer.Reset()
log.Info("should be printed")
require.Regexp(t, `INFO\s+'INFO\s*\[.*\]\s*should be printed '`, buffer.String())
require.Regexp(t, `INFO\s+t=.* lvl=info msg="should be printed"`, buffer.String())

buffer.Reset()
level.SetLevel(zap.DebugLevel)
log.Debug("should be printed with context", "value1", 12345, "value2", "string")
require.Regexp(t, `DEBUG\s+'DEBUG\s*\[.*\]\s*should be printed with context\s+value1=12345\s+value2=string'`, buffer.String())
require.Regexp(t, `DEBUG\s+t=.* lvl=debug msg="should be printed with context" value1=12345 value2=string`, buffer.String())

buffer.Reset()
log.Trace("should be skipped")
Expand Down
3 changes: 1 addition & 2 deletions logutils/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ func ZapLogger() *zap.Logger {
_zapLogger = defaultLogger()

// forward geth logs to zap logger
_gethLogger := _zapLogger.Named("geth")
log.Root().SetHandler(gethAdapter(_gethLogger))
log.SetDefault(newGethAdapter(_zapLogger.Named("geth")))
})
return _zapLogger
}
Expand Down
28 changes: 28 additions & 0 deletions logutils/lvl_from_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package logutils

import (
"fmt"
"strings"

"go.uber.org/zap/zapcore"
)

// LvlFromString returns the appropriate zapcore.Level from a string.
func LvlFromString(lvlString string) (zapcore.Level, error) {
switch strings.ToLower(lvlString) {
case "trace", "trce":
return traceLevel, nil // zap does not have a trace level, use custom
case "debug", "dbug":
return zapcore.DebugLevel, nil
case "info":
return zapcore.InfoLevel, nil
case "warn":
return zapcore.WarnLevel, nil
case "error", "eror":
return zapcore.ErrorLevel, nil
case "crit":
return zapcore.DPanicLevel, nil // zap does not have a crit level, using DPanicLevel as closest
default:
return zapcore.InvalidLevel, fmt.Errorf("unknown level: %v", lvlString)
}
}