websocket-relay/internal/logging/logging_test.go
savinmax 3d14b7fcb8 feat(logging): add configurable log output and log level support
Add a 'logging' section to config.yaml supporting:
- output: stderr (default), stdout, or a file path
- level: debug, info, warn, error (default: info)

Implementation:
- New internal/logging package with Setup() for output destination
  and Logger struct with level-aware Debug/Info/Warn/Error methods
- Config struct extended with Logging section (output + level fields)
- Hub refactored to accept *logging.Logger via constructor injection
- main.go initializes logging early after config load

The leveled logger suppresses messages below the configured threshold
while maintaining the stdlib log format. File output uses append mode
with 0644 permissions for safe log rotation.

🤖 Assisted by the code-assist SOP
2026-06-11 19:21:20 +02:00

279 lines
6.3 KiB
Go

package logging
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
)
func TestSetupStdout(t *testing.T) {
file, err := Setup("stdout")
if err != nil {
t.Fatalf("Setup(stdout) failed: %v", err)
}
if file != nil {
t.Error("Expected nil file for stdout")
}
}
func TestSetupStderr(t *testing.T) {
file, err := Setup("stderr")
if err != nil {
t.Fatalf("Setup(stderr) failed: %v", err)
}
if file != nil {
t.Error("Expected nil file for stderr")
}
}
func TestSetupEmpty(t *testing.T) {
file, err := Setup("")
if err != nil {
t.Fatalf("Setup('') failed: %v", err)
}
if file != nil {
t.Error("Expected nil file for empty string")
}
}
func TestSetupFilePath(t *testing.T) {
tmpDir := t.TempDir()
logPath := filepath.Join(tmpDir, "test.log")
file, err := Setup(logPath)
if err != nil {
t.Fatalf("Setup(file) failed: %v", err)
}
if file == nil {
t.Fatal("Expected non-nil file for file path")
}
defer file.Close()
// Verify file was created
if _, err := os.Stat(logPath); os.IsNotExist(err) {
t.Error("Expected log file to be created")
}
}
func TestSetupInvalidPath(t *testing.T) {
_, err := Setup("/nonexistent/directory/path/test.log")
if err == nil {
t.Error("Expected error for invalid path")
}
}
func TestNewLoggerDefaultLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("", &buf)
if logger.level != LevelInfo {
t.Errorf("Expected default level Info, got %d", logger.level)
}
}
func TestNewLoggerInvalidLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("invalid", &buf)
if logger.level != LevelInfo {
t.Errorf("Expected default level Info for invalid input, got %d", logger.level)
}
}
func TestNewLoggerDebugLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("debug", &buf)
if logger.level != LevelDebug {
t.Errorf("Expected level Debug, got %d", logger.level)
}
}
func TestNewLoggerWarnLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("warn", &buf)
if logger.level != LevelWarn {
t.Errorf("Expected level Warn, got %d", logger.level)
}
}
func TestNewLoggerErrorLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("error", &buf)
if logger.level != LevelError {
t.Errorf("Expected level Error, got %d", logger.level)
}
}
func TestLoggerDebugOutputAtDebugLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("debug", &buf)
logger.Debug("test message")
output := buf.String()
if !strings.Contains(output, "[DEBUG]") {
t.Errorf("Expected [DEBUG] prefix, got: %s", output)
}
if !strings.Contains(output, "test message") {
t.Errorf("Expected 'test message' in output, got: %s", output)
}
}
func TestLoggerDebugSuppressedAtInfoLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("info", &buf)
logger.Debug("should not appear")
output := buf.String()
if output != "" {
t.Errorf("Expected no output for debug at info level, got: %s", output)
}
}
func TestLoggerInfoOutputAtInfoLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("info", &buf)
logger.Info("info message")
output := buf.String()
if !strings.Contains(output, "[INFO]") {
t.Errorf("Expected [INFO] prefix, got: %s", output)
}
if !strings.Contains(output, "info message") {
t.Errorf("Expected 'info message' in output, got: %s", output)
}
}
func TestLoggerInfoSuppressedAtWarnLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("warn", &buf)
logger.Info("should not appear")
output := buf.String()
if output != "" {
t.Errorf("Expected no output for info at warn level, got: %s", output)
}
}
func TestLoggerWarnOutputAtWarnLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("warn", &buf)
logger.Warn("warn message")
output := buf.String()
if !strings.Contains(output, "[WARN]") {
t.Errorf("Expected [WARN] prefix, got: %s", output)
}
if !strings.Contains(output, "warn message") {
t.Errorf("Expected 'warn message' in output, got: %s", output)
}
}
func TestLoggerWarnSuppressedAtErrorLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("error", &buf)
logger.Warn("should not appear")
output := buf.String()
if output != "" {
t.Errorf("Expected no output for warn at error level, got: %s", output)
}
}
func TestLoggerErrorOutputAtErrorLevel(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("error", &buf)
logger.Error("error message")
output := buf.String()
if !strings.Contains(output, "[ERROR]") {
t.Errorf("Expected [ERROR] prefix, got: %s", output)
}
if !strings.Contains(output, "error message") {
t.Errorf("Expected 'error message' in output, got: %s", output)
}
}
func TestLoggerErrorAlwaysOutputs(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("error", &buf)
logger.Error("critical failure")
output := buf.String()
if !strings.Contains(output, "critical failure") {
t.Errorf("Expected error message in output, got: %s", output)
}
}
func TestLoggerDebugLevelAllMessages(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("debug", &buf)
logger.Debug("d")
logger.Info("i")
logger.Warn("w")
logger.Error("e")
output := buf.String()
if !strings.Contains(output, "[DEBUG]") {
t.Error("Expected [DEBUG] in output")
}
if !strings.Contains(output, "[INFO]") {
t.Error("Expected [INFO] in output")
}
if !strings.Contains(output, "[WARN]") {
t.Error("Expected [WARN] in output")
}
if !strings.Contains(output, "[ERROR]") {
t.Error("Expected [ERROR] in output")
}
}
func TestLoggerFormatf(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("info", &buf)
logger.Infof("count: %d", 42)
output := buf.String()
if !strings.Contains(output, "count: 42") {
t.Errorf("Expected formatted output 'count: 42', got: %s", output)
}
}
func TestLoggerLevelCaseInsensitive(t *testing.T) {
var buf bytes.Buffer
logger := NewLogger("DEBUG", &buf)
if logger.level != LevelDebug {
t.Errorf("Expected level Debug for 'DEBUG', got %d", logger.level)
}
logger = NewLogger("Info", &buf)
if logger.level != LevelInfo {
t.Errorf("Expected level Info for 'Info', got %d", logger.level)
}
logger = NewLogger("WARN", &buf)
if logger.level != LevelWarn {
t.Errorf("Expected level Warn for 'WARN', got %d", logger.level)
}
logger = NewLogger("ERROR", &buf)
if logger.level != LevelError {
t.Errorf("Expected level Error for 'ERROR', got %d", logger.level)
}
}