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
|
|
||||||
@ -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.
|
||||||
|
|||||||
58
logger.go
58
logger.go
@ -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.
|
||||||
|
|||||||
1
state.go
1
state.go
@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user