package logger import ( "context" "log/slog" "os" "testing" "time" ) func TestParseLevel(t *testing.T) { tests := []struct { name string input string expected slog.Level expectError bool }{ {"empty string", "", slog.LevelInfo, false}, {"DEBUG upper", "DEBUG", slog.LevelDebug, false}, {"debug lower", "debug", slog.LevelDebug, false}, {"INFO upper", "INFO", slog.LevelInfo, false}, {"info lower", "info", slog.LevelInfo, false}, {"WARN upper", "WARN", slog.LevelWarn, false}, {"warn lower", "warn", slog.LevelWarn, false}, {"ERROR upper", "ERROR", slog.LevelError, false}, {"error lower", "error", slog.LevelError, false}, {"invalid level", "invalid", slog.LevelInfo, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { level, err := ParseLevel(tt.input) if tt.expectError { if err == nil { t.Errorf("expected error for input %q, got nil", tt.input) } } else { if err != nil { t.Errorf("unexpected error for input %q: %v", tt.input, err) } if level != tt.expected { t.Errorf("expected level %v for input %q, got %v", tt.expected, tt.input, level) } } }) } } func TestSetLevel(t *testing.T) { // Store original level to restore later originalLevel := Level.Level() defer Level.Set(originalLevel) SetLevel(slog.LevelDebug) if Level.Level() != slog.LevelDebug { t.Errorf("expected Level to be Debug, got %v", Level.Level()) } SetLevel(slog.LevelError) if Level.Level() != slog.LevelError { t.Errorf("expected Level to be Error, got %v", Level.Level()) } } func TestSetOTLPLevel(t *testing.T) { // Store original level to restore later originalLevel := OTLPLevel.Level() defer OTLPLevel.Set(originalLevel) SetOTLPLevel(slog.LevelWarn) if OTLPLevel.Level() != slog.LevelWarn { t.Errorf("expected OTLPLevel to be Warn, got %v", OTLPLevel.Level()) } SetOTLPLevel(slog.LevelDebug) if OTLPLevel.Level() != slog.LevelDebug { t.Errorf("expected OTLPLevel to be Debug, got %v", OTLPLevel.Level()) } } func TestOTLPLevelHandler(t *testing.T) { // Create a mock handler that counts calls callCount := 0 mockHandler := &mockHandler{ handleFunc: func(ctx context.Context, r slog.Record) error { callCount++ return nil }, } // Set OTLP level to Warn originalLevel := OTLPLevel.Level() defer OTLPLevel.Set(originalLevel) OTLPLevel.Set(slog.LevelWarn) // Create OTLP level handler handler := newOTLPLevelHandler(mockHandler) ctx := context.Background() // Test that Debug and Info are filtered out if handler.Enabled(ctx, slog.LevelDebug) { t.Error("Debug level should be disabled when OTLP level is Warn") } if handler.Enabled(ctx, slog.LevelInfo) { t.Error("Info level should be disabled when OTLP level is Warn") } // Test that Warn and Error are enabled if !handler.Enabled(ctx, slog.LevelWarn) { t.Error("Warn level should be enabled when OTLP level is Warn") } if !handler.Enabled(ctx, slog.LevelError) { t.Error("Error level should be enabled when OTLP level is Warn") } // Test that Handle respects level filtering now := time.Now() debugRecord := slog.NewRecord(now, slog.LevelDebug, "debug message", 0) warnRecord := slog.NewRecord(now, slog.LevelWarn, "warn message", 0) handler.Handle(ctx, debugRecord) if callCount != 0 { t.Error("Debug record should not be passed to underlying handler") } handler.Handle(ctx, warnRecord) if callCount != 1 { t.Error("Warn record should be passed to underlying handler") } } func TestEnvironmentVariables(t *testing.T) { tests := []struct { name string envVar string envValue string configPrefix string testFunc func(t *testing.T) }{ { name: "LOG_LEVEL sets stderr level", envVar: "LOG_LEVEL", envValue: "ERROR", testFunc: func(t *testing.T) { // Reset the setup state resetLoggerSetup() // Call setupStdErrHandler which should read the env var handler := setupStdErrHandler() if handler == nil { t.Fatal("setupStdErrHandler returned nil") } if Level.Level() != slog.LevelError { t.Errorf("expected Level to be Error after setting LOG_LEVEL=ERROR, got %v", Level.Level()) } }, }, { name: "Prefixed LOG_LEVEL", envVar: "TEST_LOG_LEVEL", envValue: "DEBUG", configPrefix: "TEST", testFunc: func(t *testing.T) { ConfigPrefix = "TEST" defer func() { ConfigPrefix = "" }() resetLoggerSetup() handler := setupStdErrHandler() if handler == nil { t.Fatal("setupStdErrHandler returned nil") } if Level.Level() != slog.LevelDebug { t.Errorf("expected Level to be Debug after setting TEST_LOG_LEVEL=DEBUG, got %v", Level.Level()) } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Store original env value and level originalEnv := os.Getenv(tt.envVar) originalLevel := Level.Level() defer func() { os.Setenv(tt.envVar, originalEnv) Level.Set(originalLevel) }() // Set test environment variable os.Setenv(tt.envVar, tt.envValue) // Run the test tt.testFunc(t) }) } } // mockHandler is a simple mock implementation of slog.Handler for testing type mockHandler struct { handleFunc func(ctx context.Context, r slog.Record) error } func (m *mockHandler) Enabled(ctx context.Context, level slog.Level) bool { return true } func (m *mockHandler) Handle(ctx context.Context, r slog.Record) error { if m.handleFunc != nil { return m.handleFunc(ctx, r) } return nil } func (m *mockHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return m } func (m *mockHandler) WithGroup(name string) slog.Handler { return m } // resetLoggerSetup resets the sync.Once instances for testing func resetLoggerSetup() { // Reset package-level variables textLogger = nil otlpLogger = nil multiLogger = nil // Note: We can't easily reset sync.Once instances in tests, // but for the specific test we're doing (environment variable parsing) // we can test the setupStdErrHandler function directly }