package logger import ( "bytes" "log/slog" "strings" "testing" ) func TestParseLevel(t *testing.T) { t.Parallel() tests := []struct { name string in string expected slog.Level }{ {name: "debug", in: "debug", expected: slog.LevelDebug}, {name: "debug upper", in: "DEBUG", expected: slog.LevelDebug}, {name: "info", in: "info", expected: slog.LevelInfo}, {name: "warn", in: "warn", expected: slog.LevelWarn}, {name: "warning alias", in: "warning", expected: slog.LevelWarn}, {name: "error", in: "error", expected: slog.LevelError}, {name: "empty defaults info", in: "", expected: slog.LevelInfo}, {name: "unknown defaults info", in: "trace", expected: slog.LevelInfo}, {name: "whitespace trimmed", in: " debug ", expected: slog.LevelDebug}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := ParseLevel(tt.in); got != tt.expected { t.Fatalf("ParseLevel(%q) = %v, want %v", tt.in, got, tt.expected) } }) } } func TestNew_TextFormat(t *testing.T) { t.Parallel() var buf bytes.Buffer log := New(Options{Level: "debug", Format: "text", Out: &buf}) log.Info("hello") got := buf.String() if !strings.Contains(got, "level=INFO") || !strings.Contains(got, "msg=hello") { t.Fatalf("text output missing level/msg, got: %q", got) } } func TestNew_JSONFormat(t *testing.T) { t.Parallel() var buf bytes.Buffer log := New(Options{Level: "debug", Format: "json", Out: &buf}) log.Warn("hello", "k", "v") got := buf.String() if !strings.Contains(got, `"level":"WARN"`) || !strings.Contains(got, `"msg":"hello"`) || !strings.Contains(got, `"k":"v"`) { t.Fatalf("json output missing fields, got: %q", got) } } func TestNew_LevelFiltersBelowConfigured(t *testing.T) { t.Parallel() var buf bytes.Buffer // Configured at error level: info/warn must not be emitted. log := New(Options{Level: "error", Format: "json", Out: &buf}) log.Info("low", "k", "v") log.Warn("mid", "k", "v") log.Error("high", "k", "v") got := buf.String() if strings.Contains(got, "low") || strings.Contains(got, "mid") { t.Fatalf("lower-than-configured levels leaked, got: %q", got) } if !strings.Contains(got, "high") { t.Fatalf("configured level not emitted, got: %q", got) } } func TestNew_DefaultsToErrAndText(t *testing.T) { t.Parallel() // No options should not panic and should produce a working logger. log := New(Options{}) if log == nil { t.Fatal("New(Options{}) returned nil logger") } } func TestWithSource_TagsRecords(t *testing.T) { t.Parallel() var buf bytes.Buffer base := New(Options{Level: "debug", Format: "json", Out: &buf}) log := WithSource(base, "runtime/loop") log.Info("hello") if !strings.Contains(buf.String(), `"source":"runtime/loop"`) { t.Fatalf("missing source field, got %q", buf.String()) } } func TestScoped_TagsModuleAndSub(t *testing.T) { t.Parallel() var buf bytes.Buffer base := New(Options{Level: "debug", Format: "json", Out: &buf}) log := Scoped(base, "channel", "adapter") log.Info("hello") got := buf.String() if !strings.Contains(got, `"module":"channel"`) || !strings.Contains(got, `"sub":"adapter"`) { t.Fatalf("missing module/sub fields, got %q", got) } }