e1.6.2 Dropped log report fix.

This commit is contained in:
2025-07-13 01:23:37 -04:00
parent 115888979e
commit cf37614e35
4 changed files with 46 additions and 220 deletions

View File

@ -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

View File

@ -34,6 +34,7 @@ type logRecord struct {
Level int64 Level int64
Trace string Trace string
Args []any Args []any
unreportedDrops uint64 // Dropped log tracker
} }
// Logger instance methods for configuration and logging at different levels. // Logger instance methods for configuration and logging at different levels.

View File

@ -334,21 +334,6 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) {
return 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 var trace string
if depth > 0 { if depth > 0 {
const skipTrace = 3 // log.Info -> log -> getTrace (Adjust if call stack changes) const skipTrace = 3 // log.Info -> log -> getTrace (Adjust if call stack changes)
@ -361,6 +346,7 @@ func (l *Logger) log(flags int64, level int64, depth int64, args ...any) {
Level: level, Level: level,
Trace: trace, Trace: trace,
Args: args, Args: args,
unreportedDrops: 0, // 0 for regular logs
} }
l.sendLogRecord(record) 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 // sendLogRecord handles safe sending to the active channel
func (l *Logger) sendLogRecord(record logRecord) { func (l *Logger) sendLogRecord(record logRecord) {
defer func() { defer func() {
if recover() != nil { // Catch panic on send to closed channel if r := recover(); r != nil { // Catch panic on send to closed channel
l.state.DroppedLogs.Add(1) l.handleFailedSend(record)
} }
}() }()
if l.state.ShutdownCalled.Load() || l.state.LoggerDisabled.Load() { 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 return
} }
@ -383,11 +370,38 @@ func (l *Logger) sendLogRecord(record logRecord) {
// Non-blocking send // Non-blocking send
select { select {
case ch <- record: case ch <- record:
// Success // Success: record sent, channel was not full, check if log drops need to be reported
default: if record.unreportedDrops == 0 {
// Channel buffer is full or channel is closed // Get number of dropped logs and reset the counter to zero
l.state.DroppedLogs.Add(1) 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:
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. // internalLog handles writing internal logger diagnostics to stderr, if enabled.

View File

@ -29,7 +29,6 @@ type State struct {
CurrentSize atomic.Int64 // Size of the current log file CurrentSize atomic.Int64 // Size of the current log file
EarliestFileTime atomic.Value // stores time.Time for retention EarliestFileTime atomic.Value // stores time.Time for retention
DroppedLogs atomic.Uint64 // Counter for logs dropped DroppedLogs atomic.Uint64 // Counter for logs dropped
LoggedDrops atomic.Uint64 // Counter for dropped logs message already logged
ActiveLogChannel atomic.Value // stores chan logRecord ActiveLogChannel atomic.Value // stores chan logRecord
StdoutWriter atomic.Value // stores io.Writer (os.Stdout, os.Stderr, or io.Discard) StdoutWriter atomic.Value // stores io.Writer (os.Stdout, os.Stderr, or io.Discard)