-
Notifications
You must be signed in to change notification settings - Fork 21.5k
Closed
Description
Go introduces log/slog in v1.21, a structured logger very similar to the go-ethereum log package.
Rather than ignoring the standard-lib, I think it would be nice to create compatibility, and interop more easily with any other Go projects that use the same slog interfaces.
Slog introduction
See https://go.dev/blog/slog (22 Aug 2023) for context from the Go maintainers.
Quick summary of slog functionality to compare it to Geth:
package ethereum
import (
"context"
"errors"
"io"
"log/slog"
"os"
"testing"
"testing/slogtest"
)
func TestSLog(t *testing.T) {
// slog.Level - int level, *with room between levels*.
// Can add "notice" (google, nimbus) or "crit" and "trace" (geth today) levels
// slog.Attr - simple Key-Value struct
// slog.Handler interface:
// Enabled(context.Context, Level) bool - check if lvl is enabled
// Handle(context.Context, Record) error - process record
// WithAttrs(attrs []Attr) Handler - extend with context attributes
// WithGroup(name string) Handler - extend and group next attributes
// there are a two default handlers:
_ = slog.NewTextHandler(io.Discard, &slog.HandlerOptions{
AddSource: false, // with source-code position
Level: nil, // log-level filterer (that can change its level dynamically)
ReplaceAttr: nil, // func to replace context attributes (e.g. hide secrets from logs)
})
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})
// the handler processes records;
// slog.Record - like log.Record
//
logger := slog.New(h)
logger.Handler() // can retrieve the handler
// simple logging:
logger.Warn("fire", "err", errors.New("oh no"))
logger.Warn("hello", "a", 123)
logger.Info("world", "b", "foo")
logger.Debug("test", "a", "A", "B", "b")
// with context:
// (to provide context values for handler to add to record, not necessarily to time out, no error returned)
ctx := context.Background()
logger.WarnContext(ctx, "fire", "err", errors.New("oh no"))
logger.DebugContext(ctx, "hello", "a", 123)
logger.InfoContext(ctx, "world", "b", "foo")
logger.WarnContext(ctx, "test", "a", "A", "B", "b")
// can log custom levels like: (there's room between each standard log level for exactly this)
logger.Log(ctx, slog.LevelDebug+1, "custom trace", "attribute-context", "example")
// can check if a log level is enabled
logger.Enabled(context.Background(), slog.LevelDebug)
// Explicit attribute typing and K/V pairing, for efficiency
logger.LogAttrs(ctx, slog.LevelDebug+1, "hello", slog.Attr{Key: "foobar", Value: slog.Int64Value(1234)})
logger.With("outside", 123).WithGroup("inside").With("inner-attribute", "42").Info("group test")
// global logger logs to Default(), similar to geth log.Root()
// But it would be better not to use this as much as possible, for testing readability.
slog.Log(context.Background(), slog.LevelDebug, "hello world")
{
// slogtest is a handler-implementation tester, returning the joined error,
// not a test-logger like you may expect
h := slog.NewJSONHandler(io.Discard, nil)
err := slogtest.TestHandler(h, func() []map[string]any {
return nil // return expected structured log (would need to parse from the now discarded data)
})
if err != nil { // expected, test is not complete
t.Logf("slogtest error: %v", err)
}
}
}slog Support
To support slog I would suggest to:
- Deprecate geth
SetHandler(h)onlog.Logger;
completely swapping the handler dynamically is not necessary, and not supported byslog. - Implement the Geth
log.Loggerinterface with a newslog.Loggerwrapper,
to pass anslog.Loggerinto any existing Geth code. - Above wrapper can be compatible with both
log.Loggerandslog.Loggerinterfaces,
so Geth can pass its logger into dependencies that do leveled logging. slog.Handlerinterface implemented by wrapper aroundlog.Handler,
to direct any slog records to existing geth log handlers. (since Geth has many log handlers that we probably want to keep supporting)- Add a
SlogLevel()method to Gethlog.Lvltype, to translate it easily. - Define
slog.Level"crit" and "trace" constant ints.
Additional logging improvements
Happy to split these up in different issues, but if we're making some changes to logging then I think these will be relatively low lift to support as well:
Avoid global logger
- When writing integration tests, adding context attributes to a logger is very valuable.
E.g. distinguishing node A and node B in a test. This does not work with global logging. - When running many tests in parallel,
the test-logger ensures the different log-data is not scrambled as much across tests,
like what would happen with std-out. - Changing the global logger to a test-logger in a test is a no-no,
as a test fails if a test-logger is written to after test-completion.
Geth testlog improvements
- Expose
testlogas public-facing package: everyone who builds on top of Geth likes to test with this logger too!
We currently maintain a copy (with a copy of the license) in its own exported package here:
https://github.com/ethereum-optimism/optimism/tree/develop/op-service/testlog - Minor testlog improvements: we added dynamic padding to the start, to handle the varying source-file length, to align more of the log messages for a more readable output.
- Make testlog implement the
slog.Handler
Metadata
Metadata
Assignees
Labels
No labels