From 8404b9800ad9f9c234f5aff447670ed203796737 Mon Sep 17 00:00:00 2001 From: Martin Hutchinson Date: Wed, 27 May 2026 10:09:42 +0000 Subject: [PATCH] Improvements for bug reports and logging Output the options passed into the appender using slog. Conformance now has ability to output in JSON instead of only text. ``` go run ./cmd/conformance/posix \ --storage_dir=${LOG_DIR} \ --listen=:2025 --log_format=json {"time":"2026-05-27T10:05:43.143037492Z","level":"INFO","msg":"Creating new appender","options":{"batchMaxAge":1000000000,"batchMaxSize":256,"pushbackMaxOutstanding":4096,"maxEntrySize":65535,"checkpointInterval":1000000000,"checkpointRepublishInterval":60000000000,"garbageCollectionInterval":60000000000,"shutdownTimeout":60000000000,"primarySigner":"example.com/log/testdata"}} ``` ``` go run ./cmd/conformance/posix \ --storage_dir=${LOG_DIR} \ --listen=:2025 time=2026-05-27T10:07:04.102Z level=INFO msg="Creating new appender" options.batchMaxAge=1s options.batchMaxSize=256 options.pushbackMaxOutstanding=4096 options.maxEntrySize=65535 options.checkpointInterval=1s options.checkpointRepublishInterval=1m0s options.garbageCollectionInterval=1m0s options.shutdownTimeout=1m0s options.primarySigner=example.com/log/testdata ``` --- append_lifecycle.go | 53 +++++++++++++++++++++++++++++++++ cmd/conformance/aws/main.go | 10 ++++++- cmd/conformance/gcp/main.go | 9 +++++- cmd/conformance/posix/README.md | 2 +- cmd/conformance/posix/main.go | 10 ++++++- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/append_lifecycle.go b/append_lifecycle.go index e8ce5890a..bd3a6da7d 100644 --- a/append_lifecycle.go +++ b/append_lifecycle.go @@ -282,6 +282,7 @@ func NewAppender(ctx context.Context, d Driver, opts *AppendOptions) (*Appender, if opts == nil { return nil, nil, nil, errors.New("opts cannot be nil") } + slog.InfoContext(ctx, "Creating new appender", slog.Any("options", opts)) if err := opts.valid(); err != nil { return nil, nil, nil, err } @@ -973,3 +974,55 @@ func (o *AppendOptions) WithShutdownTimeout(timeout time.Duration) *AppendOption o.shutdownTimeout = timeout return o } + +// LogValue implements slog.LogValuer to allow safe serialization of AppendOptions. +func (o *AppendOptions) LogValue() slog.Value { + if o == nil { + return slog.Value{} + } + + attrs := []slog.Attr{ + slog.Duration("batchMaxAge", o.batchMaxAge), + slog.Uint64("batchMaxSize", uint64(o.batchMaxSize)), + slog.Uint64("pushbackMaxOutstanding", uint64(o.pushbackMaxOutstanding)), + slog.Uint64("maxEntrySize", uint64(o.maxEntrySize)), + slog.Duration("checkpointInterval", o.checkpointInterval), + slog.Duration("checkpointRepublishInterval", o.checkpointRepublishInterval), + slog.Duration("garbageCollectionInterval", o.garbageCollectionInterval), + slog.Duration("shutdownTimeout", o.shutdownTimeout), + } + + if o.primarySigner != nil { + attrs = append(attrs, slog.String("primarySigner", o.primarySigner.Name())) + } + + if len(o.additionalSigners) > 0 { + names := make([]string, len(o.additionalSigners)) + for i, s := range o.additionalSigners { + names[i] = s.Name() + } + attrs = append(attrs, slog.Any("additionalSigners", names)) + } + + if len(o.witnesses.Components) > 0 { + endpoints := o.witnesses.Endpoints() + urls := make([]string, 0, len(endpoints)) + for u := range endpoints { + urls = append(urls, u) + } + attrs = append(attrs, slog.Group("witnesses", + slog.Int("threshold", o.witnesses.N), + slog.Any("endpoints", urls), + )) + } + + if len(o.followers) > 0 { + names := make([]string, len(o.followers)) + for i, f := range o.followers { + names[i] = f.Name() + } + attrs = append(attrs, slog.Any("followers", names)) + } + + return slog.GroupValue(attrs...) +} diff --git a/cmd/conformance/aws/main.go b/cmd/conformance/aws/main.go index 52a7393da..e4901b64b 100644 --- a/cmd/conformance/aws/main.go +++ b/cmd/conformance/aws/main.go @@ -55,6 +55,7 @@ var ( publishInterval = flag.Duration("publish_interval", 3*time.Second, "How frequently to publish updated checkpoints") traceFraction = flag.Float64("trace_fraction", 0, "Fraction of open-telemetry span traces to sample") slogLevel = flag.Int("slog_level", 0, "The cut-off threshold for structured logging. Default is 0 (INFO). See https://pkg.go.dev/log/slog#Level for other levels.") + logFormat = flag.String("log_format", "text", "The format of the logs: text or json.") additionalSigners = []string{} antispamEnable = flag.Bool("antispam", false, "EXPERIMENTAL: Set to true to enable persistent antispam storage") @@ -71,7 +72,14 @@ func init() { func main() { flag.Parse() ctx := context.Background() - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}))) + var handler slog.Handler + switch *logFormat { + case "json": + handler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + default: + handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + } + slog.SetDefault(slog.New(handler)) shutdownOTel := initOTel(ctx, *traceFraction) defer shutdownOTel(ctx) diff --git a/cmd/conformance/gcp/main.go b/cmd/conformance/gcp/main.go index 82ccd86ea..a33824640 100644 --- a/cmd/conformance/gcp/main.go +++ b/cmd/conformance/gcp/main.go @@ -44,6 +44,7 @@ var ( projectID = flag.String("project", "", "GCP Project ID for Cloud Logging traces (optional)") additionalSigners = []string{} slogLevel = flag.Int("slog_level", 0, "The cut-off threshold for structured logging. Default is 0 (INFO). See https://pkg.go.dev/log/slog#Level for other levels.") + logFormat = flag.String("log_format", "json", "The format of the logs: text or json.") ) func init() { @@ -57,7 +58,13 @@ func main() { flag.Parse() ctx := context.Background() - handler := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + var handler slog.Handler + switch *logFormat { + case "text": + handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + default: + handler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + } slog.SetDefault(slog.New(logger.NewGCPContextHandler(handler, *projectID))) shutdownOTel := initOTel(ctx, *traceFraction) diff --git a/cmd/conformance/posix/README.md b/cmd/conformance/posix/README.md index 0faf61821..6dbf465e8 100644 --- a/cmd/conformance/posix/README.md +++ b/cmd/conformance/posix/README.md @@ -19,7 +19,7 @@ Then, start the personality: ```shell go run ./cmd/conformance/posix \ --storage_dir=${LOG_DIR} \ - --listen=:2025 \ + --listen=:2025 ``` ## Add entries to the log diff --git a/cmd/conformance/posix/main.go b/cmd/conformance/posix/main.go index bf5f9d14b..99f3fe507 100644 --- a/cmd/conformance/posix/main.go +++ b/cmd/conformance/posix/main.go @@ -44,6 +44,7 @@ var ( persistentAntispam = flag.Bool("antispam", false, "EXPERIMENTAL: Set to true to enable Badger-based persistent antispam storage") additionalPrivateKeyFiles = []string{} slogLevel = flag.Int("slog_level", 0, "The cut-off threshold for structured logging. Default is 0 (INFO). See https://pkg.go.dev/log/slog#Level for other levels.") + logFormat = flag.String("log_format", "text", "The format of the logs: text or json.") ) func init() { @@ -63,7 +64,14 @@ func addCacheHeaders(value string, fs http.Handler) http.HandlerFunc { func main() { flag.Parse() ctx := context.Background() - slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}))) + var handler slog.Handler + switch *logFormat { + case "json": + handler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + default: + handler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.Level(*slogLevel)}) + } + slog.SetDefault(slog.New(handler)) // Gather the info needed for reading/writing checkpoints s, a := getSignersOrDie()