diff --git a/go.mod b/go.mod index fd9bb2b3962..2bd5aa206e7 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.32.1 // indirect github.com/prometheus/procfs v0.7.3 // indirect + github.com/searKing/golang/go v1.2.138 // indirect github.com/sergi/go-diff v1.2.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -80,6 +81,7 @@ require ( github.com/yudai/pp v2.0.1+incompatible // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.66.4 // indirect diff --git a/go.sum b/go.sum index 5398b2d6b1d..a957cde1aae 100644 --- a/go.sum +++ b/go.sum @@ -450,6 +450,8 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/searKing/golang/go v1.2.138 h1:Q53UC295VzOqCwNkpabwgU60wu1KlsmxdkFYHfEMDZI= +github.com/searKing/golang/go v1.2.138/go.mod h1:IeVG9+PUi6BP5Snxrd1LahoLjxwNIJdycu9M1jq6gos= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -748,6 +750,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/logger/glog.go b/logger/glog.go index dfeb3530f1d..aa830dbb6e3 100644 --- a/logger/glog.go +++ b/logger/glog.go @@ -1,12 +1,20 @@ package logger import ( + "context" + "log/slog" + "os" + "github.com/golang/glog" + slogglog "github.com/searKing/golang/go/log/slog" ) // GlogLogger implements the Logger interface for logging using the glog library with configurable call depth. +// It also provides slog-compatible methods through an embedded slog.Logger that uses a glog handler. type GlogLogger struct { - depth int + depth int + slogLogger *slog.Logger + exitFunc func(int) // allows testing Fatal without actually exiting } // Debug logs a debug-level message with the specified format and arguments. @@ -35,7 +43,78 @@ func (logger *GlogLogger) Fatalf(msg string, args ...any) { } func NewGlogLogger() Logger { + // Create a glog handler that writes to stderr (matching glog's default behavior) + handler := slogglog.NewGlogHandler(os.Stderr, &slog.HandlerOptions{ + Level: slog.LevelDebug, // Allow all levels, glog will handle filtering + }) + + // Capture the default level-to-prefix mapping + defaultReplaceLevelString := handler.ReplaceLevelString + + // Customize to add "F" for Fatal level, delegating to default for others + handler.ReplaceLevelString = func(l slog.Level) string { + if l >= LevelFatal { + return "F" // Fatal (for LevelFatal and above) + } + return defaultReplaceLevelString(l) // Use default mapping for D/I/W/E + } + return &GlogLogger{ - depth: 1, + depth: 1, + slogLogger: slog.New(handler), + exitFunc: os.Exit, // default to os.Exit, can be overridden for testing } } + +// StructuredLogger interface implementation + +// Debug logs at Debug level using slog +func (logger *GlogLogger) Debug(msg string, args ...any) { + logger.DebugContext(context.Background(), msg, args...) +} + +// DebugContext logs at Debug level with context using slog +func (logger *GlogLogger) DebugContext(ctx context.Context, msg string, args ...any) { + logger.slogLogger.DebugContext(ctx, msg, args...) +} + +// Info logs at Info level using slog +func (logger *GlogLogger) Info(msg string, args ...any) { + logger.InfoContext(context.Background(), msg, args...) +} + +// InfoContext logs at Info level with context using slog +func (logger *GlogLogger) InfoContext(ctx context.Context, msg string, args ...any) { + logger.slogLogger.InfoContext(ctx, msg, args...) +} + +// Warn logs at Warn level using slog +func (logger *GlogLogger) Warn(msg string, args ...any) { + logger.WarnContext(context.Background(), msg, args...) +} + +// WarnContext logs at Warn level with context using slog +func (logger *GlogLogger) WarnContext(ctx context.Context, msg string, args ...any) { + logger.slogLogger.WarnContext(ctx, msg, args...) +} + +// Error logs at Error level using slog +func (logger *GlogLogger) Error(msg string, args ...any) { + logger.ErrorContext(context.Background(), msg, args...) +} + +// ErrorContext logs at Error level with context using slog +func (logger *GlogLogger) ErrorContext(ctx context.Context, msg string, args ...any) { + logger.slogLogger.ErrorContext(ctx, msg, args...) +} + +// Fatal logs at Fatal level using slog and terminates the program +func (logger *GlogLogger) Fatal(msg string, args ...any) { + logger.FatalContext(context.Background(), msg, args...) +} + +// FatalContext logs at Fatal level with context using slog and terminates the program +func (logger *GlogLogger) FatalContext(ctx context.Context, msg string, args ...any) { + logger.slogLogger.Log(ctx, LevelFatal, msg, args...) + logger.exitFunc(1) +} diff --git a/logger/glog_test.go b/logger/glog_test.go index 0af2394cb64..4158a4c7f6d 100644 --- a/logger/glog_test.go +++ b/logger/glog_test.go @@ -1,6 +1,7 @@ package logger import ( + "context" "flag" "testing" @@ -19,6 +20,7 @@ func TestNewGlogLogger(t *testing.T) { glogLogger, ok := logger.(*GlogLogger) assert.True(t, ok, "Logger should be of type *GlogLogger") assert.Equal(t, 1, glogLogger.depth, "Default depth should be 1") + assert.NotNil(t, glogLogger.slogLogger, "slogLogger field should be initialized") } func TestGlogLogger_ImplementsLoggerInterface(t *testing.T) { @@ -169,3 +171,267 @@ func TestGlogLogger_SpecialCharacters(t *testing.T) { logger.Infof("message with special chars: \n\t\"quotes\" and 'apostrophes'") }, "Messages with special characters should not panic") } + +// Tests for StructuredLogger interface implementation on GlogLogger + +func TestGlogLogger_SlogDebug(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + flag.Set("v", "2") + + logger := NewGlogLogger() + + assert.NotPanics(t, func() { + logger.Debug("debug message") + }, "Debug should not panic") + + assert.NotPanics(t, func() { + logger.Debug("debug with args", "key", "value", "number", 42) + }, "Debug with args should not panic") +} + +func TestGlogLogger_SlogDebugContext(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + flag.Set("v", "2") + + logger := NewGlogLogger() + ctx := context.Background() + + assert.NotPanics(t, func() { + logger.DebugContext(ctx, "debug with context") + }, "DebugContext should not panic") + + assert.NotPanics(t, func() { + logger.DebugContext(ctx, "debug context with args", "key", "value") + }, "DebugContext with args should not panic") +} + +func TestGlogLogger_SlogInfo(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + + assert.NotPanics(t, func() { + logger.Info("info message") + }, "Info should not panic") + + assert.NotPanics(t, func() { + logger.Info("info with args", "status", "ok") + }, "Info with args should not panic") +} + +func TestGlogLogger_SlogInfoContext(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + ctx := context.WithValue(context.Background(), "requestID", "12345") + + assert.NotPanics(t, func() { + logger.InfoContext(ctx, "info with context") + }, "InfoContext should not panic") + + assert.NotPanics(t, func() { + logger.InfoContext(ctx, "info with context and args", "component", "test") + }, "InfoContext with args should not panic") +} + +func TestGlogLogger_SlogWarn(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + + assert.NotPanics(t, func() { + logger.Warn("warning message") + }, "Warn should not panic") + + assert.NotPanics(t, func() { + logger.Warn("warning with args", "severity", "medium") + }, "Warn with args should not panic") +} + +func TestGlogLogger_SlogWarnContext(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + ctx := context.Background() + + assert.NotPanics(t, func() { + logger.WarnContext(ctx, "warning with context") + }, "WarnContext should not panic") + + assert.NotPanics(t, func() { + logger.WarnContext(ctx, "warning with context", "severity", "medium") + }, "WarnContext with args should not panic") +} + +func TestGlogLogger_SlogError(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + + assert.NotPanics(t, func() { + logger.Error("error message") + }, "Error should not panic") + + assert.NotPanics(t, func() { + logger.Error("error with details", "code", 500, "err", "internal error") + }, "Error with args should not panic") +} + +func TestGlogLogger_SlogErrorContext(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + ctx := context.Background() + + assert.NotPanics(t, func() { + logger.ErrorContext(ctx, "error with context") + }, "ErrorContext should not panic") + + assert.NotPanics(t, func() { + logger.ErrorContext(ctx, "error with context", "component", "api") + }, "ErrorContext with args should not panic") +} + +func TestGlogLogger_SlogAllLevels(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + flag.Set("v", "2") + + logger := NewGlogLogger() + ctx := context.Background() + + // Test that all slog logging levels work together + assert.NotPanics(t, func() { + logger.Debug("debug") + logger.DebugContext(ctx, "debug context") + logger.Info("info") + logger.InfoContext(ctx, "info context") + logger.Warn("warn") + logger.WarnContext(ctx, "warn context") + logger.Error("error") + logger.ErrorContext(ctx, "error context") + }, "All slog logging levels should work without panic") +} + +func TestGlogLogger_SlogWithVariousContexts(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger() + + // Test with different context types + ctxWithValue := context.WithValue(context.Background(), "requestID", "abc123") + ctxBackground := context.Background() + + assert.NotPanics(t, func() { + logger.InfoContext(ctxWithValue, "with value context") + logger.InfoContext(ctxBackground, "with background context") + }, "Different context types should work") +} + +func TestGlogLogger_BothGlogAndSlogMethods(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + flag.Set("v", "2") + + logger := NewGlogLogger() + ctx := context.Background() + + // Test that both old-style (Debugf, Infof) and new-style (Debug, Info) methods work + assert.NotPanics(t, func() { + logger.Debugf("debug formatted") + logger.Debug("debug structured") + logger.DebugContext(ctx, "debug with context") + + logger.Infof("info formatted: %s", "test") + logger.Info("info structured", "key", "value") + logger.InfoContext(ctx, "info with context") + + logger.Warnf("warn formatted") + logger.Warn("warn structured") + logger.WarnContext(ctx, "warn with context") + + logger.Errorf("error formatted: %v", "error") + logger.Error("error structured", "err", "error") + logger.ErrorContext(ctx, "error with context") + }, "Both GlogLogger and SlogLogger methods should work on same instance") +} + +func TestGlogLogger_FatalCallsExit(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger().(*GlogLogger) + + // Track whether exit was called and with what code + exitCalled := false + exitCode := -1 + + // Override the exit function for testing + logger.exitFunc = func(code int) { + exitCalled = true + exitCode = code + } + + // Call Fatal + logger.Fatal("fatal error message") + + // Verify exit was called with code 1 + assert.True(t, exitCalled, "Fatal should call exit function") + assert.Equal(t, 1, exitCode, "Fatal should exit with code 1") +} + +func TestGlogLogger_FatalContextCallsExit(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger().(*GlogLogger) + ctx := context.Background() + + // Track whether exit was called and with what code + exitCalled := false + exitCode := -1 + + // Override the exit function for testing + logger.exitFunc = func(code int) { + exitCalled = true + exitCode = code + } + + // Call FatalContext + logger.FatalContext(ctx, "fatal error with context", "key", "value") + + // Verify exit was called with code 1 + assert.True(t, exitCalled, "FatalContext should call exit function") + assert.Equal(t, 1, exitCode, "FatalContext should exit with code 1") +} + +func TestGlogLogger_FatalContextWithCustomContext(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + + logger := NewGlogLogger().(*GlogLogger) + ctx := context.WithValue(context.Background(), "requestID", "test-123") + + // Track whether exit was called + exitCalled := false + + // Override the exit function for testing + logger.exitFunc = func(code int) { + exitCalled = true + } + + // Call FatalContext with custom context + logger.FatalContext(ctx, "fatal with custom context") + + // Verify exit was called + assert.True(t, exitCalled, "FatalContext should call exit function even with custom context") +} diff --git a/logger/interface.go b/logger/interface.go index 4d840381bfb..236f6910143 100644 --- a/logger/interface.go +++ b/logger/interface.go @@ -1,6 +1,16 @@ package logger -type Logger interface { +import ( + "context" + "log/slog" +) + +// LevelFatal is a custom slog level for fatal errors that terminate the program. +// It is defined as slog.LevelError + 4 to be higher than all standard slog levels. +const LevelFatal = slog.LevelError + 4 + +// FormattedLogger provides traditional printf-style formatted logging methods. +type FormattedLogger interface { // Debugf level logging Debugf(msg string, args ...any) @@ -13,6 +23,48 @@ type Logger interface { // Errorf level logging Errorf(msg string, args ...any) - // Fatalf level logging + // Fatalf level logging and terminates the program execution Fatalf(msg string, args ...any) } + +// StructuredLogger provides structured logging methods compatible with log/slog, +// including context-aware variants for propagating request context. +type StructuredLogger interface { + // Debug logs at Debug level + Debug(msg string, args ...any) + + // DebugContext logs at Debug level with context + DebugContext(ctx context.Context, msg string, args ...any) + + // Info logs at Info level + Info(msg string, args ...any) + + // InfoContext logs at Info level with context + InfoContext(ctx context.Context, msg string, args ...any) + + // Warn logs at Warn level + Warn(msg string, args ...any) + + // WarnContext logs at Warn level with context + WarnContext(ctx context.Context, msg string, args ...any) + + // Error logs at Error level + Error(msg string, args ...any) + + // ErrorContext logs at Error level with context + ErrorContext(ctx context.Context, msg string, args ...any) + + // Fatal logs at Fatal level and terminates the program execution + Fatal(msg string, args ...any) + + // FatalContext logs at Fatal level with context and terminates the program execution + FatalContext(ctx context.Context, msg string, args ...any) +} + +// Logger combines both traditional printf-style and modern structured logging interfaces. +// Implementations must provide both formatted logging (FormattedLogger) and structured +// context-aware logging (StructuredLogger). +type Logger interface { + FormattedLogger + StructuredLogger +} diff --git a/logger/logger_test.go b/logger/logger_test.go index 1b7f285e9fc..fb0d9db1e30 100644 --- a/logger/logger_test.go +++ b/logger/logger_test.go @@ -1,7 +1,9 @@ package logger import ( + "context" "flag" + "log/slog" "testing" "github.com/stretchr/testify/assert" @@ -9,11 +11,16 @@ import ( // mockLogger is a test implementation of the Logger interface type mockLogger struct { - debugCalls []logCall - infoCalls []logCall - warnCalls []logCall - errorCalls []logCall - fatalCalls []logCall + debugCalls []logCall + infoCalls []logCall + warnCalls []logCall + errorCalls []logCall + fatalCalls []logCall + debugContextCalls []contextLogCall + infoContextCalls []contextLogCall + warnContextCalls []contextLogCall + errorContextCalls []contextLogCall + fatalContextCalls []contextLogCall } type logCall struct { @@ -21,6 +28,12 @@ type logCall struct { args []any } +type contextLogCall struct { + ctx context.Context + msg string + args []any +} + func (m *mockLogger) Debugf(msg string, args ...any) { m.debugCalls = append(m.debugCalls, logCall{msg, args}) } @@ -41,13 +54,60 @@ func (m *mockLogger) Fatalf(msg string, args ...any) { m.fatalCalls = append(m.fatalCalls, logCall{msg, args}) } +// StructuredLogger interface implementation for mockLogger + +func (m *mockLogger) Debug(msg string, args ...any) { + m.debugCalls = append(m.debugCalls, logCall{msg, args}) +} + +func (m *mockLogger) DebugContext(ctx context.Context, msg string, args ...any) { + m.debugContextCalls = append(m.debugContextCalls, contextLogCall{ctx, msg, args}) +} + +func (m *mockLogger) Info(msg string, args ...any) { + m.infoCalls = append(m.infoCalls, logCall{msg, args}) +} + +func (m *mockLogger) InfoContext(ctx context.Context, msg string, args ...any) { + m.infoContextCalls = append(m.infoContextCalls, contextLogCall{ctx, msg, args}) +} + +func (m *mockLogger) Warn(msg string, args ...any) { + m.warnCalls = append(m.warnCalls, logCall{msg, args}) +} + +func (m *mockLogger) WarnContext(ctx context.Context, msg string, args ...any) { + m.warnContextCalls = append(m.warnContextCalls, contextLogCall{ctx, msg, args}) +} + +func (m *mockLogger) Error(msg string, args ...any) { + m.errorCalls = append(m.errorCalls, logCall{msg, args}) +} + +func (m *mockLogger) ErrorContext(ctx context.Context, msg string, args ...any) { + m.errorContextCalls = append(m.errorContextCalls, contextLogCall{ctx, msg, args}) +} + +func (m *mockLogger) Fatal(msg string, args ...any) { + m.fatalCalls = append(m.fatalCalls, logCall{msg, args}) +} + +func (m *mockLogger) FatalContext(ctx context.Context, msg string, args ...any) { + m.fatalContextCalls = append(m.fatalContextCalls, contextLogCall{ctx, msg, args}) +} + func newMockLogger() *mockLogger { return &mockLogger{ - debugCalls: []logCall{}, - infoCalls: []logCall{}, - warnCalls: []logCall{}, - errorCalls: []logCall{}, - fatalCalls: []logCall{}, + debugCalls: []logCall{}, + infoCalls: []logCall{}, + warnCalls: []logCall{}, + errorCalls: []logCall{}, + fatalCalls: []logCall{}, + debugContextCalls: []contextLogCall{}, + infoContextCalls: []contextLogCall{}, + warnContextCalls: []contextLogCall{}, + errorContextCalls: []contextLogCall{}, + fatalContextCalls: []contextLogCall{}, } } @@ -302,3 +362,219 @@ func TestFatal(t *testing.T) { // Restore default logger logger = NewGlogLogger() } + +// Tests for StructuredLogger interface methods + +func TestSlogDebug(t *testing.T) { + mock := newMockLogger() + logger = mock + + // Test Debug (non-context variant) + logger.Debug("debug message") + assert.Len(t, mock.debugCalls, 1, "Should have one debug call") + assert.Equal(t, "debug message", mock.debugCalls[0].msg) + assert.Empty(t, mock.debugCalls[0].args) + + logger.Debug("debug with args", "key", "value", "number", 42) + assert.Len(t, mock.debugCalls, 2, "Should have two debug calls") + assert.Equal(t, "debug with args", mock.debugCalls[1].msg) + assert.Equal(t, []any{"key", "value", "number", 42}, mock.debugCalls[1].args) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogDebugContext(t *testing.T) { + mock := newMockLogger() + logger = mock + ctx := context.Background() + + // Test DebugContext + logger.DebugContext(ctx, "debug with context") + assert.Len(t, mock.debugContextCalls, 1, "Should have one debug context call") + assert.Equal(t, "debug with context", mock.debugContextCalls[0].msg) + assert.Equal(t, ctx, mock.debugContextCalls[0].ctx) + assert.Empty(t, mock.debugContextCalls[0].args) + + logger.DebugContext(ctx, "debug context with args", "key", "value") + assert.Len(t, mock.debugContextCalls, 2, "Should have two debug context calls") + assert.Equal(t, "debug context with args", mock.debugContextCalls[1].msg) + assert.Equal(t, []any{"key", "value"}, mock.debugContextCalls[1].args) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogInfo(t *testing.T) { + mock := newMockLogger() + logger = mock + + logger.Info("info message") + assert.Len(t, mock.infoCalls, 1, "Should have one info call") + assert.Equal(t, "info message", mock.infoCalls[0].msg) + assert.Empty(t, mock.infoCalls[0].args) + + logger.Info("info with args", "status", "ok") + assert.Len(t, mock.infoCalls, 2, "Should have two info calls") + assert.Equal(t, "info with args", mock.infoCalls[1].msg) + assert.Equal(t, []any{"status", "ok"}, mock.infoCalls[1].args) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogInfoContext(t *testing.T) { + mock := newMockLogger() + logger = mock + ctx := context.WithValue(context.Background(), "requestID", "12345") + + logger.InfoContext(ctx, "info with context") + assert.Len(t, mock.infoContextCalls, 1, "Should have one info context call") + assert.Equal(t, "info with context", mock.infoContextCalls[0].msg) + assert.Equal(t, ctx, mock.infoContextCalls[0].ctx) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogWarn(t *testing.T) { + mock := newMockLogger() + logger = mock + + logger.Warn("warning message") + assert.Len(t, mock.warnCalls, 1, "Should have one warn call") + assert.Equal(t, "warning message", mock.warnCalls[0].msg) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogWarnContext(t *testing.T) { + mock := newMockLogger() + logger = mock + ctx := context.Background() + + logger.WarnContext(ctx, "warning with context", "severity", "medium") + assert.Len(t, mock.warnContextCalls, 1, "Should have one warn context call") + assert.Equal(t, "warning with context", mock.warnContextCalls[0].msg) + assert.Equal(t, []any{"severity", "medium"}, mock.warnContextCalls[0].args) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogError(t *testing.T) { + mock := newMockLogger() + logger = mock + + logger.Error("error message") + assert.Len(t, mock.errorCalls, 1, "Should have one error call") + assert.Equal(t, "error message", mock.errorCalls[0].msg) + + logger.Error("error with details", "code", 500, "err", "internal error") + assert.Len(t, mock.errorCalls, 2, "Should have two error calls") + assert.Equal(t, []any{"code", 500, "err", "internal error"}, mock.errorCalls[1].args) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogErrorContext(t *testing.T) { + mock := newMockLogger() + logger = mock + ctx := context.Background() + + logger.ErrorContext(ctx, "error with context", "component", "api") + assert.Len(t, mock.errorContextCalls, 1, "Should have one error context call") + assert.Equal(t, "error with context", mock.errorContextCalls[0].msg) + assert.Equal(t, ctx, mock.errorContextCalls[0].ctx) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogFatal(t *testing.T) { + mock := newMockLogger() + logger = mock + + logger.Fatal("fatal error") + assert.Len(t, mock.fatalCalls, 1, "Should have one fatal call") + assert.Equal(t, "fatal error", mock.fatalCalls[0].msg) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogFatalContext(t *testing.T) { + mock := newMockLogger() + logger = mock + ctx := context.Background() + + logger.FatalContext(ctx, "fatal with context", "reason", "shutdown") + assert.Len(t, mock.fatalContextCalls, 1, "Should have one fatal context call") + assert.Equal(t, "fatal with context", mock.fatalContextCalls[0].msg) + assert.Equal(t, []any{"reason", "shutdown"}, mock.fatalContextCalls[0].args) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestSlogAllMethods(t *testing.T) { + mock := newMockLogger() + logger = mock + ctx := context.Background() + + // Test that all slog methods work without panicking + logger.Debug("debug") + logger.DebugContext(ctx, "debug context") + logger.Info("info") + logger.InfoContext(ctx, "info context") + logger.Warn("warn") + logger.WarnContext(ctx, "warn context") + logger.Error("error") + logger.ErrorContext(ctx, "error context") + logger.Fatal("fatal") + logger.FatalContext(ctx, "fatal context") + + // Verify all calls were recorded + assert.Len(t, mock.debugCalls, 1) + assert.Len(t, mock.debugContextCalls, 1) + assert.Len(t, mock.infoCalls, 1) + assert.Len(t, mock.infoContextCalls, 1) + assert.Len(t, mock.warnCalls, 1) + assert.Len(t, mock.warnContextCalls, 1) + assert.Len(t, mock.errorCalls, 1) + assert.Len(t, mock.errorContextCalls, 1) + assert.Len(t, mock.fatalCalls, 1) + assert.Len(t, mock.fatalContextCalls, 1) + + // Restore default logger + logger = NewGlogLogger() +} + +func TestWithRealGlogLoggerSlog(t *testing.T) { + // Initialize glog flags + flag.Set("logtostderr", "true") + flag.Set("v", "2") + + // Use real GlogLogger + logger = NewGlogLogger() + ctx := context.Background() + + // These should not panic + assert.NotPanics(t, func() { + logger.Debug("debug message") + logger.DebugContext(ctx, "debug with context") + logger.Info("info message") + logger.InfoContext(ctx, "info with context") + logger.Warn("warn message") + logger.WarnContext(ctx, "warn with context") + logger.Error("error message") + logger.ErrorContext(ctx, "error with context") + }, "Real GlogLogger slog methods should not panic") +} + +func TestLevelFatalConstant(t *testing.T) { + // Verify that LevelFatal is defined correctly + assert.Equal(t, LevelFatal, slog.LevelError+4, "LevelFatal should be slog.LevelError + 4") +}