398 lines
9.6 KiB
Go
398 lines
9.6 KiB
Go
package logging
|
|
|
|
import (
|
|
"bytes"
|
|
"log/slog"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// captureLogOutput runs a function and captures slog output
|
|
func captureLogOutput(level string, logFunc func()) string {
|
|
var buf bytes.Buffer
|
|
|
|
// Save original logger
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
// Parse level
|
|
var logLevel slog.Level
|
|
switch strings.ToLower(strings.TrimSpace(level)) {
|
|
case "debug":
|
|
logLevel = slog.LevelDebug
|
|
case "info", "":
|
|
logLevel = slog.LevelInfo
|
|
case "warn", "warning":
|
|
logLevel = slog.LevelWarn
|
|
case "error":
|
|
logLevel = slog.LevelError
|
|
default:
|
|
logLevel = slog.LevelInfo
|
|
}
|
|
|
|
// Create logger that writes to buffer
|
|
opts := &slog.HandlerOptions{
|
|
Level: logLevel,
|
|
}
|
|
handler := slog.NewTextHandler(&buf, opts)
|
|
slog.SetDefault(slog.New(handler))
|
|
|
|
// Run the function that generates logs
|
|
logFunc()
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
func TestInitLogger(t *testing.T) {
|
|
// Save original logger to restore after all tests
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
tests := []struct {
|
|
name string
|
|
level string
|
|
shouldLogDebug bool
|
|
shouldLogInfo bool
|
|
shouldLogWarn bool
|
|
shouldLogError bool
|
|
}{
|
|
{
|
|
name: "debug level logs all",
|
|
level: "debug",
|
|
shouldLogDebug: true,
|
|
shouldLogInfo: true,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "info level logs info and above",
|
|
level: "info",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: true,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "warn level logs warn and above",
|
|
level: "warn",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: false,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "error level logs only errors",
|
|
level: "error",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: false,
|
|
shouldLogWarn: false,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "empty level defaults to info",
|
|
level: "",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: true,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "invalid level defaults to info",
|
|
level: "invalid",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: true,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "case insensitive - DEBUG",
|
|
level: "DEBUG",
|
|
shouldLogDebug: true,
|
|
shouldLogInfo: true,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "case insensitive - WaRn",
|
|
level: "WaRn",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: false,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "whitespace handling - ' info '",
|
|
level: " info ",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: true,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
{
|
|
name: "warning alias for warn",
|
|
level: "warning",
|
|
shouldLogDebug: false,
|
|
shouldLogInfo: false,
|
|
shouldLogWarn: true,
|
|
shouldLogError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
output := captureLogOutput(tt.level, func() {
|
|
slog.Debug("debug message")
|
|
slog.Info("info message")
|
|
slog.Warn("warn message")
|
|
slog.Error("error message")
|
|
})
|
|
|
|
// Check debug
|
|
if tt.shouldLogDebug {
|
|
if !strings.Contains(output, "debug message") {
|
|
t.Errorf("Expected debug message to be logged")
|
|
}
|
|
} else {
|
|
if strings.Contains(output, "debug message") {
|
|
t.Errorf("Did not expect debug message to be logged")
|
|
}
|
|
}
|
|
|
|
// Check info
|
|
if tt.shouldLogInfo {
|
|
if !strings.Contains(output, "info message") {
|
|
t.Errorf("Expected info message to be logged")
|
|
}
|
|
} else {
|
|
if strings.Contains(output, "info message") {
|
|
t.Errorf("Did not expect info message to be logged")
|
|
}
|
|
}
|
|
|
|
// Check warn
|
|
if tt.shouldLogWarn {
|
|
if !strings.Contains(output, "warn message") {
|
|
t.Errorf("Expected warn message to be logged")
|
|
}
|
|
} else {
|
|
if strings.Contains(output, "warn message") {
|
|
t.Errorf("Did not expect warn message to be logged")
|
|
}
|
|
}
|
|
|
|
// Check error
|
|
if tt.shouldLogError {
|
|
if !strings.Contains(output, "error message") {
|
|
t.Errorf("Expected error message to be logged")
|
|
}
|
|
} else {
|
|
if strings.Contains(output, "error message") {
|
|
t.Errorf("Did not expect error message to be logged")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInitLogger_LogLevels(t *testing.T) {
|
|
// Save original logger
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
// Test that InitLogger actually calls slog.SetDefault
|
|
InitLogger("debug")
|
|
|
|
// Create a buffer to capture output
|
|
var buf bytes.Buffer
|
|
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{
|
|
Level: slog.LevelDebug,
|
|
})
|
|
slog.SetDefault(slog.New(handler))
|
|
|
|
// Log at debug level
|
|
slog.Debug("test debug message")
|
|
|
|
// Verify output contains the message
|
|
if !strings.Contains(buf.String(), "test debug message") {
|
|
t.Error("Debug message not logged after InitLogger")
|
|
}
|
|
}
|
|
|
|
func TestSetupTestLogger(t *testing.T) {
|
|
// Save original logger
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
// Test 1: SetupTestLogger suppresses INFO and DEBUG
|
|
cleanup := SetupTestLogger()
|
|
|
|
// Create a buffer to capture what SHOULD be discarded
|
|
// (but we can't really test io.Discard directly, so we'll test behavior)
|
|
|
|
// Log at different levels - since it's set to WARN, debug/info should be suppressed
|
|
// We can't capture io.Discard output, but we can verify the logger is configured correctly
|
|
logger := slog.Default()
|
|
|
|
// Verify handler is configured to discard
|
|
if logger == nil {
|
|
t.Error("Expected logger to be set")
|
|
}
|
|
|
|
// Test 2: Cleanup restores original logger
|
|
cleanup()
|
|
|
|
if slog.Default() != originalLogger {
|
|
t.Error("Expected cleanup to restore original logger")
|
|
}
|
|
}
|
|
|
|
func TestSetupTestLogger_LevelFiltering(t *testing.T) {
|
|
// Save original logger
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
// Setup test logger (WARN level, io.Discard)
|
|
cleanup := SetupTestLogger()
|
|
defer cleanup()
|
|
|
|
// Replace the handler output with a buffer so we can test
|
|
// (This is a bit of a workaround since the real SetupTestLogger uses io.Discard)
|
|
var buf bytes.Buffer
|
|
handler := slog.NewTextHandler(&buf, &slog.HandlerOptions{
|
|
Level: slog.LevelWarn,
|
|
})
|
|
slog.SetDefault(slog.New(handler))
|
|
|
|
// Log at different levels
|
|
slog.Debug("debug message")
|
|
slog.Info("info message")
|
|
slog.Warn("warn message")
|
|
slog.Error("error message")
|
|
|
|
output := buf.String()
|
|
|
|
// Debug and Info should NOT be in output (filtered by WARN level)
|
|
if strings.Contains(output, "debug message") {
|
|
t.Error("Debug message should be filtered out at WARN level")
|
|
}
|
|
if strings.Contains(output, "info message") {
|
|
t.Error("Info message should be filtered out at WARN level")
|
|
}
|
|
|
|
// Warn and Error SHOULD be in output
|
|
if !strings.Contains(output, "warn message") {
|
|
t.Error("Warn message should be logged at WARN level")
|
|
}
|
|
if !strings.Contains(output, "error message") {
|
|
t.Error("Error message should be logged at WARN level")
|
|
}
|
|
}
|
|
|
|
func TestSetupTestLogger_UsageWithTCleanup(t *testing.T) {
|
|
// This test demonstrates the intended usage pattern
|
|
originalLogger := slog.Default()
|
|
|
|
// Simulate using SetupTestLogger in a test
|
|
cleanup := SetupTestLogger()
|
|
t.Cleanup(cleanup)
|
|
|
|
// Logger should be different now
|
|
if slog.Default() == originalLogger {
|
|
t.Error("Expected logger to be changed after SetupTestLogger")
|
|
}
|
|
|
|
// When test ends, t.Cleanup will run and restore the logger
|
|
// We can't directly test this since it happens after the test function returns,
|
|
// but we're verifying the pattern works
|
|
}
|
|
|
|
func TestSetupTestLogger_MultipleCallsIndependent(t *testing.T) {
|
|
// Save original logger
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
// First call
|
|
cleanup1 := SetupTestLogger()
|
|
logger1 := slog.Default()
|
|
|
|
// Second call
|
|
cleanup2 := SetupTestLogger()
|
|
logger2 := slog.Default()
|
|
|
|
// Loggers might be different instances
|
|
if logger1 == nil || logger2 == nil {
|
|
t.Error("Expected loggers to be set")
|
|
}
|
|
|
|
// Cleanup in reverse order (like defer)
|
|
cleanup2()
|
|
cleanup1()
|
|
}
|
|
|
|
func TestInitLogger_OutputFormat(t *testing.T) {
|
|
// Save original logger
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
var buf bytes.Buffer
|
|
|
|
// Configure logger with buffer
|
|
opts := &slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
}
|
|
handler := slog.NewTextHandler(&buf, opts)
|
|
slog.SetDefault(slog.New(handler))
|
|
|
|
// Log a message
|
|
slog.Info("test message", "key", "value")
|
|
|
|
output := buf.String()
|
|
|
|
// Verify text format (not JSON)
|
|
if !strings.Contains(output, "test message") {
|
|
t.Error("Expected message in output")
|
|
}
|
|
if !strings.Contains(output, "key=value") {
|
|
t.Error("Expected key=value in text format")
|
|
}
|
|
// Should NOT be JSON
|
|
if strings.HasPrefix(output, "{") {
|
|
t.Error("Expected text format, not JSON")
|
|
}
|
|
}
|
|
|
|
func BenchmarkInitLogger(b *testing.B) {
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
InitLogger("info")
|
|
}
|
|
}
|
|
|
|
func BenchmarkSetupTestLogger(b *testing.B) {
|
|
originalLogger := slog.Default()
|
|
defer slog.SetDefault(originalLogger)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
cleanup := SetupTestLogger()
|
|
cleanup()
|
|
}
|
|
}
|
|
|
|
// Example test showing how to use SetupTestLogger
|
|
func ExampleSetupTestLogger() {
|
|
// In a test function:
|
|
cleanup := SetupTestLogger()
|
|
defer cleanup()
|
|
|
|
// Now logs at DEBUG and INFO are suppressed
|
|
slog.Debug("This won't show")
|
|
slog.Info("This won't show either")
|
|
slog.Warn("This WILL show")
|
|
|
|
// cleanup() will restore the original logger when defer runs
|
|
}
|