diff --git a/compat/README.md b/compat/README.md deleted file mode 100644 index 0e61096..0000000 --- a/compat/README.md +++ /dev/null @@ -1,188 +0,0 @@ -# Compatibility Adapters for lixenwraith/log - -This package provides compatibility adapters to use the `github.com/lixenwraith/log` logger with popular Go networking frameworks: - -- **gnet v2**: High-performance event-driven networking framework -- **fasthttp**: Fast HTTP implementation - -## Features - -- ✅ Full interface compatibility with both frameworks -- ✅ Preserves structured logging capabilities -- ✅ Configurable fatal behavior for gnet -- ✅ Automatic log level detection for fasthttp -- ✅ Optional structured field extraction from printf formats -- ✅ Thread-safe and high-performance -- ✅ Shared logger instance for multiple adapters - -## Installation - -```bash -go get github.com/lixenwraith/log -``` - -## Quick Start - -### Basic Usage with gnet - -```go -import ( - "github.com/lixenwraith/log" - "github.com/lixenwraith/log/compat" - "github.com/panjf2000/gnet/v2" -) - -// Create and configure logger -logger := log.NewLogger() -logger.InitWithDefaults("directory=/var/log/gnet", "level=-4") -defer logger.Shutdown() - -// Create gnet adapter -adapter := compat.NewGnetAdapter(logger) - -// Use with gnet -gnet.Run(eventHandler, "tcp://127.0.0.1:9000", gnet.WithLogger(adapter)) -``` - -### Basic Usage with fasthttp - -```go -import ( - "github.com/lixenwraith/log" - "github.com/lixenwraith/log/compat" - "github.com/valyala/fasthttp" -) - -// Create and configure logger -logger := log.NewLogger() -logger.InitWithDefaults("directory=/var/log/fasthttp") -defer logger.Shutdown() - -// Create fasthttp adapter -adapter := compat.NewFastHTTPAdapter(logger) - -// Use with fasthttp -server := &fasthttp.Server{ - Handler: requestHandler, - Logger: adapter, -} -server.ListenAndServe(":8080") -``` - -### Using the Builder Pattern - -```go -// Create adapters with shared configuration -builder := compat.NewBuilder(). - WithOptions( - "directory=/var/log/app", - "level=0", - "format=json", - "max_size_mb=100", - ) - -gnetAdapter, fasthttpAdapter, err := builder.Build() -if err != nil { - panic(err) -} -defer builder.GetLogger().Shutdown() -``` - -## Advanced Features - -### Structured Field Extraction - -The structured adapters can extract key-value pairs from printf-style format strings: - -```go -// Use structured adapter -adapter := compat.NewStructuredGnetAdapter(logger) - -// These calls will extract structured fields: -adapter.Infof("client=%s port=%d", "192.168.1.1", 8080) -// Logs: {"client": "192.168.1.1", "port": 8080, "source": "gnet"} - -adapter.Errorf("user: %s, action: %s, error: %s", "john", "login", "invalid password") -// Logs: {"user": "john", "action": "login", "error": "invalid password", "source": "gnet"} -``` - -### Custom Fatal Handling - -```go -adapter := compat.NewGnetAdapter(logger, - compat.WithFatalHandler(func(msg string) { - // Custom cleanup - saveState() - notifyAdmin(msg) - os.Exit(1) - }), -) -``` - -### Custom Level Detection for fasthttp - -```go -adapter := compat.NewFastHTTPAdapter(logger, - compat.WithDefaultLevel(log.LevelInfo), - compat.WithLevelDetector(func(msg string) int64 { - if strings.Contains(msg, "CRITICAL") { - return log.LevelError - } - return 0 // Use default detection - }), -) -``` - -## Configuration Examples - -### High-Performance Configuration - -```go -builder := compat.NewBuilder(). - WithOptions( - "directory=/var/log/highperf", - "level=0", // Info and above - "format=json", // Structured logs - "buffer_size=4096", // Large buffer - "flush_interval_ms=1000", // Less frequent flushes - "enable_periodic_sync=false", // Disable periodic sync - ) -``` - -### Development Configuration - -```go -builder := compat.NewBuilder(). - WithOptions( - "directory=./logs", - "level=-4", // Debug level - "format=txt", // Human-readable - "show_timestamp=true", - "show_level=true", - "trace_depth=3", // Include call traces - "flush_interval_ms=100", // Frequent flushes - ) -``` - -### Production Configuration with Monitoring - -```go -builder := compat.NewBuilder(). - WithOptions( - "directory=/var/log/prod", - "level=0", - "format=json", - "max_size_mb=1000", // 1GB files - "max_total_size_mb=10000", // 10GB total - "retention_period_hrs=168", // 7 days - "heartbeat_level=2", // Process + disk heartbeats - "heartbeat_interval_s=300", // 5 minutes - ) -``` - -## Performance Considerations - -1. **Printf Overhead**: The adapters must format printf-style strings, adding minimal overhead -2. **Structured Extraction**: The structured adapters use regex matching, which adds ~1-2μs per call -3. **Level Detection**: FastHTTP adapter's level detection adds <100ns for simple string checks -4. **Buffering**: The underlying logger's buffering minimizes I/O impact \ No newline at end of file diff --git a/interface.go b/interface.go index 93f92b2..50d1bc1 100644 --- a/interface.go +++ b/interface.go @@ -29,11 +29,12 @@ const ( // logRecord represents a single log entry. type logRecord struct { - Flags int64 - TimeStamp time.Time - Level int64 - Trace string - Args []any + Flags int64 + TimeStamp time.Time + Level int64 + Trace string + Args []any + unreportedDrops uint64 // Dropped log tracker } // Logger instance methods for configuration and logging at different levels. diff --git a/logger.go b/logger.go index befa133..3a4a058 100644 --- a/logger.go +++ b/logger.go @@ -334,21 +334,6 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) { return } - // Report dropped logs first if there has been any - currentDrops := l.state.DroppedLogs.Load() - logged := l.state.LoggedDrops.Load() - if currentDrops > logged { - if l.state.LoggedDrops.CompareAndSwap(logged, currentDrops) { - dropRecord := logRecord{ - Flags: FlagDefault, - TimeStamp: time.Now(), - Level: LevelError, - Args: []any{"Logs were dropped", "dropped_count", currentDrops - logged, "total_dropped", currentDrops}, - } - l.sendLogRecord(dropRecord) - } - } - var trace string if depth > 0 { const skipTrace = 3 // log.Info -> log -> getTrace (Adjust if call stack changes) @@ -356,11 +341,12 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) { } record := logRecord{ - Flags: flags, - TimeStamp: time.Now(), - Level: level, - Trace: trace, - Args: args, + Flags: flags, + TimeStamp: time.Now(), + Level: level, + Trace: trace, + Args: args, + unreportedDrops: 0, // 0 for regular logs } l.sendLogRecord(record) } @@ -368,13 +354,14 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) { // sendLogRecord handles safe sending to the active channel func (l *Logger) sendLogRecord(record logRecord) { defer func() { - if recover() != nil { // Catch panic on send to closed channel - l.state.DroppedLogs.Add(1) + if r := recover(); r != nil { // Catch panic on send to closed channel + l.handleFailedSend(record) } }() if l.state.ShutdownCalled.Load() || l.state.LoggerDisabled.Load() { - l.state.DroppedLogs.Add(1) + // Process drops even if logger is disabled or shutting down + l.handleFailedSend(record) return } @@ -383,13 +370,40 @@ func (l *Logger) sendLogRecord(record logRecord) { // Non-blocking send select { case ch <- record: - // Success + // Success: record sent, channel was not full, check if log drops need to be reported + if record.unreportedDrops == 0 { + // Get number of dropped logs and reset the counter to zero + droppedCount := l.state.DroppedLogs.Swap(0) + + if droppedCount > 0 { + // Dropped logs report + dropRecord := logRecord{ + Flags: FlagDefault, + TimeStamp: time.Now(), + Level: LevelError, + Args: []any{"Logs were dropped", "dropped_count", droppedCount}, + unreportedDrops: droppedCount, // Carry the count for recovery + } + // No success check is required, count is restored if it fails + l.sendLogRecord(dropRecord) + } + } default: - // Channel buffer is full or channel is closed - l.state.DroppedLogs.Add(1) + l.handleFailedSend(record) } } +// handleFailedSend restores or increments drop counter +func (l *Logger) handleFailedSend(record logRecord) { + // If the record was a drop report, add its carried count back. + // Otherwise, it was a regular log, so add 1. + amountToAdd := uint64(1) + if record.unreportedDrops > 0 { + amountToAdd = record.unreportedDrops + } + l.state.DroppedLogs.Add(amountToAdd) +} + // internalLog handles writing internal logger diagnostics to stderr, if enabled. // This centralizes all internal error reporting and makes it configurable. func (l *Logger) internalLog(format string, args ...any) { diff --git a/state.go b/state.go index d11bdc8..bfc479e 100644 --- a/state.go +++ b/state.go @@ -29,7 +29,6 @@ type State struct { CurrentSize atomic.Int64 // Size of the current log file EarliestFileTime atomic.Value // stores time.Time for retention DroppedLogs atomic.Uint64 // Counter for logs dropped - LoggedDrops atomic.Uint64 // Counter for dropped logs message already logged ActiveLogChannel atomic.Value // stores chan logRecord StdoutWriter atomic.Value // stores io.Writer (os.Stdout, os.Stderr, or io.Discard)