Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384package log
import ( "bytes" "context" "encoding/json" "log/slog" "strings" "testing"
"go.opentelemetry.io/otel/trace")
func TestNewWithFormatTextAndDefault(t *testing.T) { for _, format := range []string{"", "text"} { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", format) if err != nil { t.Fatal(err) } logger.Info("hello") if got := buf.String(); !strings.Contains(got, "spindle") || !strings.Contains(got, "hello") { t.Errorf("text log = %q", got) } }}
func TestNewWithFormatJSON(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } logger.Info("hello")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["logger"] != "spindle" || record["msg"] != "hello" { t.Errorf("json log = %#v", record) }}
func TestNewWithFormatRejectsUnknownFormat(t *testing.T) { if _, err := NewWithFormat("spindle", "unknown"); err == nil { t.Fatal("unknown format accepted") }}
func TestSubLoggerPreservesJSON(t *testing.T) { var buf bytes.Buffer base, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } SubLogger(SubLogger(base, "mill"), "session").Info("connected")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["logger"] != "spindle/mill/session" { t.Errorf("logger = %q", record["logger"]) } if strings.Count(buf.String(), `"logger"`) != 1 { t.Errorf("duplicate logger fields in %s", buf.String()) }}
func TestSubLoggerPreservesBoundAttributesAndGroups(t *testing.T) { var buf bytes.Buffer base, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } bound := base.With("request_id", "req-1").WithGroup("request").With("actor", "did:web:alice") SubLogger(bound, "xrpc").Info("handled")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["logger"] != "spindle/xrpc" || record["request_id"] != "req-1" { t.Errorf("json log = %#v", record) } group, ok := record["request"].(map[string]any) if !ok || group["actor"] != "did:web:alice" { t.Errorf("request group = %#v", record["request"]) }}
func TestSubLoggerPreservesUnknownHandler(t *testing.T) { var buf bytes.Buffer base := slog.New(slog.NewJSONHandler(&buf, nil)) SubLogger(base, "worker").Info("running")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["component"] != "worker" { t.Errorf("component = %q", record["component"]) }}
func TestTracingInLogHandler(t *testing.T) { sampledSpanCtx := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{1}, SpanID: trace.SpanID{2}, TraceFlags: trace.FlagsSampled, }) unsampledSpanCtx := trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{3}, SpanID: trace.SpanID{4}, TraceFlags: 0, })
t.Run("sampled JSON", func(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } ctx := trace.ContextWithSpanContext(context.Background(), sampledSpanCtx) logger.InfoContext(ctx, "hello")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["trace_id"] != sampledSpanCtx.TraceID().String() { t.Errorf("trace_id = %v, want %v", record["trace_id"], sampledSpanCtx.TraceID().String()) } if record["span_id"] != sampledSpanCtx.SpanID().String() { t.Errorf("span_id = %v, want %v", record["span_id"], sampledSpanCtx.SpanID().String()) } })
t.Run("sampled text", func(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "text") if err != nil { t.Fatal(err) } ctx := trace.ContextWithSpanContext(context.Background(), sampledSpanCtx) logger.InfoContext(ctx, "hello") got := buf.String() if !strings.Contains(got, sampledSpanCtx.TraceID().String()) { t.Errorf("text log missing trace_id: %q", got) } if !strings.Contains(got, sampledSpanCtx.SpanID().String()) { t.Errorf("text log missing span_id: %q", got) } })
t.Run("unsampled JSON", func(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } ctx := trace.ContextWithSpanContext(context.Background(), unsampledSpanCtx) logger.InfoContext(ctx, "hello")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if _, ok := record["trace_id"]; ok { t.Errorf("unsampled log should not have trace_id") } if _, ok := record["span_id"]; ok { t.Errorf("unsampled log should not have span_id") } })
t.Run("no context JSON", func(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } logger.Info("hello")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if _, ok := record["trace_id"]; ok { t.Errorf("no-context log should not have trace_id") } if _, ok := record["span_id"]; ok { t.Errorf("no-context log should not have span_id") } })
t.Run("prevent duplicate fields in record attributes", func(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } ctx := trace.ContextWithSpanContext(context.Background(), sampledSpanCtx) logger.InfoContext(ctx, "hello", "trace_id", "custom-trace-id", "span_id", "custom-span-id")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["trace_id"] != "custom-trace-id" { t.Errorf("trace_id = %v, want custom-trace-id", record["trace_id"]) } if record["span_id"] != "custom-span-id" { t.Errorf("span_id = %v, want custom-span-id", record["span_id"]) } })
t.Run("prevent duplicate fields in WithAttrs", func(t *testing.T) { var buf bytes.Buffer logger, err := newWithFormatWriter(&buf, "spindle", "json") if err != nil { t.Fatal(err) } ctx := trace.ContextWithSpanContext(context.Background(), sampledSpanCtx) logger.With("trace_id", "custom-trace-id", "span_id", "custom-span-id").InfoContext(ctx, "hello")
var record map[string]any if err := json.Unmarshal(buf.Bytes(), &record); err != nil { t.Fatalf("parse json log: %v", err) } if record["trace_id"] != "custom-trace-id" { t.Errorf("trace_id = %v, want custom-trace-id", record["trace_id"]) } if record["span_id"] != "custom-span-id" { t.Errorf("span_id = %v, want custom-span-id", record["span_id"]) } })}
type mockHandler struct { records []slog.Record attrs []slog.Attr groups []string name string enabled bool}
func (m *mockHandler) Enabled(ctx context.Context, level slog.Level) bool { return m.enabled}
func (m *mockHandler) Handle(ctx context.Context, r slog.Record) error { m.records = append(m.records, r) return nil}
func (m *mockHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return &mockHandler{ records: m.records, attrs: append(m.attrs, attrs...), groups: m.groups, name: m.name, enabled: m.enabled, }}
func (m *mockHandler) WithGroup(name string) slog.Handler { return &mockHandler{ records: m.records, attrs: m.attrs, groups: append(m.groups, name), name: m.name, enabled: m.enabled, }}
func (m *mockHandler) WithName(name string) slog.Handler { return &mockHandler{ records: m.records, attrs: m.attrs, groups: m.groups, name: name, enabled: m.enabled, }}
func TestFanoutHandlerNoExtra(t *testing.T) { primary := NewHandler("spindle") handler := NewFanoutHandler(primary) if handler != primary { t.Error("NewFanoutHandler with no extra handlers should return the primary handler directly") }}
func TestFanoutHandlerRouting(t *testing.T) { primaryBuf := new(bytes.Buffer) primary, err := newWithFormatWriter(primaryBuf, "primary", "json") if err != nil { t.Fatal(err) }
mock1 := &mockHandler{enabled: true} mock2 := &mockHandler{enabled: false}
fanout := NewFanoutHandler(primary.Handler(), mock1, mock2) logger := slog.New(fanout)
logger.Info("hello", "foo", "bar")
if len(mock1.records) != 1 { t.Errorf("mock1 got %d records, want 1", len(mock1.records)) } else { rec := mock1.records[0] if rec.Message != "hello" { t.Errorf("mock1 message = %q, want hello", rec.Message) } }
if len(mock2.records) != 0 { t.Errorf("mock2 got %d records, want 0", len(mock2.records)) }
if !strings.Contains(primaryBuf.String(), `"msg":"hello"`) { t.Errorf("primaryBuf = %q, missing hello", primaryBuf.String()) }}
func TestSubLoggerFanoutPropagation(t *testing.T) { primaryBuf := new(bytes.Buffer) primary, err := newWithFormatWriter(primaryBuf, "primary", "json") if err != nil { t.Fatal(err) }
mock := &mockHandler{enabled: true} fanout := NewFanoutHandler(primary.Handler(), mock) logger := slog.New(fanout)
sub := SubLogger(logger, "sub") sub.Info("test")
if !strings.Contains(primaryBuf.String(), `"logger":"primary/sub"`) { t.Errorf("primaryBuf = %q, missing logger primary/sub", primaryBuf.String()) }
if fanoutSub, ok := sub.Handler().(*fanoutHandler); ok { if m, ok := fanoutSub.others[0].(*mockHandler); ok { if m.name != "primary/sub" { t.Errorf("mock name = %q, want primary/sub", m.name) } } else { t.Error("fanoutSub others[0] is not *mockHandler") } } else { t.Error("sub handler is not *fanoutHandler") }}
func TestFanoutHandlerWithGroupAndAttrs(t *testing.T) { primaryBuf := new(bytes.Buffer) primary, err := newWithFormatWriter(primaryBuf, "primary", "json") if err != nil { t.Fatal(err) }
mock := &mockHandler{enabled: true} fanout := NewFanoutHandler(primary.Handler(), mock) logger := slog.New(fanout)
logger.With("a", "b").WithGroup("g").Info("test")
if fanoutSub, ok := logger.With("a", "b").WithGroup("g").Handler().(*fanoutHandler); ok { if m, ok := fanoutSub.others[0].(*mockHandler); ok { if len(m.attrs) != 1 || m.attrs[0].Key != "a" || m.attrs[0].Value.String() != "b" { t.Errorf("mock attrs = %v, want [{a b}]", m.attrs) } if len(m.groups) != 1 || m.groups[0] != "g" { t.Errorf("mock groups = %v, want [g]", m.groups) } } }}