mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-08 23:39:46 +00:00
f41c316b2b
* Add configuration wizard.
522 lines
13 KiB
Go
522 lines
13 KiB
Go
package logger
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestLoggerTextFormat(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
level Level
|
|
logLevel Level
|
|
message string
|
|
fields map[string]interface{}
|
|
expectOutput bool
|
|
expectContains []string
|
|
}{
|
|
{
|
|
name: "info logged at info level",
|
|
level: LevelInfo,
|
|
logLevel: LevelInfo,
|
|
message: "test message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectContains: []string{"[INFO]", "test message"},
|
|
},
|
|
{
|
|
name: "debug filtered at info level",
|
|
level: LevelInfo,
|
|
logLevel: LevelDebug,
|
|
message: "debug message",
|
|
fields: nil,
|
|
expectOutput: false,
|
|
expectContains: []string{},
|
|
},
|
|
{
|
|
name: "error logged at info level",
|
|
level: LevelInfo,
|
|
logLevel: LevelError,
|
|
message: "error message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectContains: []string{"[ERROR]", "error message"},
|
|
},
|
|
{
|
|
name: "info with fields",
|
|
level: LevelInfo,
|
|
logLevel: LevelInfo,
|
|
message: "test message",
|
|
fields: map[string]interface{}{
|
|
"key1": "value1",
|
|
"key2": 123,
|
|
},
|
|
expectOutput: true,
|
|
expectContains: []string{"[INFO]", "test message", "key1", "value1"},
|
|
},
|
|
{
|
|
name: "warn logged at warn level",
|
|
level: LevelWarn,
|
|
logLevel: LevelWarn,
|
|
message: "warning message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectContains: []string{"[WARN]", "warning message"},
|
|
},
|
|
{
|
|
name: "info filtered at warn level",
|
|
level: LevelWarn,
|
|
logLevel: LevelInfo,
|
|
message: "info message",
|
|
fields: nil,
|
|
expectOutput: false,
|
|
expectContains: []string{},
|
|
},
|
|
{
|
|
name: "debug logged at debug level",
|
|
level: LevelDebug,
|
|
logLevel: LevelDebug,
|
|
message: "debug message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectContains: []string{"[DEBUG]", "debug message"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := &bytes.Buffer{}
|
|
logger := New(tt.level, FormatText, buf)
|
|
|
|
// Log at the specified level
|
|
switch tt.logLevel {
|
|
case LevelDebug:
|
|
if tt.fields != nil {
|
|
logger.Debug(tt.message, tt.fields)
|
|
} else {
|
|
logger.Debug(tt.message)
|
|
}
|
|
case LevelInfo:
|
|
if tt.fields != nil {
|
|
logger.Info(tt.message, tt.fields)
|
|
} else {
|
|
logger.Info(tt.message)
|
|
}
|
|
case LevelWarn:
|
|
if tt.fields != nil {
|
|
logger.Warn(tt.message, tt.fields)
|
|
} else {
|
|
logger.Warn(tt.message)
|
|
}
|
|
case LevelError:
|
|
if tt.fields != nil {
|
|
logger.Error(tt.message, tt.fields)
|
|
} else {
|
|
logger.Error(tt.message)
|
|
}
|
|
}
|
|
|
|
output := buf.String()
|
|
|
|
if tt.expectOutput {
|
|
assert.NotEmpty(t, output, "Expected log output but got none")
|
|
for _, expected := range tt.expectContains {
|
|
assert.Contains(t, output, expected, "Expected output to contain: %s", expected)
|
|
}
|
|
} else {
|
|
assert.Empty(t, output, "Expected no log output but got: %s", output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoggerJSONFormat(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
level Level
|
|
logLevel Level
|
|
message string
|
|
fields map[string]interface{}
|
|
expectOutput bool
|
|
expectLevel string
|
|
}{
|
|
{
|
|
name: "info logged at info level",
|
|
level: LevelInfo,
|
|
logLevel: LevelInfo,
|
|
message: "test message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectLevel: "INFO",
|
|
},
|
|
{
|
|
name: "debug filtered at info level",
|
|
level: LevelInfo,
|
|
logLevel: LevelDebug,
|
|
message: "debug message",
|
|
fields: nil,
|
|
expectOutput: false,
|
|
expectLevel: "",
|
|
},
|
|
{
|
|
name: "error logged at debug level",
|
|
level: LevelDebug,
|
|
logLevel: LevelError,
|
|
message: "error message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectLevel: "ERROR",
|
|
},
|
|
{
|
|
name: "info with fields",
|
|
level: LevelInfo,
|
|
logLevel: LevelInfo,
|
|
message: "test message",
|
|
fields: map[string]interface{}{
|
|
"context": "production",
|
|
"port": 8080,
|
|
"retry": 3,
|
|
},
|
|
expectOutput: true,
|
|
expectLevel: "INFO",
|
|
},
|
|
{
|
|
name: "warn at warn level",
|
|
level: LevelWarn,
|
|
logLevel: LevelWarn,
|
|
message: "warning message",
|
|
fields: nil,
|
|
expectOutput: true,
|
|
expectLevel: "WARN",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := &bytes.Buffer{}
|
|
logger := New(tt.level, FormatJSON, buf)
|
|
|
|
// Log at the specified level
|
|
switch tt.logLevel {
|
|
case LevelDebug:
|
|
if tt.fields != nil {
|
|
logger.Debug(tt.message, tt.fields)
|
|
} else {
|
|
logger.Debug(tt.message)
|
|
}
|
|
case LevelInfo:
|
|
if tt.fields != nil {
|
|
logger.Info(tt.message, tt.fields)
|
|
} else {
|
|
logger.Info(tt.message)
|
|
}
|
|
case LevelWarn:
|
|
if tt.fields != nil {
|
|
logger.Warn(tt.message, tt.fields)
|
|
} else {
|
|
logger.Warn(tt.message)
|
|
}
|
|
case LevelError:
|
|
if tt.fields != nil {
|
|
logger.Error(tt.message, tt.fields)
|
|
} else {
|
|
logger.Error(tt.message)
|
|
}
|
|
}
|
|
|
|
output := buf.String()
|
|
|
|
if tt.expectOutput {
|
|
assert.NotEmpty(t, output, "Expected log output but got none")
|
|
|
|
// Parse JSON
|
|
var entry logEntry
|
|
err := json.Unmarshal([]byte(strings.TrimSpace(output)), &entry)
|
|
require.NoError(t, err, "Failed to parse JSON output: %s", output)
|
|
|
|
// Validate fields
|
|
assert.Equal(t, tt.expectLevel, entry.Level)
|
|
assert.Equal(t, tt.message, entry.Message)
|
|
assert.NotEmpty(t, entry.Time, "Time field should not be empty")
|
|
|
|
// Validate custom fields if provided
|
|
if tt.fields != nil {
|
|
require.NotNil(t, entry.Fields, "Expected fields in JSON output")
|
|
for key, expectedValue := range tt.fields {
|
|
actualValue, exists := entry.Fields[key]
|
|
assert.True(t, exists, "Expected field %s not found in output", key)
|
|
// JSON unmarshaling converts numbers to float64
|
|
if floatVal, ok := expectedValue.(int); ok {
|
|
assert.Equal(t, float64(floatVal), actualValue)
|
|
} else {
|
|
assert.Equal(t, expectedValue, actualValue)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
assert.Empty(t, output, "Expected no log output but got: %s", output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGlobalLogger(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
initLevel Level
|
|
initFormat Format
|
|
logFunc func(string, ...map[string]interface{})
|
|
message string
|
|
expectContains string
|
|
}{
|
|
{
|
|
name: "global info logger text",
|
|
initLevel: LevelInfo,
|
|
initFormat: FormatText,
|
|
logFunc: Info,
|
|
message: "global info message",
|
|
expectContains: "[INFO]",
|
|
},
|
|
{
|
|
name: "global error logger text",
|
|
initLevel: LevelInfo,
|
|
initFormat: FormatText,
|
|
logFunc: Error,
|
|
message: "global error message",
|
|
expectContains: "[ERROR]",
|
|
},
|
|
{
|
|
name: "global warn logger json",
|
|
initLevel: LevelWarn,
|
|
initFormat: FormatJSON,
|
|
logFunc: Warn,
|
|
message: "global warn message",
|
|
expectContains: `"level":"WARN"`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Capture stderr by replacing globalLogger's output
|
|
buf := &bytes.Buffer{}
|
|
Init(tt.initLevel, tt.initFormat)
|
|
globalLogger.output = buf
|
|
|
|
// Call the global log function
|
|
tt.logFunc(tt.message)
|
|
|
|
output := buf.String()
|
|
assert.Contains(t, output, tt.expectContains)
|
|
assert.Contains(t, output, tt.message)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLogLevelsFiltering(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
loggerLevel Level
|
|
logAtLevels []Level
|
|
expectOutputs []bool
|
|
}{
|
|
{
|
|
name: "debug level logs everything",
|
|
loggerLevel: LevelDebug,
|
|
logAtLevels: []Level{LevelDebug, LevelInfo, LevelWarn, LevelError},
|
|
expectOutputs: []bool{true, true, true, true},
|
|
},
|
|
{
|
|
name: "info level filters debug",
|
|
loggerLevel: LevelInfo,
|
|
logAtLevels: []Level{LevelDebug, LevelInfo, LevelWarn, LevelError},
|
|
expectOutputs: []bool{false, true, true, true},
|
|
},
|
|
{
|
|
name: "warn level filters debug and info",
|
|
loggerLevel: LevelWarn,
|
|
logAtLevels: []Level{LevelDebug, LevelInfo, LevelWarn, LevelError},
|
|
expectOutputs: []bool{false, false, true, true},
|
|
},
|
|
{
|
|
name: "error level only logs errors",
|
|
loggerLevel: LevelError,
|
|
logAtLevels: []Level{LevelDebug, LevelInfo, LevelWarn, LevelError},
|
|
expectOutputs: []bool{false, false, false, true},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := &bytes.Buffer{}
|
|
logger := New(tt.loggerLevel, FormatText, buf)
|
|
|
|
for i, logLevel := range tt.logAtLevels {
|
|
buf.Reset()
|
|
|
|
switch logLevel {
|
|
case LevelDebug:
|
|
logger.Debug("test")
|
|
case LevelInfo:
|
|
logger.Info("test")
|
|
case LevelWarn:
|
|
logger.Warn("test")
|
|
case LevelError:
|
|
logger.Error("test")
|
|
}
|
|
|
|
hasOutput := buf.Len() > 0
|
|
assert.Equal(t, tt.expectOutputs[i], hasOutput,
|
|
"Level %v at logger level %v: expected output=%v, got=%v",
|
|
logLevel, tt.loggerLevel, tt.expectOutputs[i], hasOutput)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoggerNilOutput(t *testing.T) {
|
|
// Test that logger defaults to os.Stderr when output is nil
|
|
logger := New(LevelInfo, FormatText, nil)
|
|
assert.NotNil(t, logger.output, "Logger output should not be nil")
|
|
}
|
|
|
|
func TestLevelToString(t *testing.T) {
|
|
tests := []struct {
|
|
level Level
|
|
expected string
|
|
}{
|
|
{LevelDebug, "DEBUG"},
|
|
{LevelInfo, "INFO"},
|
|
{LevelWarn, "WARN"},
|
|
{LevelError, "ERROR"},
|
|
{Level(999), "UNKNOWN"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.expected, func(t *testing.T) {
|
|
result := levelToString(tt.level)
|
|
assert.Equal(t, tt.expected, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestJSONFieldTypes(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
fields map[string]interface{}
|
|
}{
|
|
{
|
|
name: "string fields",
|
|
fields: map[string]interface{}{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
},
|
|
},
|
|
{
|
|
name: "numeric fields",
|
|
fields: map[string]interface{}{
|
|
"port": 8080,
|
|
"timeout": 30,
|
|
"retry": 3,
|
|
},
|
|
},
|
|
{
|
|
name: "boolean fields",
|
|
fields: map[string]interface{}{
|
|
"enabled": true,
|
|
"running": false,
|
|
},
|
|
},
|
|
{
|
|
name: "mixed types",
|
|
fields: map[string]interface{}{
|
|
"context": "production",
|
|
"port": 8080,
|
|
"enabled": true,
|
|
"namespace": "default",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := &bytes.Buffer{}
|
|
logger := New(LevelInfo, FormatJSON, buf)
|
|
|
|
logger.Info("test message", tt.fields)
|
|
|
|
var entry logEntry
|
|
err := json.Unmarshal([]byte(strings.TrimSpace(buf.String())), &entry)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, len(tt.fields), len(entry.Fields),
|
|
"Field count mismatch")
|
|
|
|
for key := range tt.fields {
|
|
_, exists := entry.Fields[key]
|
|
assert.True(t, exists, "Field %s not found in JSON output", key)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInitWithCustomOutput(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
output io.Writer
|
|
expectDiscard bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "init with custom buffer",
|
|
output: &bytes.Buffer{},
|
|
expectDiscard: false,
|
|
description: "Should use provided buffer",
|
|
},
|
|
{
|
|
name: "init with io.Discard",
|
|
output: io.Discard,
|
|
expectDiscard: true,
|
|
description: "Should use io.Discard to silence output",
|
|
},
|
|
{
|
|
name: "init without output defaults to stderr",
|
|
output: nil,
|
|
expectDiscard: false,
|
|
description: "Should default to stderr when no output provided",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.output != nil {
|
|
Init(LevelInfo, FormatText, tt.output)
|
|
} else {
|
|
Init(LevelInfo, FormatText)
|
|
}
|
|
|
|
// Verify global logger was initialized
|
|
assert.NotNil(t, globalLogger, "Global logger should be initialized")
|
|
|
|
if tt.output != nil && !tt.expectDiscard {
|
|
// For buffer, verify output works
|
|
if buf, ok := tt.output.(*bytes.Buffer); ok {
|
|
Info("test message")
|
|
output := buf.String()
|
|
assert.Contains(t, output, "test message")
|
|
assert.Contains(t, output, "[INFO]")
|
|
}
|
|
} else if tt.expectDiscard {
|
|
// For io.Discard, verify no output appears (we can't really test this directly,
|
|
// but we can verify the logger was set with the right output)
|
|
assert.Equal(t, io.Discard, globalLogger.output)
|
|
}
|
|
})
|
|
}
|
|
}
|