v0.1.4 formatter race fix, fiber adapter added, default config changed, docs updated

This commit is contained in:
2025-11-17 16:33:08 -05:00
parent 4ed618abbb
commit 9b0a632b52
22 changed files with 797 additions and 56 deletions

View File

@ -98,6 +98,15 @@ func (b *Builder) BuildFastHTTP(opts ...FastHTTPOption) (*FastHTTPAdapter, error
return NewFastHTTPAdapter(l, opts...), nil
}
// BuildFiber creates a Fiber v2.54.x adapter
func (b *Builder) BuildFiber(opts ...FiberOption) (*FiberAdapter, error) {
l, err := b.getLogger()
if err != nil {
return nil, err
}
return NewFiberAdapter(l, opts...), nil
}
// GetLogger returns the underlying *log.Logger instance
// If a logger has not been provided or created yet, it will be initialized
func (b *Builder) GetLogger() (*log.Logger, error) {
@ -106,7 +115,7 @@ func (b *Builder) GetLogger() (*log.Logger, error) {
// --- Example Usage ---
//
// The following demonstrates how to integrate lixenwraith/log with gnet and fasthttp
// The following demonstrates how to integrate lixenwraith/log with gnet, fasthttp, and Fiber
// using a single, shared logger instance
//
// // 1. Create and configure application's main logger
@ -127,6 +136,9 @@ func (b *Builder) GetLogger() (*log.Logger, error) {
// fasthttpLogger, err := builder.BuildFastHTTP()
// if err != nil { /* handle error */ }
//
// fiberLogger, err := builder.BuildFiber()
// if err != nil { /* handle error */ }
//
// // 4. Configure your servers with the adapters
//
// // For gnet:
@ -142,4 +154,16 @@ func (b *Builder) GetLogger() (*log.Logger, error) {
// },
// Logger: fasthttpLogger,
// }
// go server.ListenAndServe(":8080")
// go server.ListenAndServe(":8080")
//
// // For Fiber v2.54.x:
// // The adapter is passed to fiber.New() via the config
// app := fiber.New(fiber.Config{
// AppName: "My Application",
// })
// app.UpdateConfig(fiber.Config{
// AppName: "My Application",
// })
// // Note: Set the logger after app creation if needed
// // fiber uses internal logging, adapter can be used in custom middleware
// go app.Listen(":3000")

View File

@ -22,6 +22,7 @@ func createTestCompatBuilder(t *testing.T) (*Builder, *log.Logger, string) {
Directory(tmpDir).
Format("json").
LevelString("debug").
EnableFile(true).
Build()
require.NoError(t, err)
@ -224,4 +225,126 @@ func TestFastHTTPAdapter(t *testing.T) {
assert.Equal(t, "source", fields[2])
assert.Equal(t, "fasthttp", fields[3])
}
}
// TestFiberAdapter tests the Fiber adapter's logging output across all log levels
func TestFiberAdapter(t *testing.T) {
builder, logger, tmpDir := createTestCompatBuilder(t)
defer logger.Shutdown()
var fatalCalled bool
var panicCalled bool
adapter, err := builder.BuildFiber(
WithFiberFatalHandler(func(msg string) {
fatalCalled = true
}),
WithFiberPanicHandler(func(msg string) {
panicCalled = true
}),
)
require.NoError(t, err)
// Test formatted logging (Tracef, Debugf, Infof, Warnf, Errorf, Fatalf, Panicf)
adapter.Tracef("fiber trace id=%d", 1)
adapter.Debugf("fiber debug id=%d", 2)
adapter.Infof("fiber info id=%d", 3)
adapter.Warnf("fiber warn id=%d", 4)
adapter.Errorf("fiber error id=%d", 5)
adapter.Fatalf("fiber fatal id=%d", 6)
adapter.Panicf("fiber panic id=%d", 7)
err = logger.Flush(time.Second)
require.NoError(t, err)
lines := readLogFile(t, tmpDir, 7)
expected := []struct {
level string
msg string
}{
{"DEBUG", "fiber trace id=1"},
{"DEBUG", "fiber debug id=2"},
{"INFO", "fiber info id=3"},
{"WARN", "fiber warn id=4"},
{"ERROR", "fiber error id=5"},
{"ERROR", "fiber fatal id=6"},
{"ERROR", "fiber panic id=7"},
}
require.Len(t, lines, 7, "Should have 7 fiber log lines")
for i, line := range lines {
var entry map[string]any
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"])
fields := entry["fields"].([]any)
assert.Equal(t, "msg", fields[0])
assert.Equal(t, expected[i].msg, fields[1])
assert.Equal(t, "source", fields[2])
assert.Equal(t, "fiber", fields[3])
}
assert.True(t, fatalCalled, "Custom fatal handler should have been called")
assert.True(t, panicCalled, "Custom panic handler should have been called")
}
// TestFiberAdapterStructuredLogging tests Fiber's structured logging (WithLogger methods)
func TestFiberAdapterStructuredLogging(t *testing.T) {
builder, logger, tmpDir := createTestCompatBuilder(t)
defer logger.Shutdown()
adapter, err := builder.BuildFiber()
require.NoError(t, err)
// Test structured logging with key-value pairs
adapter.Infow("request served", "status", 200, "client_ip", "127.0.0.1", "method", "GET")
adapter.Debugw("query executed", "duration_ms", 42, "query", "SELECT * FROM users")
err = logger.Flush(time.Second)
require.NoError(t, err)
lines := readLogFile(t, tmpDir, 2)
require.Len(t, lines, 2, "Should have 2 fiber structured log lines")
// Check first structured log (Infow)
var entry1 map[string]any
err = json.Unmarshal([]byte(lines[0]), &entry1)
require.NoError(t, err)
assert.Equal(t, "INFO", entry1["level"])
fields1 := entry1["fields"].([]any)
assert.Equal(t, "msg", fields1[0])
assert.Equal(t, "request served", fields1[1])
assert.Equal(t, "source", fields1[2])
assert.Equal(t, "fiber", fields1[3])
assert.Equal(t, "status", fields1[4])
assert.Equal(t, 200.0, fields1[5]) // JSON numbers are float64
assert.Equal(t, "client_ip", fields1[6])
assert.Equal(t, "127.0.0.1", fields1[7])
// Check second structured log (Debugw)
var entry2 map[string]any
err = json.Unmarshal([]byte(lines[1]), &entry2)
require.NoError(t, err)
assert.Equal(t, "DEBUG", entry2["level"])
fields2 := entry2["fields"].([]any)
assert.Equal(t, "msg", fields2[0])
assert.Equal(t, "query executed", fields2[1])
assert.Equal(t, "source", fields2[2])
assert.Equal(t, "fiber", fields2[3])
assert.Equal(t, "duration_ms", fields2[4])
assert.Equal(t, 42.0, fields2[5]) // JSON numbers are float64
}
// TestFiberBuilderIntegration ensures Fiber adapter can be built from builder
func TestFiberBuilderIntegration(t *testing.T) {
builder, logger, _ := createTestCompatBuilder(t)
defer logger.Shutdown()
fiberAdapter, err := builder.BuildFiber()
require.NoError(t, err)
assert.NotNil(t, fiberAdapter)
assert.Equal(t, logger, fiberAdapter.logger)
}

254
compat/fiber.go Normal file
View File

@ -0,0 +1,254 @@
// FILE: lixenwraith/log/compat/fiber.go
package compat
import (
"fmt"
"os"
"time"
"github.com/lixenwraith/log"
)
// FiberAdapter wraps lixenwraith/log.Logger to implement Fiber's CommonLogger interface
// This provides compatibility with Fiber v2.54.x logging requirements
type FiberAdapter struct {
logger *log.Logger
fatalHandler func(msg string) // Customizable fatal behavior
panicHandler func(msg string) // Customizable panic behavior
}
// NewFiberAdapter creates a new Fiber-compatible logger adapter
func NewFiberAdapter(logger *log.Logger, opts ...FiberOption) *FiberAdapter {
adapter := &FiberAdapter{
logger: logger,
fatalHandler: func(msg string) {
os.Exit(1) // Default behavior
},
panicHandler: func(msg string) {
panic(msg) // Default behavior
},
}
for _, opt := range opts {
opt(adapter)
}
return adapter
}
// FiberOption allows customizing adapter behavior
type FiberOption func(*FiberAdapter)
// WithFiberFatalHandler sets a custom fatal handler
func WithFiberFatalHandler(handler func(string)) FiberOption {
return func(a *FiberAdapter) {
a.fatalHandler = handler
}
}
// WithFiberPanicHandler sets a custom panic handler
func WithFiberPanicHandler(handler func(string)) FiberOption {
return func(a *FiberAdapter) {
a.panicHandler = handler
}
}
// --- Logger interface implementation (7 methods) ---
// Trace logs at trace/debug level
func (a *FiberAdapter) Trace(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Debug("msg", msg, "source", "fiber", "level", "trace")
}
// Debug logs at debug level
func (a *FiberAdapter) Debug(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Debug("msg", msg, "source", "fiber")
}
// Info logs at info level
func (a *FiberAdapter) Info(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Info("msg", msg, "source", "fiber")
}
// Warn logs at warn level
func (a *FiberAdapter) Warn(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Warn("msg", msg, "source", "fiber")
}
// Error logs at error level
func (a *FiberAdapter) Error(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Error("msg", msg, "source", "fiber")
}
// Fatal logs at error level and triggers fatal handler
func (a *FiberAdapter) Fatal(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Error("msg", msg, "source", "fiber", "fatal", true)
// Ensure log is flushed before exit
_ = a.logger.Flush(100 * time.Millisecond)
if a.fatalHandler != nil {
a.fatalHandler(msg)
}
}
// Panic logs at error level and triggers panic handler
func (a *FiberAdapter) Panic(v ...any) {
msg := fmt.Sprint(v...)
a.logger.Error("msg", msg, "source", "fiber", "panic", true)
// Ensure log is flushed before panic
_ = a.logger.Flush(100 * time.Millisecond)
if a.panicHandler != nil {
a.panicHandler(msg)
}
}
// Write makes FiberAdapter implement io.Writer interface
// This allows it to be used with fiber.Config.ErrorHandler output redirection
func (a *FiberAdapter) Write(p []byte) (n int, err error) {
msg := string(p)
// Trim trailing newline if present
if len(msg) > 0 && msg[len(msg)-1] == '\n' {
msg = msg[:len(msg)-1]
}
a.logger.Info("msg", msg, "source", "fiber")
return len(p), nil
}
// --- FormatLogger interface implementation (7 methods) ---
// Tracef logs at trace/debug level with printf-style formatting
func (a *FiberAdapter) Tracef(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Debug("msg", msg, "source", "fiber", "level", "trace")
}
// Debugf logs at debug level with printf-style formatting
func (a *FiberAdapter) Debugf(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Debug("msg", msg, "source", "fiber")
}
// Infof logs at info level with printf-style formatting
func (a *FiberAdapter) Infof(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Info("msg", msg, "source", "fiber")
}
// Warnf logs at warn level with printf-style formatting
func (a *FiberAdapter) Warnf(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Warn("msg", msg, "source", "fiber")
}
// Errorf logs at error level with printf-style formatting
func (a *FiberAdapter) Errorf(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Error("msg", msg, "source", "fiber")
}
// Fatalf logs at error level and triggers fatal handler
func (a *FiberAdapter) Fatalf(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Error("msg", msg, "source", "fiber", "fatal", true)
// Ensure log is flushed before exit
_ = a.logger.Flush(100 * time.Millisecond)
if a.fatalHandler != nil {
a.fatalHandler(msg)
}
}
// Panicf logs at error level and triggers panic handler
func (a *FiberAdapter) Panicf(format string, v ...any) {
msg := fmt.Sprintf(format, v...)
a.logger.Error("msg", msg, "source", "fiber", "panic", true)
// Ensure log is flushed before panic
_ = a.logger.Flush(100 * time.Millisecond)
if a.panicHandler != nil {
a.panicHandler(msg)
}
}
// --- WithLogger interface implementation (7 methods) ---
// Tracew logs at trace/debug level with structured key-value pairs
func (a *FiberAdapter) Tracew(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+6)
fields = append(fields, "msg", msg, "source", "fiber", "level", "trace")
fields = append(fields, keysAndValues...)
a.logger.Debug(fields...)
}
// Debugw logs at debug level with structured key-value pairs
func (a *FiberAdapter) Debugw(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+4)
fields = append(fields, "msg", msg, "source", "fiber")
fields = append(fields, keysAndValues...)
a.logger.Debug(fields...)
}
// Infow logs at info level with structured key-value pairs
func (a *FiberAdapter) Infow(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+4)
fields = append(fields, "msg", msg, "source", "fiber")
fields = append(fields, keysAndValues...)
a.logger.Info(fields...)
}
// Warnw logs at warn level with structured key-value pairs
func (a *FiberAdapter) Warnw(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+4)
fields = append(fields, "msg", msg, "source", "fiber")
fields = append(fields, keysAndValues...)
a.logger.Warn(fields...)
}
// Errorw logs at error level with structured key-value pairs
func (a *FiberAdapter) Errorw(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+4)
fields = append(fields, "msg", msg, "source", "fiber")
fields = append(fields, keysAndValues...)
a.logger.Error(fields...)
}
// Fatalw logs at error level with structured key-value pairs and triggers fatal handler
func (a *FiberAdapter) Fatalw(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+6)
fields = append(fields, "msg", msg, "source", "fiber", "fatal", true)
fields = append(fields, keysAndValues...)
a.logger.Error(fields...)
// Ensure log is flushed before exit
_ = a.logger.Flush(100 * time.Millisecond)
if a.fatalHandler != nil {
a.fatalHandler(msg)
}
}
// Panicw logs at error level with structured key-value pairs and triggers panic handler
func (a *FiberAdapter) Panicw(msg string, keysAndValues ...any) {
fields := make([]any, 0, len(keysAndValues)+6)
fields = append(fields, "msg", msg, "source", "fiber", "panic", true)
fields = append(fields, keysAndValues...)
a.logger.Error(fields...)
// Ensure log is flushed before panic
_ = a.logger.Flush(100 * time.Millisecond)
if a.panicHandler != nil {
a.panicHandler(msg)
}
}

View File

@ -1,4 +1,4 @@
// FILE: lixenwraith/log/compat/structured.go
// FILE: lixenwraith/log/compat/structured_gnet.go
package compat
import (