Documentation
¶
Overview ¶
Package loggerj provides an ultra-high-performance, asynchronous, and lock-free logging facility designed for high-throughput Go services. It offers zero heap allocations in the hot path, atomic rate limiting, log rotation, and structured fields in both text and JSON formats.
Architecture: Pre-Compiled Execution Profiles ¶
Unlike traditional loggers that use mutexes and maps for rate limiting and sampling in the hot path, loggerj uses a "Pre-compiled Execution Profile" architecture.
- Cold Path (Init-time): You register log types using RegisterSub(). This pre-bakes JSON/Text prefixes into []byte and initializes lock-free atomic counters for rate limiting and sampling. Profiles are stored in an immutable copy-on-write registry accessed via atomic.Pointer, ensuring zero interface boxing and zero map lookups in the hot path.
- Hot Path (Log-time): The Log() method performs ZERO map lookups, ZERO mutex locks, and ZERO heap allocations. It uses atomic.CompareAndSwap (CAS) for rate limiting and atomic.Add for sampling. String-to-byte conversion uses unsafe zero-copy (Go 1.21+). Timestamps are deferred to the worker goroutine, removing a vDSO syscall from the hot path.
- Worker: A dedicated goroutine formats entries, injects pre-baked []byte prefixes, and writes to the underlying io.Writer via bufio. Flush() drains the channel before writing, guaranteeing no log loss on explicit flush.
This design ensures that logging never blocks the caller, eliminates GC pressure in the hot path, and scales linearly with CPU cores without lock contention.
Quick Start ¶
logger := loggerj.NewLogger(loggerj.Config{
JSONOutput: true,
FlushTimeout: 50 * time.Millisecond,
})
// COLD PATH: Register profiles once at startup
logger.RegisterSub("HTTP",
loggerj.WithRateLimit(1000, time.Second),
loggerj.WithFields("env", "prod", "service", "gateway"),
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go logger.Start(ctx)
defer logger.Close()
// HOT PATH: Ultra-fast, zero-allocation logging
logger.InfoString("HTTP", "request received", "method", "GET", "path", "/api")
logger.ErrorString("DB", "connection failed", "host", "localhost", "err", "timeout")
// Context-aware logging (opt-in, zero cost when unused)
ctx = context.WithValue(ctx, loggerj.TraceIDKey, "abc-123")
logger.InfoCtx(ctx, "HTTP", "traced request", "method", "POST")
Performance ¶
Benchmarks on Apple M1 Pro (10 cores), Go 1.21+:
Filtered: ~2.0 ns/op (484M logs/s) 0 allocs/op RateLimited: ~44 ns/op (23M logs/s) 0 allocs/op Parallel: ~86 ns/op (11.6M logs/s) 0 allocs/op JSON: ~65 ns/op (15.3M logs/s) 0 allocs/op StringAPI: ~66 ns/op (15.1M logs/s) 0 allocs/op WithCaller: ~461 ns/op (2.2M logs/s) 2 allocs/op
See BENCH.md for detailed benchmark results and methodology.
Index ¶
- Constants
- type Config
- type Entry
- type Level
- type Logger
- func (l *Logger) AsWriter(level Level, logType string) io.Writer
- func (l *Logger) Close() error
- func (l *Logger) Debug(logType string, msg []byte, fields ...string)
- func (l *Logger) DebugCtx(ctx context.Context, logType string, msg string, fields ...string)
- func (l *Logger) DebugString(logType string, msg string, fields ...string)
- func (l *Logger) Drops() uint64
- func (l *Logger) Error(logType string, msg []byte, fields ...string)
- func (l *Logger) ErrorCtx(ctx context.Context, logType string, msg string, fields ...string)
- func (l *Logger) ErrorString(logType string, msg string, fields ...string)
- func (l *Logger) Flush()
- func (l *Logger) GetLevel() Level
- func (l *Logger) Info(logType string, msg []byte, fields ...string)
- func (l *Logger) InfoCtx(ctx context.Context, logType string, msg string, fields ...string)
- func (l *Logger) InfoString(logType string, msg string, fields ...string)
- func (l *Logger) Log(level Level, logType string, msg []byte, fields ...string)
- func (l *Logger) RegisterSub(logType string, opts ...SubOption)
- func (l *Logger) ResetDrops()
- func (l *Logger) SetLevelValue(level Level)
- func (l *Logger) SetOnDrop(fn func(dropped uint64))
- func (l *Logger) Start(ctx context.Context)
- func (l *Logger) StartWithWriter(ctx context.Context, w io.Writer)
- func (l *Logger) Stats() map[string]uint64
- func (l *Logger) Warn(logType string, msg []byte, fields ...string)
- func (l *Logger) WarnCtx(ctx context.Context, logType string, msg string, fields ...string)
- func (l *Logger) WarnString(logType string, msg string, fields ...string)
- type StdLogWriter
- type SubOption
- type SubProfile
Constants ¶
const ( // TraceIDKey is the context key for distributed trace identifiers. TraceIDKey contextKey = iota // RequestIDKey is the context key for request identifiers. RequestIDKey // SpanIDKey is the context key for span identifiers. SpanIDKey )
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Config ¶
type Config struct {
// JSONOutput controls the output format. If true, logs are formatted as JSON.
// If false, logs are formatted as human-readable text. Default: false
JSONOutput bool
// FlushTimeout is the interval at which the worker flushes buffered logs.
// Shorter timeouts reduce latency but increase I/O operations. Default: 50ms
FlushTimeout time.Duration
// ChannelSize is the capacity of the internal log channel. Larger values
// provide more buffering for burst traffic. If full, entries are dropped.
// Default: 4096
ChannelSize int
// WorkerBufferSize is the initial capacity of the worker's format buffer.
// Minimum: 256. Default: 4096
WorkerBufferSize int
// FlushThreshold is the byte count at which the worker flushes the
// format buffer to the underlying writer. Should be <= WorkerBufferSize.
// Minimum: 256. Default: 4096
FlushThreshold int
// WriterBufferSize is the size of the bufio.Writer buffer used for I/O.
// Minimum: 512. Default: 8192
WriterBufferSize int
// RateLimitWindow is the default time window for rate limiting, in seconds.
// Used if a SubProfile doesn't specify its own window via WithRateLimit.
// Minimum: 1. Default: 1
RateLimitWindow int64
// IncludeCaller adds file:line information to each log entry.
// WARNING: Adds ~460ns overhead and 2 allocations per log entry.
// Should be disabled in production for maximum performance. Default: false
IncludeCaller bool
// OutputFile is the path to the log file. If empty, logs are written to stderr.
OutputFile string
// MaxFileSize is the maximum size of the log file before rotation.
// If 0, rotation is disabled. Default: 0
MaxFileSize int64
// MaxBackupFiles is the maximum number of rotated log files to keep.
// Only effective if MaxFileSize > 0. Default: 0
MaxBackupFiles int
}
Config holds the configuration for a Logger instance. All fields have sensible defaults and can be left at their zero values.
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults for production use.
type Entry ¶
type Entry struct {
Level Level
Type string
Msg []byte
File string
Line int
Fields []string
Profile *SubProfile
}
Entry represents a single log record. Entries are pooled using sync.Pool to minimize allocations. The Reset method clears all fields for reuse.
Timestamps are not stored in the Entry; they are captured by the worker goroutine at format time, removing a vDSO syscall from the hot path.
type Level ¶
type Level uint8
Level represents the severity of a log entry. Lower values indicate more verbose logging. The logger filters entries below the configured threshold.
const ( // LevelDebug is the most verbose level, used for detailed debugging information. LevelDebug Level = 0 // LevelInfo is the default level, used for general operational messages. LevelInfo Level = 1 // LevelWarn indicates potential issues that should be monitored. LevelWarn Level = 2 // LevelError indicates serious problems that require immediate attention. LevelError Level = 3 )
type Logger ¶
type Logger struct {
// contains filtered or unexported fields
}
Logger is the main logging instance. It provides asynchronous, high-throughput, lock-free logging with zero heap allocations in the hot path.
func NewLogger ¶
NewLogger creates a new Logger instance. The logger is not started; call Start or StartWithWriter to begin processing.
func (*Logger) AsWriter ¶
AsWriter returns an io.Writer that routes all writes to the logger at the specified level and logType.
Usage with the standard library log package:
log.SetFlags(0) // Disable std log timestamps; loggerj adds its own log.SetOutput(logger.AsWriter(loggerj.LevelInfo, "STDLIB"))
Note: This adapter incurs a minor allocation (string(p)) per write. This is acceptable for intercepting legacy or third-party logs but should not be used for the application's primary high-throughput path.
func (*Logger) Close ¶
Close releases resources held by the logger, including flushing the buffered writer and closing the log file handle.
func (*Logger) DebugCtx ¶ added in v1.1.0
DebugCtx logs a message at LevelDebug with optional context extraction. If ctx is nil or contains no known keys, it behaves exactly like DebugString with zero additional cost.
func (*Logger) DebugString ¶
DebugString logs a string message at LevelDebug with zero-copy string-to-byte conversion. Caller skip is 2.
func (*Logger) Drops ¶
Drops returns the total number of log entries dropped because the internal channel was full.
func (*Logger) ErrorCtx ¶ added in v1.1.0
ErrorCtx logs a message at LevelError with optional context extraction. If ctx is nil or contains no known keys, it behaves exactly like ErrorString with zero additional cost.
func (*Logger) ErrorString ¶
ErrorString logs a string message at LevelError with zero-copy string-to-byte conversion. Caller skip is 2.
func (*Logger) Flush ¶
func (l *Logger) Flush()
Flush forces an immediate flush of all pending log entries. It signals the worker to drain the channel and write the buffer, then blocks until the worker confirms completion (or times out after 1 second).
If no worker is running (globalWriter is nil), Flush drains and discards all pending entries to prevent channel blockage.
func (*Logger) InfoCtx ¶ added in v1.1.0
InfoCtx logs a message at LevelInfo with optional context extraction. If ctx is nil or contains no known keys, it behaves exactly like InfoString with zero additional cost.
func (*Logger) InfoString ¶
InfoString logs a string message at LevelInfo with zero-copy string-to-byte conversion. Caller skip is 2.
func (*Logger) RegisterSub ¶
RegisterSub registers a SubProfile for a specific logType.
COLD PATH: Call this during application initialization, NOT inside HTTP handlers or hot loops. The registry uses a copy-on-write strategy: a new immutable snapshot is created and swapped atomically, so concurrent hot-path reads are never blocked.
func (*Logger) ResetDrops ¶
func (l *Logger) ResetDrops()
ResetDrops resets the drop counter to zero.
func (*Logger) SetLevelValue ¶
SetLevelValue sets the current log level threshold atomically. Entries below this level are discarded in ~2ns with zero allocations.
func (*Logger) SetOnDrop ¶ added in v1.1.0
SetOnDrop registers a callback invoked whenever a log entry is dropped due to a full channel. The callback receives the current total drop count.
WARNING: This callback is invoked from the hot path. Keep it fast (e.g., atomic counter increment, metrics gauge update). Never perform I/O or acquire locks inside this callback.
func (*Logger) Start ¶
Start begins the worker goroutine that processes log entries. It writes to the configured OutputFile or stderr.
func (*Logger) StartWithWriter ¶
StartWithWriter begins the worker goroutine with a custom io.Writer.
Lifecycle synchronization:
- The started channel is closed (via sync.Once) when the worker begins, allowing callers to wait for readiness without polling or sleeping.
- The workerDone channel is closed when the worker exits (after draining remaining entries on context cancellation).
The Flush() method drains all pending channel entries before writing, guaranteeing no log loss on explicit flush.
type StdLogWriter ¶
type StdLogWriter struct {
// contains filtered or unexported fields
}
StdLogWriter wraps a Logger to implement the io.Writer interface. This allows the standard library "log" package (and third-party libraries that rely on it) to route their output through loggerj's async pipeline.
type SubOption ¶
type SubOption func(*SubProfile)
SubOption configures a SubProfile during RegisterSub.
func WithFields ¶
WithFields adds static key-value pairs that will be pre-baked into the JSON/Text prefixes. This avoids formatting these fields in the hot path.
func WithRateLimit ¶
WithRateLimit sets a lock-free rate limit for this specific logType. limit is the max logs per window. window is the duration (e.g., time.Second).
func WithSampleRate ¶
WithSampleRate sets a lock-free sampling rate for this logType. rate means 1 out of `rate` logs will be written (0 disables sampling).
type SubProfile ¶
type SubProfile struct {
Name string
// contains filtered or unexported fields
}
SubProfile represents a pre-compiled execution profile for a specific logType. Unlike traditional loggers that use mutexes and maps in the hot path, SubProfile holds lock-free atomic counters for rate limiting/sampling and pre-baked []byte prefixes for zero-CPU formatting.