package observability import ( "bytes" "context" "encoding/json" "log/slog" "testing" "sourcecraft.dev/bigbes/lethe/internal/config" ) // TestLoggerInitTintAndJSON exercises Init for both supported formats and // ensures the package-default logger is replaced. func TestLoggerInitTintAndJSON(t *testing.T) { prev := slog.Default() t.Cleanup(func() { slog.SetDefault(prev) }) for _, format := range []string{"tint", "json"} { l := &Logger{Cfg: config.LoggingConfig{Level: "info", Format: format}} if err := l.Init(context.Background()); err != nil { t.Fatalf("Init(%s): %v", format, err) } if l.L == nil { t.Fatalf("Init(%s): L is nil", format) } if slog.Default() != l.L { t.Fatalf("Init(%s): slog.Default not replaced", format) } } } func TestLoggerInitRejectsUnknownLevelAndFormat(t *testing.T) { l := &Logger{Cfg: config.LoggingConfig{Level: "verbose", Format: "json"}} if err := l.Init(context.Background()); err == nil { t.Fatalf("Init: expected error for unknown level") } l = &Logger{Cfg: config.LoggingConfig{Level: "info", Format: "yaml"}} if err := l.Init(context.Background()); err == nil { t.Fatalf("Init: expected error for unknown format") } } // TestContextHandlerAddsRequestIDAndUser proves the contextHandler stamp // fires for both keys when present, and is silent when absent. func TestContextHandlerAddsRequestIDAndUser(t *testing.T) { var buf bytes.Buffer inner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug}) logger := slog.New(&contextHandler{inner: inner}) ctx := WithUser(WithRequestID(context.Background(), "req-42"), "alice") logger.InfoContext(ctx, "hello") var rec map[string]any if err := json.Unmarshal(buf.Bytes(), &rec); err != nil { t.Fatalf("unmarshal log line: %v", err) } if rec["request_id"] != "req-42" { t.Fatalf("request_id = %v; want req-42", rec["request_id"]) } if rec["user"] != "alice" { t.Fatalf("user = %v; want alice", rec["user"]) } // Empty context: no request_id/user fields. buf.Reset() logger.InfoContext(context.Background(), "anon") rec = nil if err := json.Unmarshal(buf.Bytes(), &rec); err != nil { t.Fatalf("unmarshal anon log line: %v", err) } if _, ok := rec["request_id"]; ok { t.Errorf("anon: request_id present unexpectedly") } if _, ok := rec["user"]; ok { t.Errorf("anon: user present unexpectedly") } } // TestJSONMaskingRedactsSensitiveKeys covers the JSON ReplaceAttr path. The // scribe tint path is library-tested by go.bigb.es/auxilia. func TestJSONMaskingRedactsSensitiveKeys(t *testing.T) { var buf bytes.Buffer h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{ReplaceAttr: maskAttrJSON}) logger := slog.New(h) logger.Info("login", slog.String("password", "hunter2"), slog.String("authorization", "Bearer xyz"), slog.String("user", "alice")) var rec map[string]any if err := json.Unmarshal(buf.Bytes(), &rec); err != nil { t.Fatalf("unmarshal: %v", err) } if rec["password"] != "***" { t.Errorf("password = %v; want ***", rec["password"]) } if rec["authorization"] != "***" { t.Errorf("authorization = %v; want ***", rec["authorization"]) } if rec["user"] != "alice" { t.Errorf("user = %v; want alice (not masked)", rec["user"]) } }