Files
insights-go/error_tracking_slog_test.go
zeekayandHanzo Dev 8ee8950230 Go 1.27.1, and the code it can say more simply
The module declares go 1.27.1 and the adapter image follows. go fix
applied the modernizers and left ptrString and ptrBool with no callers,
so they are gone; omitzero is not applied, since it changes what the
JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:52:31 -07:00

236 lines
7.4 KiB
Go

package insights
import (
"context"
"errors"
"log/slog"
"testing"
"time"
)
func createLogRecord(level slog.Level, msg string, attrs ...slog.Attr) slog.Record {
r := slog.Record{Time: time.Now(), Level: level, Message: msg}
for _, a := range attrs {
r.AddAttrs(a)
}
return r
}
func TestSlogCaptureHandler_Handle(t *testing.T) {
optFactory := func(distinctID string) []SlogOption {
return []SlogOption{
WithMinCaptureLevel(slog.LevelWarn),
WithDistinctIDFn(func(_ context.Context, _ slog.Record) string { return distinctID }),
}
}
tests := map[string]struct {
createLogHandler func(slog.Handler, EnqueueClient) *SlogCaptureHandler
record slog.Record
nextHandlerEnabled bool
shouldForward bool
shouldEnqueue bool
}{
"nil client: forwards only": {
createLogHandler: func(next slog.Handler, _ EnqueueClient) *SlogCaptureHandler {
return NewSlogCaptureHandler(next, nil, optFactory("test")...)
},
record: createLogRecord(slog.LevelError, "title",
slog.Any("error", errors.New("test-error"))),
nextHandlerEnabled: true,
shouldForward: true,
shouldEnqueue: false,
},
"below min level: forward only": {
createLogHandler: func(next slog.Handler, client EnqueueClient) *SlogCaptureHandler {
return NewSlogCaptureHandler(next, client, optFactory("test")...)
},
record: createLogRecord(slog.LevelInfo, "warn"),
nextHandlerEnabled: true,
shouldForward: true,
shouldEnqueue: false,
},
"at min level: forward and enqueue": {
createLogHandler: func(next slog.Handler, client EnqueueClient) *SlogCaptureHandler {
return NewSlogCaptureHandler(next, client, optFactory("test")...)
},
record: createLogRecord(slog.LevelWarn, "warn"),
nextHandlerEnabled: true,
shouldForward: true,
shouldEnqueue: true,
},
"empty distinct ID: forward only": {
createLogHandler: func(next slog.Handler, client EnqueueClient) *SlogCaptureHandler {
return NewSlogCaptureHandler(next, client, optFactory("")...)
},
record: createLogRecord(slog.LevelError, "oops"),
nextHandlerEnabled: true,
shouldForward: true,
shouldEnqueue: false,
},
"next handler not enabled: enqueue only": {
createLogHandler: func(next slog.Handler, client EnqueueClient) *SlogCaptureHandler {
return NewSlogCaptureHandler(next, client, optFactory("test")...)
},
record: createLogRecord(slog.LevelError, "title", slog.Any("error", errors.New("kaboom"))),
nextHandlerEnabled: false,
shouldForward: false,
shouldEnqueue: true,
},
"happy path: forward and enqueues": {
createLogHandler: func(next slog.Handler, client EnqueueClient) *SlogCaptureHandler {
return NewSlogCaptureHandler(next, client, optFactory("test")...)
},
record: createLogRecord(slog.LevelError, "title", slog.Any("error", errors.New("kaboom"))),
nextHandlerEnabled: true,
shouldForward: true,
shouldEnqueue: true,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
next := &fakeNextSlogHandler{isEnabled: tc.nextHandlerEnabled}
client := &fakeEnqueueClient{}
ctx := context.Background()
logHandler := tc.createLogHandler(next, client)
if err := logHandler.Handle(ctx, tc.record); err != nil {
t.Fatalf("Handle returned error: %v", err)
}
if tc.shouldForward && len(next.handledRecords) == 0 {
t.Fatalf("expected forwarding to next handler")
}
if !tc.shouldForward && len(next.handledRecords) != 0 {
t.Fatalf("did not expect forwarding, got %d", len(next.handledRecords))
}
if tc.shouldEnqueue && logHandler.client == nil {
t.Fatalf("we can't check for enqueued messages without a client")
}
if logHandler.client != nil {
if tc.shouldEnqueue && len(client.enqueuedMsgs) != 1 {
t.Fatalf("expected 1 enqueue, got %d", len(client.enqueuedMsgs))
}
if !tc.shouldEnqueue && len(client.enqueuedMsgs) != 0 {
t.Fatalf("expected 0 enqueues, got %d", len(client.enqueuedMsgs))
}
}
})
}
}
func TestErrorExtractor_ExtractDescription(t *testing.T) {
testError := errors.New("this-is-an-error")
wrappedError := wrappedError{err: testError}
extractor := ErrorExtractor{ErrorKeys: []string{"error", "err"}, Fallback: "fallback-loaded"}
tests := map[string]struct {
rec slog.Record
want string
}{
"extraction of error by key": {
rec: createLogRecord(slog.LevelError, "test", slog.Any("error", testError)),
want: "this-is-an-error",
},
"extraction of error by case-insensitive key": {
rec: createLogRecord(slog.LevelError, "test", slog.Any("ErR", testError)),
want: "this-is-an-error",
},
"extraction of wrapped error by key": {
rec: createLogRecord(slog.LevelError, "test", slog.Any("err", wrappedError)),
want: "wrap: this-is-an-error",
},
"extraction of error as group returns the first error found": {
rec: createLogRecord(slog.LevelError, "test", slog.Group("err",
slog.Any("foo", "bar"),
slog.Any("err", testError),
slog.Any("error", wrappedError),
)),
want: "this-is-an-error",
},
"in case of multiple error keys, the first one wins": {
rec: createLogRecord(slog.LevelError, "test", slog.Any("error", testError), slog.Any("err", wrappedError)),
want: "this-is-an-error",
},
"no match returns fallback": {
rec: createLogRecord(slog.LevelError, "test", slog.String("note", "this is a test case")),
want: "fallback-loaded",
},
"error key without error should return fallback": {
rec: createLogRecord(slog.LevelError, "test", slog.String("error", "i'm not an error")),
want: "fallback-loaded",
},
"extraction via LogValuer": {
rec: createLogRecord(slog.LevelError, "test", slog.Any("error", errValuer{err: wrappedError})),
want: "wrap: this-is-an-error",
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
got := extractor.ExtractDescription(tc.rec)
if got != tc.want {
t.Fatalf("ExtractDescription failed got=%q, want=%q", got, tc.want)
}
})
}
}
func TestSlogCaptureHandler_WithPropertiesFn(t *testing.T) {
next := &fakeNextSlogHandler{isEnabled: true}
client := &fakeEnqueueClient{}
ctx := context.Background()
handler := NewSlogCaptureHandler(next, client,
WithMinCaptureLevel(slog.LevelWarn),
WithDistinctIDFn(func(_ context.Context, _ slog.Record) string {
return "test-user"
}),
WithPropertiesFn(func(_ context.Context, r slog.Record) Properties {
props := NewProperties()
r.Attrs(func(a slog.Attr) bool {
props.Set(a.Key, a.Value.Any())
return true
})
return props
}),
)
record := createLogRecord(slog.LevelError, "test error",
slog.String("environment", "production"),
slog.Int("retry_count", 3),
)
if err := handler.Handle(ctx, record); err != nil {
t.Fatalf("Handle returned error: %v", err)
}
if len(client.enqueuedMsgs) != 1 {
t.Fatalf("expected 1 enqueued message, got %d", len(client.enqueuedMsgs))
}
exception, ok := client.enqueuedMsgs[0].(Exception)
if !ok {
t.Fatalf("expected Exception, got %T", client.enqueuedMsgs[0])
}
if exception.Properties == nil {
t.Fatal("expected Properties to be set")
}
expectedProps := map[string]any{
"environment": "production",
"retry_count": int64(3),
}
for key, expected := range expectedProps {
if exception.Properties[key] != expected {
t.Errorf("property %s: expected %v (type %T), got %v (type %T)",
key, expected, expected, exception.Properties[key], exception.Properties[key])
}
}
}