e1.6.2 Dropped log report fix.
This commit is contained in:
188
compat/README.md
188
compat/README.md
@ -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
|
||||
11
interface.go
11
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.
|
||||
|
||||
66
logger.go
66
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) {
|
||||
|
||||
1
state.go
1
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)
|
||||
|
||||
Reference in New Issue
Block a user