2025-06-05 21:21:28 +07:00

253 lines
5.4 KiB
Go

package logger
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"sync"
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// captureOutput captures log output for testing
func captureOutput(f func()) string {
r, w, err := os.Pipe()
if err != nil {
panic(err)
}
// Replace stdout/stderr
oldStdout := os.Stdout
oldStderr := os.Stderr
os.Stdout = w
os.Stderr = w
// Reset the logger after the test
oldLogger := log
defer func() {
log = oldLogger
os.Stdout = oldStdout
os.Stderr = oldStderr
}()
// Create a new logger for testing
log = logrus.New()
log.SetOutput(w)
log.SetFormatter(&logrus.JSONFormatter{
TimestampFormat: time.RFC3339Nano,
})
// Run the function
f()
// Close the writer
if err := w.Close(); err != nil {
panic(fmt.Sprintf("failed to close writer: %v", err))
}
// Read the output
var buf bytes.Buffer
_, err = io.Copy(&buf, r)
if err != nil {
panic(err)
}
return buf.String()
}
func TestLogger_Levels(t *testing.T) {
tests := []struct {
name string
setLevel string
logFunc func()
expected bool
}{
{
name: "debug level shows debug logs",
setLevel: "debug",
logFunc: func() { Debug("test debug") },
expected: true,
},
{
name: "info level hides debug logs",
setLevel: "info",
logFunc: func() { Debug("test debug") },
expected: false,
},
{
name: "error level shows error logs",
setLevel: "error",
logFunc: func() { Error("test error") },
expected: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
output := captureOutput(func() {
Init(&LogConfig{Level: tt.setLevel, Format: "json"})
tt.logFunc()
})
if tt.expected {
assert.Contains(t, output, "message")
} else {
assert.Empty(t, output)
}
})
}
}
func TestLogger_JSONOutput(t *testing.T) {
output := captureOutput(func() {
Init(&LogConfig{Level: "debug", Format: "json"})
Info("test message")
})
// Verify it's valid JSON
var data map[string]interface{}
err := json.Unmarshal([]byte(output), &data)
require.NoError(t, err)
// Check required fields
assert.Contains(t, data, "message")
assert.Contains(t, data, "level")
assert.Contains(t, data, "@timestamp")
}
func TestLogger_TextOutput(t *testing.T) {
output := captureOutput(func() {
Init(&LogConfig{Level: "info", Format: "text"})
Warn("test warning")
})
// Basic checks for text format
assert.Contains(t, output, "test warning")
assert.Contains(t, output, "level=warning")
}
func TestLogger_WithFields(t *testing.T) {
output := captureOutput(func() {
Init(&LogConfig{Level: "info", Format: "json"})
WithFields(Fields{
"key1": "value1",
"key2": 42,
}).Info("test fields")
})
var data map[string]interface{}
err := json.Unmarshal([]byte(output), &data)
require.NoError(t, err)
assert.Equal(t, "value1", data["key1"])
assert.Equal(t, float64(42), data["key2"])
}
func TestLogger_WithError(t *testing.T) {
err := io.EOF
output := captureOutput(func() {
Init(&LogConfig{Level: "error", Format: "json"})
WithError(err).Error("test error")
})
var data map[string]interface{}
jsonErr := json.Unmarshal([]byte(output), &data)
require.NoError(t, jsonErr)
assert.Contains(t, data["error"], "EOF")
}
func TestLogger_CallerInfo(t *testing.T) {
output := captureOutput(func() {
Init(&LogConfig{
Level: "info",
Format: "json",
EnableCaller: true,
})
Info("test caller")
})
var data map[string]interface{}
err := json.Unmarshal([]byte(output), &data)
require.NoError(t, err)
// Caller should be in the format "file:line"
caller, ok := data["caller"].(string)
require.True(t, ok)
assert.True(t, strings.Contains(caller, ".go:"), "caller should contain file and line number")
}
func TestLogger_Concurrent(t *testing.T) {
Init(&LogConfig{Level: "info", Format: "json"})
var wg sync.WaitGroup
count := 10
for i := 0; i < count; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
WithFields(Fields{"goroutine": n}).Info("concurrent log")
}(i)
}
// Just verify no panic occurs
wg.Wait()
}
func TestLogger_DefaultFields(t *testing.T) {
// Create a buffer to capture output
var buf bytes.Buffer
// Initialize logger with test configuration
Init(&LogConfig{
Level: "info",
Format: "json",
EnableCaller: true,
})
// Replace the output writer with our buffer
log.SetOutput(&buf)
// Log a test message
Info("test default fields")
// Parse the JSON output
var data map[string]interface{}
err := json.Unmarshal(buf.Bytes(), &data)
require.NoError(t, err, "Failed to unmarshal log output")
// Check that default fields are included
assert.Equal(t, "starter-kit", data["app_name"], "app_name should be set in default fields")
// The env field might be empty in test environment, which is fine
// as long as the field exists in the log entry
_, envExists := data["env"]
assert.True(t, envExists, "env field should exist in log entry")
}
func TestLogger_LevelChanges(t *testing.T) {
output := captureOutput(func() {
Init(&LogConfig{Level: "error", Format: "json"})
Debug("should not appear")
SetLevel("debug")
Debug("should appear")
})
// Split output into lines
lines := strings.Split(strings.TrimSpace(output), "\n")
// Should only have one log message (the second Debug)
assert.Len(t, lines, 1)
assert.Contains(t, lines[0], "should appear")
}