1
0
Fork 0
mirror of https://github.com/chainguard-dev/clog synced 2026-07-06 22:12:46 +00:00
clog/slogtest/slogtest.go
Jason Hall 5a063eb822
slogtest: demonstrate use of t.Context() (#41)
Go 1.24 added [`t.Context()`](https://pkg.go.dev/testing#T.Context),
which is presumably more idiomatic to use with clog than our own
slogtest package.

This PR adds a comment mentioning `t.Context` and demonstrates how to
get `t.Context` to have the same behavior as `slogtest.Context(t)`.

Without the `slog.SetDefault` the behavior is different:

```
go test -trimpath ./slogtest -run=SlogTest -v             
=== RUN   TestSlogTest
    slogtest.go:36: level=INFO source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:13 msg="hello world" foo=bar
    slogtest.go:36: level=INFO source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:14 msg="me again" bar=baz
    slogtest.go:36: level=INFO source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:15 msg="okay last one" baz=true
    slogtest.go:36: level=DEBUG source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:17 msg="hello debug"
    slogtest.go:36: level=INFO source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:18 msg="hello info"
    slogtest.go:36: level=WARN source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:19 msg="hello warn"
    slogtest.go:36: level=ERROR source=github.com/chainguard-dev/clog/slogtest/slogtest_test.go:20 msg="hello error"
    slogtest.go:36: level=INFO source=github.com/chainguard-dev/clog/slogtest/another_test.go:9 msg="hello from fn" foo=bar
--- PASS: TestSlogTest (0.00s)
=== RUN   TestSlogTestTContext
2025/07/08 12:29:28 INFO hello world foo=bar
2025/07/08 12:29:28 INFO me again bar=baz
2025/07/08 12:29:28 INFO okay last one baz=true
2025/07/08 12:29:28 INFO hello info
2025/07/08 12:29:28 WARN hello warn
2025/07/08 12:29:28 ERROR hello error
2025/07/08 12:29:28 INFO hello from fn foo=bar
--- PASS: TestSlogTestTContext (0.00s)
PASS
ok      github.com/chainguard-dev/clog/slogtest 0.211s
```

(debug logs are dropped, no file paths or line numbers, timestamps are
added)

I don't know whether we should officially deprecate `slogtest` in favor
of `clog.FromContext(t.Context())`, but at least this documents an
off-ramp.

Signed-off-by: Jason Hall <jason@chainguard.dev>
2025-08-30 21:11:13 -04:00

84 lines
2.4 KiB
Go

// Package slogtest provides utilities for emitting test logs using clog.
//
// func TestExample(t *testing.T) {
// ctx := slogtest.Context(t)
// clog.FromContext(ctx).With("foo", "bar").Info("hello world")
// }
//
// This produces the following test output:
//
// === RUN TestExample
// slogtest.go:24: level=INFO source=/path/to/example_test.go:13 msg="hello world" foo=bar
//
// This package is intended to be used in tests only.
//
// In Go 1.24, *testing.T etc added `t.Context()` methods, which return a
// context.Context to be used in tests. You can use `clog.FromContext(t.Context())`
// to get a logger in tests instead, and configure the default logger to get the
// same logging behavior as `slogtest.Context(t)`.
package slogtest
import (
"context"
"io"
"log/slog"
"strings"
"testing"
"github.com/chainguard-dev/clog"
)
var _ io.Writer = &logAdapter{}
type logAdapter struct{ l Logger }
func (l *logAdapter) Write(b []byte) (int, error) {
l.l.Log(strings.TrimSuffix(string(b), "\n"))
return len(b), nil
}
var _ Logger = (*testing.T)(nil)
var _ Logger = (*testing.B)(nil)
var _ Logger = (*testing.F)(nil)
type Logger interface {
Log(args ...any)
Context() context.Context
}
// TestLogger gets a logger to use in unit and end to end tests.
// This logger is configured to log at debug level.
func TestLogger(t Logger) *clog.Logger {
return clog.New(slog.NewTextHandler(&logAdapter{l: t}, &slog.HandlerOptions{
Level: slog.LevelDebug,
AddSource: true,
ReplaceAttr: RemoveTime,
}))
}
// TestLoggerWithOptions gets a logger to use in unit and end to end tests.
func TestLoggerWithOptions(t Logger, opts *slog.HandlerOptions) *clog.Logger {
return clog.New(slog.NewTextHandler(&logAdapter{l: t}, opts))
}
// Context returns a context with a logger to be used in tests.
func Context(t Logger) context.Context {
return clog.WithLogger(t.Context(), TestLogger(t))
}
// TestContextWithLogger returns a context with a logger to be used in tests
//
// Deprecated: Use Context instead.
func TestContextWithLogger(t Logger) context.Context { return Context(t) }
// RemoveTime removes the top-level time attribute.
// It is intended to be used as a ReplaceAttr function,
// to make example output deterministic.
//
// This is taken from slog/internal/slogtest.RemoveTime.
func RemoveTime(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey && len(groups) == 0 {
return slog.Attr{}
}
return a
}