250 lines
5.3 KiB
Go
250 lines
5.3 KiB
Go
package logger
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"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
|
|
w.Close()
|
|
|
|
// 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")
|
|
}
|