Files
log/compat/compat_test.go

240 lines
6.8 KiB
Go

// FILE: lixenwraith/log/compat/compat_test.go
package compat
import (
"bufio"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/lixenwraith/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// createTestCompatBuilder creates a standard setup for compatibility adapter tests.
func createTestCompatBuilder(t *testing.T) (*Builder, *log.Logger, string) {
t.Helper()
tmpDir := t.TempDir()
appLogger, err := log.NewBuilder().
Directory(tmpDir).
Format("json").
LevelString("debug").
Build()
require.NoError(t, err)
// Start the logger before using it.
err = appLogger.Start()
require.NoError(t, err)
builder := NewBuilder().WithLogger(appLogger)
return builder, appLogger, tmpDir
}
// readLogFile reads a log file, retrying briefly to await async writes.
func readLogFile(t *testing.T, dir string, expectedLines int) []string {
t.Helper()
var err error
// Retry for a short period to handle logging delays.
for i := 0; i < 20; i++ {
var files []os.DirEntry
files, err = os.ReadDir(dir)
if err == nil && len(files) > 0 {
var logFile *os.File
logFilePath := filepath.Join(dir, files[0].Name())
logFile, err = os.Open(logFilePath)
if err == nil {
scanner := bufio.NewScanner(logFile)
var readLines []string
for scanner.Scan() {
readLines = append(readLines, scanner.Text())
}
logFile.Close()
if len(readLines) >= expectedLines {
return readLines
}
}
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("Failed to read %d log lines from directory %s. Last error: %v", expectedLines, dir, err)
return nil
}
func TestCompatBuilder(t *testing.T) {
t.Run("with existing logger", func(t *testing.T) {
builder, logger, _ := createTestCompatBuilder(t)
defer logger.Shutdown()
gnetAdapter, err := builder.BuildGnet()
require.NoError(t, err)
assert.NotNil(t, gnetAdapter)
assert.Equal(t, logger, gnetAdapter.logger)
})
t.Run("with config", func(t *testing.T) {
logCfg := log.DefaultConfig()
logCfg.Directory = t.TempDir()
builder := NewBuilder().WithConfig(logCfg)
fasthttpAdapter, err := builder.BuildFastHTTP()
require.NoError(t, err)
assert.NotNil(t, fasthttpAdapter)
logger1, _ := builder.GetLogger()
// The builder now creates AND starts the logger internally if needed.
// We need to defer shutdown to clean up resources.
defer logger1.Shutdown()
})
}
func TestGnetAdapter(t *testing.T) {
builder, logger, tmpDir := createTestCompatBuilder(t)
defer logger.Shutdown()
var fatalCalled bool
adapter, err := builder.BuildGnet(WithFatalHandler(func(msg string) {
fatalCalled = true
}))
require.NoError(t, err)
adapter.Debugf("gnet debug id=%d", 1)
adapter.Infof("gnet info id=%d", 2)
adapter.Warnf("gnet warn id=%d", 3)
adapter.Errorf("gnet error id=%d", 4)
adapter.Fatalf("gnet fatal id=%d", 5)
err = logger.Flush(time.Second)
require.NoError(t, err)
// The "Logger started" message is also logged, so we expect 6 lines.
lines := readLogFile(t, tmpDir, 6)
// Define expected log data. The order in the "fields" array is fixed by the adapter call.
expected := []struct{ level, msg string }{
{"DEBUG", "gnet debug id=1"},
{"INFO", "gnet info id=2"},
{"WARN", "gnet warn id=3"},
{"ERROR", "gnet error id=4"},
{"ERROR", "gnet fatal id=5"},
}
// Filter out the "Logger started" line
var logLines []string
for _, line := range lines {
if !strings.Contains(line, "Logger started") {
logLines = append(logLines, line)
}
}
require.Len(t, logLines, 5, "Should have 5 gnet log lines after filtering")
for i, line := range logLines {
var entry map[string]interface{}
err := json.Unmarshal([]byte(line), &entry)
require.NoError(t, err, "Failed to parse log line: %s", line)
assert.Equal(t, expected[i].level, entry["level"])
// The logger puts all arguments into a "fields" array.
// The adapter's calls look like: logger.Info("msg", msg, "source", "gnet")
fields := entry["fields"].([]interface{})
assert.Equal(t, "msg", fields[0])
assert.Equal(t, expected[i].msg, fields[1])
assert.Equal(t, "source", fields[2])
assert.Equal(t, "gnet", fields[3])
}
assert.True(t, fatalCalled, "Custom fatal handler should have been called")
}
func TestStructuredGnetAdapter(t *testing.T) {
builder, logger, tmpDir := createTestCompatBuilder(t)
defer logger.Shutdown()
adapter, err := builder.BuildStructuredGnet()
require.NoError(t, err)
adapter.Infof("request served status=%d client_ip=%s", 200, "127.0.0.1")
err = logger.Flush(time.Second)
require.NoError(t, err)
// The "Logger started" message is also logged, so we expect 2 lines.
lines := readLogFile(t, tmpDir, 2)
// Find our specific log line
var logLine string
for _, line := range lines {
if strings.Contains(line, "request served") {
logLine = line
break
}
}
require.NotEmpty(t, logLine, "Did not find the structured gnet log line")
var entry map[string]interface{}
err = json.Unmarshal([]byte(logLine), &entry)
require.NoError(t, err)
// The structured adapter parses keys and values, so we check them directly.
fields := entry["fields"].([]interface{})
assert.Equal(t, "INFO", entry["level"])
assert.Equal(t, "msg", fields[0])
assert.Equal(t, "request served", fields[1])
assert.Equal(t, "status", fields[2])
assert.Equal(t, 200.0, fields[3]) // JSON numbers are float64
assert.Equal(t, "client_ip", fields[4])
assert.Equal(t, "127.0.0.1", fields[5])
assert.Equal(t, "source", fields[6])
assert.Equal(t, "gnet", fields[7])
}
func TestFastHTTPAdapter(t *testing.T) {
builder, logger, tmpDir := createTestCompatBuilder(t)
defer logger.Shutdown()
adapter, err := builder.BuildFastHTTP()
require.NoError(t, err)
testMessages := []string{
"this is some informational message",
"a debug message for the developers",
"warning: something might be wrong",
"an error occurred while processing",
}
for _, msg := range testMessages {
adapter.Printf("%s", msg)
}
err = logger.Flush(time.Second)
require.NoError(t, err)
// Expect 4 test messages + 1 "Logger started" message
lines := readLogFile(t, tmpDir, 5)
expectedLevels := []string{"INFO", "DEBUG", "WARN", "ERROR"}
// Filter out the "Logger started" line
var logLines []string
for _, line := range lines {
if !strings.Contains(line, "Logger started") {
logLines = append(logLines, line)
}
}
require.Len(t, logLines, 4, "Should have 4 fasthttp log lines after filtering")
for i, line := range logLines {
var entry map[string]interface{}
err := json.Unmarshal([]byte(line), &entry)
require.NoError(t, err, "Failed to parse log line: %s", line)
assert.Equal(t, expectedLevels[i], entry["level"])
fields := entry["fields"].([]interface{})
assert.Equal(t, "msg", fields[0])
assert.Equal(t, testMessages[i], fields[1])
assert.Equal(t, "source", fields[2])
assert.Equal(t, "fasthttp", fields[3])
}
}