-
Notifications
You must be signed in to change notification settings - Fork 21.1k
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.Logger
interface with a newslog.Logger
wrapper,
to pass anslog.Logger
into any existing Geth code. - Above wrapper can be compatible with both
log.Logger
andslog.Logger
interfaces,
so Geth can pass its logger into dependencies that do leveled logging. slog.Handler
interface 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.Lvl
type, 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
testlog
as 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