Files
releases/v1/logging/buffered_logger.go
T
Stephan Renatus aa38bb5b57 logging: make WithContext() optional
Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
2026-03-30 10:20:13 +02:00

183 lines
4.2 KiB
Go

package logging
import (
"fmt"
"maps"
"sync"
"time"
)
type logEntry struct {
level Level
message string
fields map[string]any
time time.Time
}
// BufferedLogger captures log entries in memory until Flush is called,
// at which point it replays all buffered entries to the target logger.
// After Flush() is called, the BufferedLogger should not be used anymore.
type BufferedLogger struct {
mu sync.Mutex
buffer []logEntry
maxEntries int
currentLevel Level
}
// NewBufferedLogger creates a new buffered logger that will buffer up to maxEntries.
func NewBufferedLogger(maxEntries int) *BufferedLogger {
if maxEntries <= 0 {
maxEntries = 1000
}
return &BufferedLogger{
buffer: make([]logEntry, 0, maxEntries),
maxEntries: maxEntries,
currentLevel: Info,
}
}
func (b *BufferedLogger) addToBuffer(level Level, format string, args []any, fields map[string]any) {
message := format
if len(args) > 0 {
message = fmt.Sprintf(format, args...)
}
entry := logEntry{
level: level,
message: message,
fields: fields,
time: time.Now(),
}
b.mu.Lock()
defer b.mu.Unlock()
if len(b.buffer) >= b.maxEntries {
b.buffer = b.buffer[1:]
}
b.buffer = append(b.buffer, entry)
}
func (*BufferedLogger) logToTarget(target Logger, entry logEntry) {
fields := make(map[string]any, len(entry.fields))
maps.Copy(fields, entry.fields)
fields["time"] = entry.time
logger := target.WithFields(fields)
switch entry.level {
case Debug:
logger.Debug("%s", entry.message)
case Info:
logger.Info("%s", entry.message)
case Warn:
logger.Warn("%s", entry.message)
case Error:
logger.Error("%s", entry.message)
}
}
func (b *BufferedLogger) Debug(format string, args ...any) {
b.addToBuffer(Debug, format, args, nil)
}
func (b *BufferedLogger) Info(format string, args ...any) {
b.addToBuffer(Info, format, args, nil)
}
func (b *BufferedLogger) Warn(format string, args ...any) {
b.addToBuffer(Warn, format, args, nil)
}
func (b *BufferedLogger) Error(format string, args ...any) {
b.addToBuffer(Error, format, args, nil)
}
// WithFields returns a new logger with additional fields.
func (b *BufferedLogger) WithFields(fields map[string]any) Logger {
return &bufferedLoggerWithFields{
parent: b,
fields: fields,
}
}
// GetLevel returns the current log level.
func (b *BufferedLogger) GetLevel() Level {
b.mu.Lock()
defer b.mu.Unlock()
return b.currentLevel
}
// SetLevel sets the log level.
func (b *BufferedLogger) SetLevel(level Level) {
b.mu.Lock()
defer b.mu.Unlock()
b.currentLevel = level
}
// Close discards all buffered entries without flushing them.
// After calling Close, the BufferedLogger should not be used anymore.
func (b *BufferedLogger) Close() {
b.mu.Lock()
defer b.mu.Unlock()
b.buffer = nil
}
// Flush replays all buffered entries to the target logger.
// After calling Flush, the BufferedLogger should not be used anymore.
// The caller should switch to using the target logger directly.
func (b *BufferedLogger) Flush(targetLogger Logger) {
if targetLogger == nil {
return
}
b.mu.Lock()
targetLogger.SetLevel(b.currentLevel)
entries := b.buffer
b.buffer = nil
b.mu.Unlock()
for _, entry := range entries {
b.logToTarget(targetLogger, entry)
}
}
type bufferedLoggerWithFields struct {
parent *BufferedLogger
fields map[string]any
}
func (b *bufferedLoggerWithFields) Debug(format string, args ...any) {
b.parent.addToBuffer(Debug, format, args, b.fields)
}
func (b *bufferedLoggerWithFields) Info(format string, args ...any) {
b.parent.addToBuffer(Info, format, args, b.fields)
}
func (b *bufferedLoggerWithFields) Warn(format string, args ...any) {
b.parent.addToBuffer(Warn, format, args, b.fields)
}
func (b *bufferedLoggerWithFields) Error(format string, args ...any) {
b.parent.addToBuffer(Error, format, args, b.fields)
}
func (b *bufferedLoggerWithFields) WithFields(fields map[string]any) Logger {
merged := make(map[string]any, len(b.fields)+len(fields))
maps.Copy(merged, b.fields)
maps.Copy(merged, fields)
return &bufferedLoggerWithFields{
parent: b.parent,
fields: merged,
}
}
func (b *bufferedLoggerWithFields) GetLevel() Level {
return b.parent.GetLevel()
}
func (b *bufferedLoggerWithFields) SetLevel(level Level) {
b.parent.SetLevel(level)
}