diff options
author | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2025-08-02 20:55:11 +0530 |
---|---|---|
committer | Marc Pervaz Boocha <mboocha@sudomsg.com> | 2025-08-02 20:55:11 +0530 |
commit | ce6cf13c2d67c3368251d1eea5593198f5021330 (patch) | |
tree | d4c2347fd45fce395bf22a30e423697494d4284f /logging/test | |
download | kit-0.1.0.tar kit-0.1.0.tar.gz kit-0.1.0.tar.bz2 kit-0.1.0.tar.lz kit-0.1.0.tar.xz kit-0.1.0.tar.zst kit-0.1.0.zip |
Initial Commitv0.1.0
Diffstat (limited to 'logging/test')
-rw-r--r-- | logging/test/handler.go | 112 |
1 files changed, 112 insertions, 0 deletions
diff --git a/logging/test/handler.go b/logging/test/handler.go new file mode 100644 index 0000000..d1f5d40 --- /dev/null +++ b/logging/test/handler.go @@ -0,0 +1,112 @@ +package test + +// Package test provides mocks and helpers for testing code that uses slog logging. +// +// It includes MockHandler, a thread-safe slog.Handler implementation for capturing log records in tests. + +import ( + "context" + "log/slog" + "slices" + "sync" + "testing" +) + +type logRecorder struct { + mu sync.Mutex + records []slog.Record +} + +func (h *logRecorder) Append(r slog.Record) { + h.mu.Lock() + defer h.mu.Unlock() + + h.records = append(h.records, r.Clone()) +} + +func (h *logRecorder) Records() []slog.Record { + h.mu.Lock() + defer h.mu.Unlock() + return slices.Clone(h.records) +} + +// MockHandler is a slog.Handler that records log records for testing. +// +// It supports attribute and group chaining like slog's built-in handlers. +// Use NewMockLogHandler to construct one, then pass it to slog.New in your tests. +// +// All recorded logs can be retrieved with the Records method. +type MockHandler struct { + recorder *logRecorder + parent *MockHandler + group string + attrs []slog.Attr +} + +var _ slog.Handler = &MockHandler{} + +// NewMockLogHandler creates a new MockHandler for use in tests. +func NewMockLogHandler(tb testing.TB) *MockHandler { + tb.Helper() + + return &MockHandler{ + recorder: &logRecorder{}, + } +} + +func (h *MockHandler) Enabled(ctx context.Context, level slog.Level) bool { + return true // Capture all logs +} + +func (h *MockHandler) Handle(ctx context.Context, r slog.Record) error { + if h.parent == nil { + r.Clone() + h.recorder.Append(r) + return nil + } + + newRecord := slog.NewRecord(r.Time, r.Level, r.Message, r.PC) + + attrs := slices.Clone(h.attrs) + r.Attrs(func(attr slog.Attr) bool { + attrs = append(attrs, attr) + return true + }) + + if h.group != "" { + newRecord.AddAttrs(slog.Attr{ + Key: h.group, + Value: slog.GroupValue(attrs...), + }) + } else { + newRecord.AddAttrs(attrs...) + } + + return h.parent.Handle(ctx, newRecord) +} + +func (h *MockHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return &MockHandler{ + recorder: h.recorder, + parent: h, + attrs: attrs, + } +} + +func (h *MockHandler) WithGroup(name string) slog.Handler { + if name == "" { + return h + } + return &MockHandler{ + recorder: h.recorder, + parent: h, + group: name, + } +} + +// Records returns a copy of all slog.Records captured by this handler so far. +// +// It is safe to call from multiple goroutines. +func (h *MockHandler) Records() []slog.Record { + return h.recorder.Records() +} |