v0.1.11 harden logging lifecycle, sanitization, and formatter edge cases
Test / test (push) Canceled after 0s
Test / fuzz (./compat, FuzzParseFormat) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatJSON) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatTextPolicy) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSanitize) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSerializerJSON) (push) Canceled after 0s
Test / test (push) Canceled after 0s
Test / fuzz (./compat, FuzzParseFormat) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatJSON) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatTextPolicy) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSanitize) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSerializerJSON) (push) Canceled after 0s
This commit is contained in:
1 parent
79a81db3ca
commit
2417e51da1
46 files changed
+1846
-1289
No files matched your search
+4
-55
@@ -97,7 +97,7 @@ func (b *Builder) BuildFastHTTP(opts ...FastHTTPOption) (*FastHTTPAdapter, error
|
||||
return NewFastHTTPAdapter(l, opts...), nil
|
||||
}
|
||||
|
||||
// BuildFiber creates a Fiber v2.54.x adapter
|
||||
// BuildFiber creates a Fiber v2 adapter
|
||||
func (b *Builder) BuildFiber(opts ...FiberOption) (*FiberAdapter, error) {
|
||||
l, err := b.getLogger()
|
||||
if err != nil {
|
||||
@@ -112,57 +112,6 @@ func (b *Builder) GetLogger() (*log.Logger, error) {
|
||||
return b.getLogger()
|
||||
}
|
||||
|
||||
// --- Example Usage ---
|
||||
//
|
||||
// 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
|
||||
// appLogger := log.NewLogger()
|
||||
// logCfg := log.DefaultConfig()
|
||||
// logCfg.Level = log.LevelDebug
|
||||
// if err := appLogger.ApplyConfig(logCfg); err != nil {
|
||||
// panic(fmt.Sprintf("failed to configure logger: %v", err))
|
||||
// }
|
||||
//
|
||||
// // 2. Create a builder and provide the existing logger
|
||||
// builder := compat.NewBuilder().WithLogger(appLogger)
|
||||
//
|
||||
// // 3. Build the required adapters
|
||||
// gnetLogger, err := builder.BuildGnet()
|
||||
// if err != nil { /* handle 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:
|
||||
// var events gnet.EventHandler // your-event-handler
|
||||
// // The adapter is passed directly into the gnet options
|
||||
// go gnet.Run(events, "tcp://:9000", gnet.WithLogger(gnetLogger))
|
||||
//
|
||||
// // For fasthttp:
|
||||
// // The adapter is assigned directly to the server's Logger field
|
||||
// server := &fasthttp.Server{
|
||||
// Handler: func(ctx *fasthttp.RequestCtx) {
|
||||
// ctx.WriteString("Hello, world!")
|
||||
// },
|
||||
// Logger: fasthttpLogger,
|
||||
// }
|
||||
// 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")
|
||||
// Configure and Start the shared Logger before building adapters. WithConfig
|
||||
// initializes a new Logger but does not Start it; use GetLogger for lifecycle
|
||||
// control. See doc/adapters.md for framework integration examples.
|
||||
@@ -501,4 +501,3 @@ func TestFiberAdapterWriter(t *testing.T) {
|
||||
eq(t, level, "INFO", "level")
|
||||
checkFields(t, fields, []any{"msg", "writer output", "source", "fiber"}, "Write")
|
||||
}
|
||||
|
||||
+7
-3
@@ -59,16 +59,20 @@ func (a *FastHTTPAdapter) Printf(format string, args ...any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Log with appropriate level
|
||||
switch level {
|
||||
case log.LevelDebug:
|
||||
a.logger.Debug("msg", msg, "source", "fasthttp")
|
||||
case log.LevelInfo:
|
||||
a.logger.Info("msg", msg, "source", "fasthttp")
|
||||
case log.LevelWarn:
|
||||
a.logger.Warn("msg", msg, "source", "fasthttp")
|
||||
case log.LevelError:
|
||||
a.logger.Error("msg", msg, "source", "fasthttp")
|
||||
default:
|
||||
a.logger.Info("msg", msg, "source", "fasthttp")
|
||||
if a.logger.Enabled(level) {
|
||||
a.logger.LogContext(log.Context{}, a.logger.Flags(), level, a.logger.GetConfig().TraceDepth,
|
||||
"msg", msg, "source", "fasthttp")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,4 +103,4 @@ func DetectLogLevel(msg string) int64 {
|
||||
|
||||
// Default to info level
|
||||
return log.LevelInfo
|
||||
}
|
||||
}
|
||||
+38
-2
@@ -9,7 +9,7 @@ import (
|
||||
)
|
||||
|
||||
// FiberAdapter wraps lixenwraith/log.Logger to implement Fiber's CommonLogger interface
|
||||
// This provides compatibility with Fiber v2.54.x logging requirements
|
||||
// This provides compatibility with Fiber v2 logging methods
|
||||
type FiberAdapter struct {
|
||||
logger *log.Logger
|
||||
fatalHandler func(msg string) // Customizable fatal behavior
|
||||
@@ -62,24 +62,36 @@ func (a *FiberAdapter) Trace(v ...any) {
|
||||
|
||||
// Debug logs at debug level
|
||||
func (a *FiberAdapter) Debug(v ...any) {
|
||||
if !a.logger.Enabled(log.LevelDebug) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprint(v...)
|
||||
a.logger.Debug("msg", msg, "source", "fiber")
|
||||
}
|
||||
|
||||
// Info logs at info level
|
||||
func (a *FiberAdapter) Info(v ...any) {
|
||||
if !a.logger.Enabled(log.LevelInfo) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprint(v...)
|
||||
a.logger.Info("msg", msg, "source", "fiber")
|
||||
}
|
||||
|
||||
// Warn logs at warn level
|
||||
func (a *FiberAdapter) Warn(v ...any) {
|
||||
if !a.logger.Enabled(log.LevelWarn) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprint(v...)
|
||||
a.logger.Warn("msg", msg, "source", "fiber")
|
||||
}
|
||||
|
||||
// Error logs at error level
|
||||
func (a *FiberAdapter) Error(v ...any) {
|
||||
if !a.logger.Enabled(log.LevelError) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprint(v...)
|
||||
a.logger.Error("msg", msg, "source", "fiber")
|
||||
}
|
||||
@@ -132,24 +144,36 @@ func (a *FiberAdapter) Tracef(format string, v ...any) {
|
||||
|
||||
// Debugf logs at debug level with printf-style formatting
|
||||
func (a *FiberAdapter) Debugf(format string, v ...any) {
|
||||
if !a.logger.Enabled(log.LevelDebug) {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
if !a.logger.Enabled(log.LevelInfo) {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
if !a.logger.Enabled(log.LevelWarn) {
|
||||
return
|
||||
}
|
||||
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) {
|
||||
if !a.logger.Enabled(log.LevelError) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, v...)
|
||||
a.logger.Error("msg", msg, "source", "fiber")
|
||||
}
|
||||
@@ -192,6 +216,9 @@ func (a *FiberAdapter) Tracew(msg string, keysAndValues ...any) {
|
||||
|
||||
// Debugw logs at debug level with structured key-value pairs
|
||||
func (a *FiberAdapter) Debugw(msg string, keysAndValues ...any) {
|
||||
if !a.logger.Enabled(log.LevelDebug) {
|
||||
return
|
||||
}
|
||||
fields := make([]any, 0, len(keysAndValues)+4)
|
||||
fields = append(fields, "msg", msg, "source", "fiber")
|
||||
fields = append(fields, keysAndValues...)
|
||||
@@ -200,6 +227,9 @@ func (a *FiberAdapter) Debugw(msg string, keysAndValues ...any) {
|
||||
|
||||
// Infow logs at info level with structured key-value pairs
|
||||
func (a *FiberAdapter) Infow(msg string, keysAndValues ...any) {
|
||||
if !a.logger.Enabled(log.LevelInfo) {
|
||||
return
|
||||
}
|
||||
fields := make([]any, 0, len(keysAndValues)+4)
|
||||
fields = append(fields, "msg", msg, "source", "fiber")
|
||||
fields = append(fields, keysAndValues...)
|
||||
@@ -208,6 +238,9 @@ func (a *FiberAdapter) Infow(msg string, keysAndValues ...any) {
|
||||
|
||||
// Warnw logs at warn level with structured key-value pairs
|
||||
func (a *FiberAdapter) Warnw(msg string, keysAndValues ...any) {
|
||||
if !a.logger.Enabled(log.LevelWarn) {
|
||||
return
|
||||
}
|
||||
fields := make([]any, 0, len(keysAndValues)+4)
|
||||
fields = append(fields, "msg", msg, "source", "fiber")
|
||||
fields = append(fields, keysAndValues...)
|
||||
@@ -216,6 +249,9 @@ func (a *FiberAdapter) Warnw(msg string, keysAndValues ...any) {
|
||||
|
||||
// Errorw logs at error level with structured key-value pairs
|
||||
func (a *FiberAdapter) Errorw(msg string, keysAndValues ...any) {
|
||||
if !a.logger.Enabled(log.LevelError) {
|
||||
return
|
||||
}
|
||||
fields := make([]any, 0, len(keysAndValues)+4)
|
||||
fields = append(fields, "msg", msg, "source", "fiber")
|
||||
fields = append(fields, keysAndValues...)
|
||||
@@ -250,4 +286,4 @@ func (a *FiberAdapter) Panicw(msg string, keysAndValues ...any) {
|
||||
if a.panicHandler != nil {
|
||||
a.panicHandler(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-1
@@ -42,24 +42,36 @@ func WithFatalHandler(handler func(string)) GnetOption {
|
||||
|
||||
// Debugf logs at debug level with printf-style formatting
|
||||
func (a *GnetAdapter) Debugf(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelDebug) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
a.logger.Debug("msg", msg, "source", "gnet")
|
||||
}
|
||||
|
||||
// Infof logs at info level with printf-style formatting
|
||||
func (a *GnetAdapter) Infof(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelInfo) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
a.logger.Info("msg", msg, "source", "gnet")
|
||||
}
|
||||
|
||||
// Warnf logs at warn level with printf-style formatting
|
||||
func (a *GnetAdapter) Warnf(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelWarn) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
a.logger.Warn("msg", msg, "source", "gnet")
|
||||
}
|
||||
|
||||
// Errorf logs at error level with printf-style formatting
|
||||
func (a *GnetAdapter) Errorf(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelError) {
|
||||
return
|
||||
}
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
a.logger.Error("msg", msg, "source", "gnet")
|
||||
}
|
||||
@@ -75,4 +87,4 @@ func (a *GnetAdapter) Fatalf(format string, args ...any) {
|
||||
if a.fatalHandler != nil {
|
||||
a.fatalHandler(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package compat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseFormatKeepsArgumentAssociation(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
format string
|
||||
args []any
|
||||
want []any
|
||||
}{
|
||||
{"prefix %s id=%d", []any{"alice", 42}, []any{"msg", "prefix alice id=42"}},
|
||||
{"id=%[2]d name=%[1]s", []any{"alice", 42}, []any{"msg", "id=42 name=alice"}},
|
||||
{"id=%d suffix", []any{42}, []any{"msg", "suffix", "id", 42}},
|
||||
{"user=%s id=%d", []any{"alice", 42}, []any{"user", "alice", "id", 42}},
|
||||
{"id=%d %s", []any{42, "tail"}, []any{"msg", "id=42 tail"}},
|
||||
{"id=%*d", []any{4, 42}, []any{"msg", "id= 42"}},
|
||||
{"literal %% id=%d", []any{42}, []any{"msg", "literal % id=42"}},
|
||||
} {
|
||||
t.Run(tc.format, func(t *testing.T) {
|
||||
if got := parseFormat(tc.format, tc.args); !reflect.DeepEqual(got, tc.want) {
|
||||
t.Fatalf("got %#v, want %#v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzParseFormat(f *testing.F) {
|
||||
for _, format := range []string{"id=%d", "%s id=%d", "%%id=%d", "id=%[1]s"} {
|
||||
f.Add(format, "value")
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, format, value string) {
|
||||
if len(format) > 1024 {
|
||||
t.Skip()
|
||||
}
|
||||
fields := parseFormat(format, []any{value})
|
||||
if len(fields)%2 != 0 {
|
||||
t.Fatalf("odd fields %#v", fields)
|
||||
}
|
||||
for i := 0; i < len(fields); i += 2 {
|
||||
if _, ok := fields[i].(string); !ok {
|
||||
t.Fatalf("non-string key %#v", fields)
|
||||
}
|
||||
}
|
||||
if len(fields) == 2 && fields[0] == "msg" {
|
||||
if fields[1] != fmt.Sprintf(format, value) {
|
||||
t.Fatal("fallback changed message")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
+40
-57
@@ -8,70 +8,41 @@ import (
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
// parseFormat attempts to extract structured fields from printf-style format strings
|
||||
// Useful for preserving structured logging semantics
|
||||
// Only unindexed, single-argument verbs are eligible for extraction. Mixed
|
||||
// printf expressions fall back intact, rather than attributing an argument to
|
||||
// the wrong key (width, precision and explicit indexes may consume arguments).
|
||||
var keyValuePattern = regexp.MustCompile(`(\w+)\s*[:=]\s*%[vsdqxXeEfFgGpbcU]`)
|
||||
|
||||
func parseFormat(format string, args []any) []any {
|
||||
// Pattern to detect common structured patterns like "key=%v" or "key: %v"
|
||||
keyValuePattern := regexp.MustCompile(`(\w+)\s*[:=]\s*%[vsdqxXeEfFgGpbcU]`)
|
||||
|
||||
fallback := func() []any { return []any{"msg", fmt.Sprintf(format, args...)} }
|
||||
matches := keyValuePattern.FindAllStringSubmatchIndex(format, -1)
|
||||
if len(matches) == 0 || len(matches) > len(args) {
|
||||
// Fallback to simple message if pattern doesn't match
|
||||
return []any{"msg", fmt.Sprintf(format, args...)}
|
||||
if len(matches) == 0 || len(matches) != len(args) {
|
||||
return fallback()
|
||||
}
|
||||
|
||||
// Build structured fields
|
||||
fields := make([]any, 0, len(matches)*2+2)
|
||||
lastEnd := 0
|
||||
argIndex := 0
|
||||
|
||||
for _, match := range matches {
|
||||
// Add any text before this match as part of the message
|
||||
if match[0] > lastEnd {
|
||||
prefix := format[lastEnd:match[0]]
|
||||
if strings.TrimSpace(prefix) != "" {
|
||||
if len(fields) == 0 {
|
||||
fields = append(fields, "msg", strings.TrimSpace(prefix))
|
||||
}
|
||||
}
|
||||
var text []string
|
||||
end := 0
|
||||
for i, m := range matches {
|
||||
gap := format[end:m[0]]
|
||||
if strings.Contains(gap, "%") {
|
||||
return fallback()
|
||||
}
|
||||
|
||||
// Extract key name
|
||||
keyStart := match[2]
|
||||
keyEnd := match[3]
|
||||
key := format[keyStart:keyEnd]
|
||||
|
||||
// Get corresponding value
|
||||
if argIndex < len(args) {
|
||||
fields = append(fields, key, args[argIndex])
|
||||
argIndex++
|
||||
if part := strings.TrimSpace(gap); part != "" {
|
||||
text = append(text, part)
|
||||
}
|
||||
|
||||
lastEnd = match[1]
|
||||
fields = append(fields, format[m[2]:m[3]], args[i])
|
||||
end = m[1]
|
||||
}
|
||||
|
||||
// Handle remaining format string and args
|
||||
if lastEnd < len(format) {
|
||||
remainingFormat := format[lastEnd:]
|
||||
remainingArgs := args[argIndex:]
|
||||
if len(remainingArgs) > 0 {
|
||||
remaining := fmt.Sprintf(remainingFormat, remainingArgs...)
|
||||
if strings.TrimSpace(remaining) != "" {
|
||||
if len(fields) == 0 {
|
||||
fields = append(fields, "msg", strings.TrimSpace(remaining))
|
||||
} else {
|
||||
// Append to existing message
|
||||
for i := 0; i < len(fields); i += 2 {
|
||||
if fields[i] == "msg" {
|
||||
fields[i+1] = fmt.Sprintf("%v %s", fields[i+1], strings.TrimSpace(remaining))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tail := format[end:]
|
||||
if strings.Contains(tail, "%") {
|
||||
return fallback()
|
||||
}
|
||||
if part := strings.TrimSpace(tail); part != "" {
|
||||
text = append(text, part)
|
||||
}
|
||||
if len(text) > 0 {
|
||||
fields = append([]any{"msg", strings.Join(text, " ")}, fields...)
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
@@ -91,6 +62,9 @@ func NewStructuredGnetAdapter(logger *log.Logger, opts ...GnetOption) *Structure
|
||||
|
||||
// Debugf logs with structured field extraction
|
||||
func (a *StructuredGnetAdapter) Debugf(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelDebug) {
|
||||
return
|
||||
}
|
||||
if a.extractFields {
|
||||
fields := parseFormat(format, args)
|
||||
a.logger.Debug(append(fields, "source", "gnet")...)
|
||||
@@ -101,6 +75,9 @@ func (a *StructuredGnetAdapter) Debugf(format string, args ...any) {
|
||||
|
||||
// Infof logs with structured field extraction
|
||||
func (a *StructuredGnetAdapter) Infof(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelInfo) {
|
||||
return
|
||||
}
|
||||
if a.extractFields {
|
||||
fields := parseFormat(format, args)
|
||||
a.logger.Info(append(fields, "source", "gnet")...)
|
||||
@@ -111,6 +88,9 @@ func (a *StructuredGnetAdapter) Infof(format string, args ...any) {
|
||||
|
||||
// Warnf logs with structured field extraction
|
||||
func (a *StructuredGnetAdapter) Warnf(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelWarn) {
|
||||
return
|
||||
}
|
||||
if a.extractFields {
|
||||
fields := parseFormat(format, args)
|
||||
a.logger.Warn(append(fields, "source", "gnet")...)
|
||||
@@ -121,10 +101,13 @@ func (a *StructuredGnetAdapter) Warnf(format string, args ...any) {
|
||||
|
||||
// Errorf logs with structured field extraction
|
||||
func (a *StructuredGnetAdapter) Errorf(format string, args ...any) {
|
||||
if !a.logger.Enabled(log.LevelError) {
|
||||
return
|
||||
}
|
||||
if a.extractFields {
|
||||
fields := parseFormat(format, args)
|
||||
a.logger.Error(append(fields, "source", "gnet")...)
|
||||
} else {
|
||||
a.GnetAdapter.Errorf(format, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user