forked from aegis/pyserveX
feat: Add reverse proxy functionality with enhanced routing capabilities
- Introduced IgnoreRequestPath option in proxy configuration to allow exact match routing. - Implemented proxy_pass directive in routing extension to handle backend requests. - Enhanced error handling for backend unavailability and timeouts. - Added integration tests for reverse proxy, including basic requests, exact match routes, regex routes, header forwarding, and query string preservation. - Created helper functions for setting up test servers and backends, along with assertion utilities for response validation. - Updated server initialization to support extension management and middleware chaining. - Improved logging for debugging purposes during request handling.
This commit is contained in:
+287
-85
@@ -1,136 +1,338 @@
|
||||
// Package logging provides structured logging with zap
|
||||
package logging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
"github.com/konduktor/konduktor/internal/config"
|
||||
)
|
||||
|
||||
// Config is a simple configuration for basic logger setup
|
||||
type Config struct {
|
||||
Level string
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
// Logger wraps zap.SugaredLogger with additional functionality
|
||||
type Logger struct {
|
||||
level string
|
||||
timestampFormat string
|
||||
configFull *config.LoggingConfig
|
||||
*zap.SugaredLogger
|
||||
zap *zap.Logger
|
||||
config *config.LoggingConfig
|
||||
name string
|
||||
}
|
||||
|
||||
// New creates a new Logger with basic configuration
|
||||
func New(cfg Config) (*Logger, error) {
|
||||
level := parseLevel(cfg.Level)
|
||||
|
||||
timestampFormat := cfg.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = "2006-01-02 15:04:05"
|
||||
}
|
||||
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.TimeEncoderOfLayout(timestampFormat)
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
|
||||
core := zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(encoderConfig),
|
||||
zapcore.AddSync(os.Stdout),
|
||||
level,
|
||||
)
|
||||
|
||||
zapLogger := zap.New(core)
|
||||
return &Logger{
|
||||
level: cfg.Level,
|
||||
timestampFormat: timestampFormat,
|
||||
SugaredLogger: zapLogger.Sugar(),
|
||||
zap: zapLogger,
|
||||
name: "konduktor",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewFromConfig creates a Logger from full LoggingConfig
|
||||
func NewFromConfig(cfg config.LoggingConfig) (*Logger, error) {
|
||||
timestampFormat := cfg.Format.TimestampFormat
|
||||
var cores []zapcore.Core
|
||||
|
||||
// Parse main level
|
||||
mainLevel := parseLevel(cfg.Level)
|
||||
|
||||
// Add console core if enabled
|
||||
if cfg.ConsoleOutput {
|
||||
consoleLevel := mainLevel
|
||||
if cfg.Console != nil && cfg.Console.Level != "" {
|
||||
consoleLevel = parseLevel(cfg.Console.Level)
|
||||
}
|
||||
|
||||
var consoleEncoder zapcore.Encoder
|
||||
formatConfig := cfg.Format
|
||||
if cfg.Console != nil {
|
||||
formatConfig = mergeFormatConfig(cfg.Format, cfg.Console.Format)
|
||||
}
|
||||
|
||||
encoderCfg := createEncoderConfig(formatConfig)
|
||||
if formatConfig.Type == "json" {
|
||||
consoleEncoder = zapcore.NewJSONEncoder(encoderCfg)
|
||||
} else {
|
||||
if formatConfig.UseColors {
|
||||
encoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
}
|
||||
consoleEncoder = zapcore.NewConsoleEncoder(encoderCfg)
|
||||
}
|
||||
|
||||
consoleSyncer := zapcore.AddSync(os.Stdout)
|
||||
cores = append(cores, zapcore.NewCore(consoleEncoder, consoleSyncer, consoleLevel))
|
||||
}
|
||||
|
||||
// Add file cores
|
||||
for _, fileConfig := range cfg.Files {
|
||||
fileCore, err := createFileCore(fileConfig, cfg.Format, mainLevel)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create file logger for %s: %w", fileConfig.Path, err)
|
||||
}
|
||||
|
||||
// If specific loggers are configured, wrap with filter
|
||||
if len(fileConfig.Loggers) > 0 {
|
||||
fileCore = &filteredCore{
|
||||
Core: fileCore,
|
||||
loggers: fileConfig.Loggers,
|
||||
}
|
||||
}
|
||||
|
||||
cores = append(cores, fileCore)
|
||||
}
|
||||
|
||||
// If no cores configured, add default console
|
||||
if len(cores) == 0 {
|
||||
encoderCfg := zap.NewProductionEncoderConfig()
|
||||
encoderCfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
|
||||
encoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
cores = append(cores, zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(encoderCfg),
|
||||
zapcore.AddSync(os.Stdout),
|
||||
mainLevel,
|
||||
))
|
||||
}
|
||||
|
||||
// Combine all cores
|
||||
core := zapcore.NewTee(cores...)
|
||||
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
|
||||
return &Logger{
|
||||
SugaredLogger: zapLogger.Sugar(),
|
||||
zap: zapLogger,
|
||||
config: &cfg,
|
||||
name: "konduktor",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Named returns a logger with a specific name (for filtering)
|
||||
func (l *Logger) Named(name string) *Logger {
|
||||
return &Logger{
|
||||
SugaredLogger: l.SugaredLogger.Named(name),
|
||||
zap: l.zap.Named(name),
|
||||
config: l.config,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// With returns a logger with additional fields
|
||||
func (l *Logger) With(args ...interface{}) *Logger {
|
||||
return &Logger{
|
||||
SugaredLogger: l.SugaredLogger.With(args...),
|
||||
zap: l.zap.Sugar().With(args...).Desugar(),
|
||||
config: l.config,
|
||||
name: l.name,
|
||||
}
|
||||
}
|
||||
|
||||
// Sync flushes any buffered log entries
|
||||
func (l *Logger) Sync() error {
|
||||
return l.zap.Sync()
|
||||
}
|
||||
|
||||
// GetZap returns the underlying zap.Logger
|
||||
func (l *Logger) GetZap() *zap.Logger {
|
||||
return l.zap
|
||||
}
|
||||
|
||||
// Debug logs a debug message
|
||||
func (l *Logger) Debug(msg string, keysAndValues ...interface{}) {
|
||||
l.SugaredLogger.Debugw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// Info logs an info message
|
||||
func (l *Logger) Info(msg string, keysAndValues ...interface{}) {
|
||||
l.SugaredLogger.Infow(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// Warn logs a warning message
|
||||
func (l *Logger) Warn(msg string, keysAndValues ...interface{}) {
|
||||
l.SugaredLogger.Warnw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// Error logs an error message
|
||||
func (l *Logger) Error(msg string, keysAndValues ...interface{}) {
|
||||
l.SugaredLogger.Errorw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// Fatal logs a fatal message and exits
|
||||
func (l *Logger) Fatal(msg string, keysAndValues ...interface{}) {
|
||||
l.SugaredLogger.Fatalw(msg, keysAndValues...)
|
||||
}
|
||||
|
||||
// --- Helper functions ---
|
||||
|
||||
func parseLevel(level string) zapcore.Level {
|
||||
switch strings.ToUpper(level) {
|
||||
case "DEBUG":
|
||||
return zapcore.DebugLevel
|
||||
case "INFO":
|
||||
return zapcore.InfoLevel
|
||||
case "WARN", "WARNING":
|
||||
return zapcore.WarnLevel
|
||||
case "ERROR":
|
||||
return zapcore.ErrorLevel
|
||||
case "CRITICAL", "FATAL":
|
||||
return zapcore.FatalLevel
|
||||
default:
|
||||
return zapcore.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
func createEncoderConfig(format config.LogFormatConfig) zapcore.EncoderConfig {
|
||||
timestampFormat := format.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = "2006-01-02 15:04:05"
|
||||
}
|
||||
|
||||
return &Logger{
|
||||
level: cfg.Level,
|
||||
timestampFormat: timestampFormat,
|
||||
configFull: &cfg,
|
||||
}, nil
|
||||
cfg := zapcore.EncoderConfig{
|
||||
TimeKey: "timestamp",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
FunctionKey: zapcore.OmitKey,
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||
EncodeTime: zapcore.TimeEncoderOfLayout(timestampFormat),
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
if !format.ShowModule {
|
||||
cfg.NameKey = zapcore.OmitKey
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func (l *Logger) formatTime() string {
|
||||
return time.Now().Format(l.timestampFormat)
|
||||
func mergeFormatConfig(base, override config.LogFormatConfig) config.LogFormatConfig {
|
||||
result := base
|
||||
if override.Type != "" {
|
||||
result.Type = override.Type
|
||||
}
|
||||
if override.TimestampFormat != "" {
|
||||
result.TimestampFormat = override.TimestampFormat
|
||||
}
|
||||
// UseColors and ShowModule are bool - check if override has non-default
|
||||
result.UseColors = override.UseColors
|
||||
result.ShowModule = override.ShowModule
|
||||
return result
|
||||
}
|
||||
|
||||
func (l *Logger) log(level string, msg string, fields ...interface{}) {
|
||||
timestamp := l.formatTime()
|
||||
|
||||
// Simple console output for now
|
||||
// TODO: Implement proper structured logging with zap
|
||||
output := timestamp + " [" + level + "] " + msg
|
||||
|
||||
if len(fields) > 0 {
|
||||
output += " {"
|
||||
for i := 0; i < len(fields); i += 2 {
|
||||
if i > 0 {
|
||||
output += ", "
|
||||
}
|
||||
if i+1 < len(fields) {
|
||||
output += fields[i].(string) + "=" + formatValue(fields[i+1])
|
||||
}
|
||||
func createFileCore(fileConfig config.FileLogConfig, defaultFormat config.LogFormatConfig, defaultLevel zapcore.Level) (zapcore.Core, error) {
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(fileConfig.Path)
|
||||
if dir != "" && dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return nil, fmt.Errorf("failed to create log directory %s: %w", dir, err)
|
||||
}
|
||||
output += "}"
|
||||
}
|
||||
|
||||
os.Stdout.WriteString(output + "\n")
|
||||
// Configure log rotation with lumberjack
|
||||
maxSize := 10 // MB
|
||||
if fileConfig.MaxBytes > 0 {
|
||||
maxSize = int(fileConfig.MaxBytes / (1024 * 1024))
|
||||
if maxSize < 1 {
|
||||
maxSize = 1
|
||||
}
|
||||
}
|
||||
|
||||
backupCount := 5
|
||||
if fileConfig.BackupCount > 0 {
|
||||
backupCount = fileConfig.BackupCount
|
||||
}
|
||||
|
||||
rotator := &lumberjack.Logger{
|
||||
Filename: fileConfig.Path,
|
||||
MaxSize: maxSize,
|
||||
MaxBackups: backupCount,
|
||||
MaxAge: 30, // days
|
||||
Compress: true,
|
||||
}
|
||||
|
||||
// Determine level
|
||||
level := defaultLevel
|
||||
if fileConfig.Level != "" {
|
||||
level = parseLevel(fileConfig.Level)
|
||||
}
|
||||
|
||||
// Create encoder
|
||||
format := defaultFormat
|
||||
if fileConfig.Format.Type != "" {
|
||||
format = mergeFormatConfig(defaultFormat, fileConfig.Format)
|
||||
}
|
||||
// Files should not use colors
|
||||
format.UseColors = false
|
||||
|
||||
encoderConfig := createEncoderConfig(format)
|
||||
var encoder zapcore.Encoder
|
||||
if format.Type == "json" {
|
||||
encoder = zapcore.NewJSONEncoder(encoderConfig)
|
||||
} else {
|
||||
encoder = zapcore.NewConsoleEncoder(encoderConfig)
|
||||
}
|
||||
|
||||
return zapcore.NewCore(encoder, zapcore.AddSync(rotator), level), nil
|
||||
}
|
||||
|
||||
func formatValue(v interface{}) string {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
return val
|
||||
case int:
|
||||
return fmt.Sprintf("%d", val)
|
||||
case int64:
|
||||
return fmt.Sprintf("%d", val)
|
||||
case float64:
|
||||
return fmt.Sprintf("%.2f", val)
|
||||
case bool:
|
||||
return fmt.Sprintf("%t", val)
|
||||
case error:
|
||||
return val.Error()
|
||||
default:
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
// filteredCore wraps a Core to filter by logger name
|
||||
type filteredCore struct {
|
||||
zapcore.Core
|
||||
loggers []string
|
||||
}
|
||||
|
||||
func (l *Logger) Debug(msg string, fields ...interface{}) {
|
||||
if l.shouldLog("DEBUG") {
|
||||
l.log("DEBUG", msg, fields...)
|
||||
func (c *filteredCore) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||
if !c.shouldLog(entry.LoggerName) {
|
||||
return ce
|
||||
}
|
||||
return c.Core.Check(entry, ce)
|
||||
}
|
||||
|
||||
func (l *Logger) Info(msg string, fields ...interface{}) {
|
||||
if l.shouldLog("INFO") {
|
||||
l.log("INFO", msg, fields...)
|
||||
func (c *filteredCore) shouldLog(loggerName string) bool {
|
||||
if len(c.loggers) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, allowed := range c.loggers {
|
||||
if loggerName == allowed || strings.HasPrefix(loggerName, allowed+".") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *Logger) Warn(msg string, fields ...interface{}) {
|
||||
if l.shouldLog("WARN") {
|
||||
l.log("WARN", msg, fields...)
|
||||
func (c *filteredCore) With(fields []zapcore.Field) zapcore.Core {
|
||||
return &filteredCore{
|
||||
Core: c.Core.With(fields),
|
||||
loggers: c.loggers,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Error(msg string, fields ...interface{}) {
|
||||
if l.shouldLog("ERROR") {
|
||||
l.log("ERROR", msg, fields...)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) shouldLog(level string) bool {
|
||||
levels := map[string]int{
|
||||
"DEBUG": 0,
|
||||
"INFO": 1,
|
||||
"WARN": 2,
|
||||
"ERROR": 3,
|
||||
}
|
||||
|
||||
currentLevel, ok := levels[l.level]
|
||||
if !ok {
|
||||
currentLevel = 1 // Default to INFO
|
||||
}
|
||||
|
||||
msgLevel, ok := levels[level]
|
||||
if !ok {
|
||||
msgLevel = 1
|
||||
}
|
||||
|
||||
return msgLevel >= currentLevel
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package logging
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/konduktor/konduktor/internal/config"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
@@ -15,71 +17,84 @@ func TestNew(t *testing.T) {
|
||||
t.Fatal("Expected logger, got nil")
|
||||
}
|
||||
|
||||
if logger.level != "INFO" {
|
||||
t.Errorf("Expected level INFO, got %s", logger.level)
|
||||
if logger.name != "konduktor" {
|
||||
t.Errorf("Expected name konduktor, got %s", logger.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_DefaultTimestampFormat(t *testing.T) {
|
||||
logger, _ := New(Config{Level: "DEBUG"})
|
||||
logger, err := New(Config{Level: "DEBUG"})
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if logger.timestampFormat != "2006-01-02 15:04:05" {
|
||||
t.Errorf("Expected default timestamp format, got %s", logger.timestampFormat)
|
||||
// Logger should be created successfully
|
||||
if logger == nil {
|
||||
t.Fatal("Expected logger, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNew_CustomTimestampFormat(t *testing.T) {
|
||||
logger, _ := New(Config{
|
||||
logger, err := New(Config{
|
||||
Level: "DEBUG",
|
||||
TimestampFormat: "15:04:05",
|
||||
})
|
||||
|
||||
if logger.timestampFormat != "15:04:05" {
|
||||
t.Errorf("Expected custom timestamp format, got %s", logger.timestampFormat)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if logger == nil {
|
||||
t.Fatal("Expected logger, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_ShouldLog(t *testing.T) {
|
||||
tests := []struct {
|
||||
loggerLevel string
|
||||
msgLevel string
|
||||
shouldLog bool
|
||||
}{
|
||||
{"DEBUG", "DEBUG", true},
|
||||
{"DEBUG", "INFO", true},
|
||||
{"DEBUG", "WARN", true},
|
||||
{"DEBUG", "ERROR", true},
|
||||
{"INFO", "DEBUG", false},
|
||||
{"INFO", "INFO", true},
|
||||
{"INFO", "WARN", true},
|
||||
{"INFO", "ERROR", true},
|
||||
{"WARN", "DEBUG", false},
|
||||
{"WARN", "INFO", false},
|
||||
{"WARN", "WARN", true},
|
||||
{"WARN", "ERROR", true},
|
||||
{"ERROR", "DEBUG", false},
|
||||
{"ERROR", "INFO", false},
|
||||
{"ERROR", "WARN", false},
|
||||
{"ERROR", "ERROR", true},
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
cfg := config.LoggingConfig{
|
||||
Level: "DEBUG",
|
||||
ConsoleOutput: true,
|
||||
Format: config.LogFormatConfig{
|
||||
Type: "standard",
|
||||
UseColors: true,
|
||||
ShowModule: true,
|
||||
TimestampFormat: "2006-01-02 15:04:05",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.loggerLevel+"_"+tt.msgLevel, func(t *testing.T) {
|
||||
logger, _ := New(Config{Level: tt.loggerLevel})
|
||||
logger, err := NewFromConfig(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if got := logger.shouldLog(tt.msgLevel); got != tt.shouldLog {
|
||||
t.Errorf("shouldLog(%s) = %v, want %v", tt.msgLevel, got, tt.shouldLog)
|
||||
}
|
||||
})
|
||||
if logger == nil {
|
||||
t.Fatal("Expected logger, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_ShouldLog_InvalidLevel(t *testing.T) {
|
||||
logger, _ := New(Config{Level: "INVALID"})
|
||||
func TestNewFromConfig_WithConsole(t *testing.T) {
|
||||
cfg := config.LoggingConfig{
|
||||
Level: "INFO",
|
||||
ConsoleOutput: true,
|
||||
Format: config.LogFormatConfig{
|
||||
Type: "standard",
|
||||
UseColors: true,
|
||||
},
|
||||
Console: &config.ConsoleLogConfig{
|
||||
Level: "DEBUG",
|
||||
Format: config.LogFormatConfig{
|
||||
Type: "standard",
|
||||
UseColors: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Should default to INFO level
|
||||
if !logger.shouldLog("INFO") {
|
||||
t.Error("Invalid level should default to INFO")
|
||||
logger, err := NewFromConfig(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if logger == nil {
|
||||
t.Fatal("Expected logger, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,34 +126,65 @@ func TestLogger_Error(t *testing.T) {
|
||||
logger.Error("test message", "key", "value")
|
||||
}
|
||||
|
||||
func TestFormatValue(t *testing.T) {
|
||||
func TestLogger_Named(t *testing.T) {
|
||||
logger, _ := New(Config{Level: "INFO"})
|
||||
named := logger.Named("test.module")
|
||||
|
||||
if named == nil {
|
||||
t.Fatal("Expected named logger, got nil")
|
||||
}
|
||||
|
||||
if named.name != "test.module" {
|
||||
t.Errorf("Expected name 'test.module', got %s", named.name)
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
named.Info("test from named logger")
|
||||
}
|
||||
|
||||
func TestLogger_With(t *testing.T) {
|
||||
logger, _ := New(Config{Level: "INFO"})
|
||||
withFields := logger.With("service", "test")
|
||||
|
||||
if withFields == nil {
|
||||
t.Fatal("Expected logger with fields, got nil")
|
||||
}
|
||||
|
||||
// Should not panic
|
||||
withFields.Info("test with fields")
|
||||
}
|
||||
|
||||
func TestLogger_Sync(t *testing.T) {
|
||||
logger, _ := New(Config{Level: "INFO"})
|
||||
|
||||
// Should not panic
|
||||
err := logger.Sync()
|
||||
// Sync may return an error for stdout on some systems, ignore it
|
||||
_ = err
|
||||
}
|
||||
|
||||
func TestParseLevel(t *testing.T) {
|
||||
tests := []struct {
|
||||
input interface{}
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"test", "test"},
|
||||
{42, "*"}, // int converts to rune
|
||||
{nil, ""},
|
||||
{"DEBUG", "debug"},
|
||||
{"INFO", "info"},
|
||||
{"WARN", "warn"},
|
||||
{"WARNING", "warn"},
|
||||
{"ERROR", "error"},
|
||||
{"CRITICAL", "fatal"},
|
||||
{"FATAL", "fatal"},
|
||||
{"invalid", "info"}, // defaults to INFO
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got := formatValue(tt.input)
|
||||
// Just check it doesn't panic
|
||||
_ = got
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_FormatTime(t *testing.T) {
|
||||
logger, _ := New(Config{
|
||||
Level: "INFO",
|
||||
TimestampFormat: "2006-01-02",
|
||||
})
|
||||
|
||||
result := logger.formatTime()
|
||||
|
||||
// Should be in expected format (YYYY-MM-DD)
|
||||
if len(result) != 10 {
|
||||
t.Errorf("Expected date format YYYY-MM-DD, got %s", result)
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
level := parseLevel(tt.input)
|
||||
if level.String() != tt.expected {
|
||||
t.Errorf("parseLevel(%s) = %s, want %s", tt.input, level.String(), tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,12 +207,3 @@ func BenchmarkLogger_Debug_Filtered(b *testing.B) {
|
||||
logger.Debug("test message", "key", "value")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLogger_ShouldLog(b *testing.B) {
|
||||
logger, _ := New(Config{Level: "INFO"})
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
logger.shouldLog("DEBUG")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user