loggerj

package module
v1.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 14 Imported by: 0

README

Go Version

loggerj

High-performance, zero-allocation asynchronous logging for Go.

loggerj uses a Pre-Compiled Execution Profile architecture. Rate limits, sampling, and static fields are baked into memory at startup. The hot path consists purely of atomic operations and memory copies — zero mutex locks, zero heap allocations, zero GC pressure.

Features

  • Asynchronous Pipeline: Non-blocking channel + dedicated worker goroutine.
  • Zero-Allocation Typed Fields: Int(), Bool(), Dur() without interface boxing.
  • Lock-Free Rate Limiting & Sampling: atomic.CompareAndSwap (CAS) with bounded backoff. Applies to the async hot path.
  • Pre-baked SubProfiles: Static fields formatted once at init, injected via memcpy at log time.
  • Sync Mode with Durability Tiers: 4 explicit tiers for audit trails. Direct and FsyncEveryWrite are lock-free (O_APPEND atomic); OSBuffered and FsyncEveryN hold a mutex around the shared bufio.Writer during buffer copy AND flush/fsync.
  • Native log rotation — size-based with backup retention, zero external dependencies. No time-based rotation, no compression. For daily rotation or gzip, use lumberjack via StartWithWriter().
  • slog.Handler Adapter: Routes standard log/slog calls through loggerj's zero-alloc pipeline.

Quick Start

package main

import (
    "context"
    "time"

    "github.com/uretgec/loggerj"
)

func main() {
    // 1. Initialize Logger
    logger := loggerj.NewLogger(loggerj.Config{
        JSONOutput:   true,
        FlushTimeout: 50 * time.Millisecond,
    })

    // 2. Define SubProfiles (COLD PATH: do this once at startup)
    logger.RegisterSub("HTTP",
        loggerj.WithRateLimit(1000, time.Second),
        loggerj.WithFields("env", "prod", "service", "gateway"),
    )

    // 3. Start the async worker
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    go logger.Start(ctx)
    defer logger.Close()

    // 4. Log messages (HOT PATH: zero allocation)
    logger.InfoString("HTTP", "request received", "method", "GET", "path", "/api")
    
    // 5. Typed fields for dynamic values (zero alloc)
    status := 200
    latency := 150 * time.Millisecond
    logger.InfoFields("HTTP", []byte("request completed"),
        loggerj.Int("status", status),
        loggerj.Dur("latency", latency),
        loggerj.Bool("cached", true),
    )

    // 6. slog integration (optional)
    // slogger := slog.New(loggerj.NewSlogHandler(logger, "APP"))
    
    // 7. Ensure all logs are written before exit
    logger.Flush()
}

Standard Library Integration

Intercept logs from Go's standard log package and third-party libraries:

log.SetFlags(0) // Disable std log timestamps; loggerj adds its own
log.SetOutput(logger.AsWriter(loggerj.LevelInfo, "STDLIB"))
log.Println("This message flows through loggerj's async pipeline")

Note: The AsWriter adapter is zero-allocation on Go 1.22+. On Go 1.21, it shows 1 alloc/op due to compiler escape analysis limitations across the io.Writer interface boundary. The native InfoString/InfoFields APIs are always zero-alloc.

Performance & Trade-offs

loggerj is optimized for async throughput and explicit durability guarantees.

  • Async Mode: Designed for high-QPS microservices, gateways, and proxies where log volume exceeds 1M logs/s and GC pauses must be avoided.
  • Sync Mode: Designed for audit trails and financial logs. Unlike other loggers that implicitly rely on OS page cache, loggerj explicitly documents crash-survival guarantees via DurabilityTier.

For detailed ns/op metrics, hardware benchmarks, and fair apples-to-apples comparisons with zap, zerolog, and slog, see BENCH.md. For architectural trade-offs, missing features (like nested JSON objects), and migration guides, see COMPARISON.md.

Documentation

  • EXAMPLES.md — Comprehensive examples for all features.
  • BENCH.md — Detailed benchmark methodology and results.
  • COMPARISON.md — Honest comparison with zap, zerolog, slog, logrus.
  • ROADMAP.md — Future vision and development roadmap.
  • CHANGELOG.md — Release notes and migration guides.

License

MIT License

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.

  1. 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.
  2. 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.
  3. 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 See BENCH.md for detailed benchmark results and methodology.

Index

Constants

View Source
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

	// SyncMode bypasses the async channel/worker pipeline and writes each log
	// entry directly to the underlying writer with a single atomic write()
	// syscall. When SyncMode is true:
	//
	//   - The log channel and worker goroutine are NOT created.
	//   - Start()/StartWithWriter() become no-ops.
	//   - Flush() becomes a no-op (writes are already synchronous).
	//   - Each log call performs: format → write() → return.
	//
	// The file is opened with O_APPEND, which the POSIX kernel guarantees
	// to be atomic for writes up to PIPE_BUF (typically 4096-65536 bytes).
	// This means concurrent goroutines writing to the same file will NOT
	// interleave their log lines — no mutex is needed.
	//
	// This mode is FIXED at creation time and cannot be toggled at runtime.
	// Use for audit trails, financial logs, or any scenario requiring
	// per-log write guarantees. Target: <400ns/op, ≤1 alloc/op.
	//
	// Default: false (async mode)
	SyncMode bool

	// DurabilityTier controls the sync-mode durability guarantee.
	// Only effective when SyncMode is true. Default: OSBuffered
	DurabilityTier DurabilityTier

	// FsyncEveryNCount is the number of writes before calling fsync(2).
	// Only effective when DurabilityTier is FsyncEveryN. Default: 100
	FsyncEveryNCount 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 DurabilityTier added in v1.4.0

type DurabilityTier uint8

DurabilityTier controls what guarantee SyncMode provides beyond the write(2) syscall itself. Higher tiers trade latency for a stronger promise about what survives a crash.

const (
	// OSBuffered (default): uses bufio.Writer with periodic flush (every
	// 10ms or 100 logs, whichever comes first). Each write is a buffer
	// copy (~5ns), not a syscall. Survives process crash. Does NOT survive
	// OS crash or power loss — data may still be in the page cache.
	//
	// This is the guarantee zap and zerolog provide, though neither states
	// it explicitly; loggerj states it here on purpose.
	//
	// Throughput: ~300ns/op (competitive with zerolog/zap sync mode).
	OSBuffered DurabilityTier = iota

	// Direct: one write(2) syscall per log entry, no buffering. Relies on
	// O_APPEND atomicity for concurrent safety. Survives process crash.
	// Does NOT survive OS crash or power loss.
	//
	// Throughput: ~1550ns/op (syscall overhead dominates).
	Direct

	// FsyncEveryN: calls fsync(2) after every N writes (configured via
	// Config.FsyncEveryNCount). Survives OS crash / power loss for
	// committed entries, at the cost of fsync latency (typically 1-10ms
	// on spinning disk, less on SSD/NVMe).
	//
	// Throughput: ~5000ns/op (fsync latency amortized over N logs).
	FsyncEveryN

	// FsyncEveryWrite: calls fsync(2) after every single write. Maximum
	// durability, minimum throughput. Intended for audit trails, not
	// high-volume application logs.
	//
	// Throughput: ~5000-10000ns/op (fsync on every log).
	FsyncEveryWrite
)

func (DurabilityTier) String added in v1.4.0

func (d DurabilityTier) String() string

String returns the human-readable name of the durability tier.

type Entry

type Entry struct {
	Level  Level
	Type   string
	Msg    []byte
	File   string
	Line   int
	Fields []string // legacy string-field API (Log, InfoString, ...)
	// FieldsV holds typed fields from the Field API (LogFields,
	// InfoFields, ...). An Entry uses exactly one of Fields or FieldsV
	// per log call, never both — the formatter checks FieldsV first.
	FieldsV []Field
	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.

func (*Entry) Reset

func (e *Entry) Reset()

Reset clears all fields of the Entry for reuse. Large slices (Msg > 4096 bytes, Fields > 64 elements) are released to the garbage collector to prevent permanent memory retention in the pool.

type Field added in v1.4.0

type Field struct {
	Key  string
	Type FieldType
	Num  uint64 // holds Int64/Uint64/Float64(bits)/Duration(ns)/Bool(0-1)
	Str  string // holds String value, or Error.Error() text
}

Field is a single structured log attribute. It carries its value in one of the untyped union members below instead of interface{}, so building a Field never allocates — the same guarantee zap.Field provides.

Size: 48 bytes (string key + uint8 type + uint64 num + string val). Passed by value — no pointer indirection, no heap escape.

func Bool added in v1.4.0

func Bool(key string, val bool) Field

Bool constructs a boolean field. Zero allocation.

func Dur added in v1.4.0

func Dur(key string, val time.Duration) Field

Dur constructs a duration field. Zero allocation — stored as nanoseconds.

func Err added in v1.4.0

func Err(err error) Field

Err constructs an error field with key "error". Returns a zero-value Field (skipped by the encoder) if err is nil — mirrors zap.Error's nil-safety. Zero allocation when err is nil.

func ErrWithKey added in v1.4.0

func ErrWithKey(key string, err error) Field

ErrWithKey constructs an error field with a custom key. Returns a zero-value Field (skipped by the encoder) if err is nil.

func Float64 added in v1.4.0

func Float64(key string, val float64) Field

Float64 constructs a float64 field. Zero allocation — bits are stored in Num via math.Float64bits.

func Int added in v1.4.0

func Int(key string, val int) Field

Int constructs an int field. Zero allocation — the value is stored directly in the Num union member, no boxing.

func Int64 added in v1.4.0

func Int64(key string, val int64) Field

Int64 constructs an int64 field. Zero allocation.

func Str added in v1.4.0

func Str(key, val string) Field

Str constructs a string field. Zero allocation.

func Uint64 added in v1.4.0

func Uint64(key string, val uint64) Field

Uint64 constructs a uint64 field. Zero allocation.

type FieldType added in v1.4.0

type FieldType uint8

FieldType identifies which union member of Field is populated, avoiding interface{} boxing on the hot path.

const (
	StringType   FieldType = iota // Str field: value in Str
	Int64Type                     // Int/Int64: value in Num (as int64 bits)
	Uint64Type                    // Uint64: value in Num
	Float64Type                   // Float64: value in Num (as float64 bits)
	BoolType                      // Bool: value in Num (0 or 1)
	DurationType                  // Dur: value in Num (nanoseconds)
	ErrorType                     // Err: value in Str (err.Error() text)
)

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
)

func (Level) String

func (l Level) String() string

String returns the human-readable name of the log level. Unknown levels return "UNKNOWN" without allocation.

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

func NewLogger(c Config) *Logger

NewLogger creates a new Logger instance. The logger is not started; call Start or StartWithWriter to begin processing.

func (*Logger) AsWriter

func (l *Logger) AsWriter(level Level, logType string) io.Writer

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"))

Zero-allocation guarantee: The Write method uses bytes.TrimRight (not strings.TrimRight) to remove trailing newlines without allocation. The underlying Log() method immediately copies msg via append(e.Msg[:0], msg...), satisfying the io.Writer contract (not retaining p after Write returns).

This adapter is suitable for intercepting legacy or third-party logs that use the standard library log package. For the application's primary high-throughput path, prefer the native logger.InfoString or logger.Info methods to avoid the function call overhead of the io.Writer interface.

func (*Logger) Close

func (l *Logger) Close() error

Close releases resources held by the logger. In async mode: flushes the buffered writer and closes the log file. In sync mode: flushes the shared buffered writer and closes the sync file.

func (*Logger) Debug

func (l *Logger) Debug(logType string, msg []byte, fields ...string)

Debug logs a message at LevelDebug. Caller skip is 2.

func (*Logger) DebugCtx added in v1.1.0

func (l *Logger) DebugCtx(ctx context.Context, logType string, msg string, fields ...string)

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) DebugFields added in v1.4.0

func (l *Logger) DebugFields(logType string, msg []byte, fields ...Field)

DebugFields logs a message at LevelDebug with typed fields. Caller skip is 2.

func (*Logger) DebugFieldsString added in v1.4.0

func (l *Logger) DebugFieldsString(logType string, msg string, fields ...Field)

DebugFieldsString logs a string message at LevelDebug with typed fields.

func (*Logger) DebugString

func (l *Logger) DebugString(logType string, msg string, fields ...string)

DebugString logs a string message at LevelDebug with zero-copy string-to-byte conversion. Caller skip is 2.

func (*Logger) Drops

func (l *Logger) Drops() uint64

Drops returns the total number of log entries dropped because the internal channel was full.

func (*Logger) Error

func (l *Logger) Error(logType string, msg []byte, fields ...string)

Error logs a message at LevelError. Caller skip is 2.

func (*Logger) ErrorCtx added in v1.1.0

func (l *Logger) ErrorCtx(ctx context.Context, logType string, msg string, fields ...string)

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) ErrorFields added in v1.4.0

func (l *Logger) ErrorFields(logType string, msg []byte, fields ...Field)

ErrorFields logs a message at LevelError with typed fields. Caller skip is 2.

func (*Logger) ErrorFieldsString added in v1.4.0

func (l *Logger) ErrorFieldsString(logType string, msg string, fields ...Field)

ErrorFieldsString logs a string message at LevelError with typed fields.

func (*Logger) ErrorString

func (l *Logger) ErrorString(logType string, msg string, fields ...string)

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, Flush drains and discards all pending entries to prevent channel blockage. This also fixes the 1-second block that occurred when OutputFile was set but Start() hadn't been called yet.

func (*Logger) GetLevel

func (l *Logger) GetLevel() Level

GetLevel returns the current log level threshold.

func (*Logger) Info

func (l *Logger) Info(logType string, msg []byte, fields ...string)

Info logs a message at LevelInfo. Caller skip is 2.

func (*Logger) InfoCtx added in v1.1.0

func (l *Logger) InfoCtx(ctx context.Context, logType string, msg string, fields ...string)

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) InfoFields added in v1.4.0

func (l *Logger) InfoFields(logType string, msg []byte, fields ...Field)

InfoFields logs a message at LevelInfo with typed fields. Caller skip is 2.

func (*Logger) InfoFieldsString added in v1.4.0

func (l *Logger) InfoFieldsString(logType string, msg string, fields ...Field)

InfoFieldsString logs a string message at LevelInfo with typed fields.

func (*Logger) InfoString

func (l *Logger) InfoString(logType string, msg string, fields ...string)

InfoString logs a string message at LevelInfo with zero-copy string-to-byte conversion. Caller skip is 2.

func (*Logger) Log

func (l *Logger) Log(level Level, logType string, msg []byte, fields ...string)

Log is the public core logging method. Caller skip is 1.

func (*Logger) LogFields added in v1.4.0

func (l *Logger) LogFields(level Level, logType string, msg []byte, fields ...Field)

LogFields logs a message with typed fields at the given level. This is the zero-allocation alternative to Log() with string fields. Caller skip is 1.

func (*Logger) RegisterSub

func (l *Logger) RegisterSub(logType string, opts ...SubOption)

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

func (l *Logger) SetLevelValue(level Level)

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

func (l *Logger) SetOnDrop(fn func(dropped uint64))

SetOnDrop registers a callback invoked whenever a log entry is dropped due to a full channel. Thread-safe; can be called concurrently with logging. Pass nil to unregister the callback.

func (*Logger) Start

func (l *Logger) Start(ctx context.Context)

Start begins the worker goroutine that processes log entries. It writes to the configured OutputFile or stderr.

func (*Logger) StartWithWriter

func (l *Logger) StartWithWriter(ctx context.Context, w io.Writer)

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.

func (*Logger) Stats

func (l *Logger) Stats() Stats

Stats returns a snapshot of logger statistics.

func (*Logger) Warn

func (l *Logger) Warn(logType string, msg []byte, fields ...string)

Warn logs a message at LevelWarn. Caller skip is 2.

func (*Logger) WarnCtx added in v1.1.0

func (l *Logger) WarnCtx(ctx context.Context, logType string, msg string, fields ...string)

WarnCtx logs a message at LevelWarn with optional context extraction. If ctx is nil or contains no known keys, it behaves exactly like WarnString with zero additional cost.

func (*Logger) WarnFields added in v1.4.0

func (l *Logger) WarnFields(logType string, msg []byte, fields ...Field)

WarnFields logs a message at LevelWarn with typed fields. Caller skip is 2.

func (*Logger) WarnFieldsString added in v1.4.0

func (l *Logger) WarnFieldsString(logType string, msg string, fields ...Field)

WarnFieldsString logs a string message at LevelWarn with typed fields.

func (*Logger) WarnString

func (l *Logger) WarnString(logType string, msg string, fields ...string)

WarnString logs a string message at LevelWarn with zero-copy string-to-byte conversion. Caller skip is 2.

type SlogHandler added in v1.4.0

type SlogHandler struct {
	// contains filtered or unexported fields
}

----------------------------------------------------------------------------- slog.Handler Adapter (Go 1.21+ Ecosystem Integration) -----------------------------------------------------------------------------

SlogHandler implements slog.Handler, routing all slog calls through loggerj's zero-allocation typed-field pipeline.

Group handling follows the loggerj Field model: nested groups are flattened to dotted keys. Example:

slog.Group("http", "method", "GET")

becomes:

"http.method":"GET"

This keeps the Field API allocation-free and works well with flat log pipelines such as Loki, Elasticsearch, and Datadog.

func NewSlogHandler added in v1.4.0

func NewSlogHandler(l *Logger, logType string) *SlogHandler

NewSlogHandler returns a slog.Handler that routes all records into the given Logger under the specified logType.

func (*SlogHandler) Enabled added in v1.4.0

func (h *SlogHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled implements slog.Handler.

func (*SlogHandler) Handle added in v1.4.0

func (h *SlogHandler) Handle(ctx context.Context, r slog.Record) error

Handle implements slog.Handler.

func (*SlogHandler) WithAttrs added in v1.4.0

func (h *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs implements slog.Handler.

func (*SlogHandler) WithGroup added in v1.4.0

func (h *SlogHandler) WithGroup(name string) slog.Handler

WithGroup implements slog.Handler.

Nested groups produce dotted prefixes:

WithGroup("outer").WithGroup("inner")

results in keys like:

"outer.inner.key"

type Stats added in v1.2.0

type Stats struct {
	Drops           uint64
	ChannelSize     uint64
	ChannelCap      uint64
	SyncWriteErrors uint64 // Non-zero means at least one sync-mode write failed
	RotationErrors  uint64 // Non-zero means at least one rotation step failed
}

Stats represents a snapshot of logger statistics. Using a struct instead of a map avoids heap allocations on every call, which is important for observability loops (e.g., Prometheus exporters) that poll Stats() frequently.

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.

func (*StdLogWriter) Write

func (w *StdLogWriter) Write(p []byte) (int, error)

Write implements the io.Writer interface. Trailing newlines from std log are trimmed for cleaner loggerj output. Uses bytes.TrimRight to avoid the string(p) allocation that occurred with strings.TrimRight.

Zero-allocation guarantee: The Log() method immediately copies msg via append(e.Msg[:0], msg...), so the io.Writer contract (not retaining p after Write returns) is satisfied. This eliminates the only documented allocation in the AsWriter adapter path.

type SubOption

type SubOption func(*SubProfile)

SubOption configures a SubProfile during RegisterSub.

func WithFields

func WithFields(fields ...string) SubOption

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

func WithRateLimit(limit int64, window time.Duration) SubOption

WithRateLimit sets a lock-free rate limit for this specific logType.

limit is the max logs per window. window is the duration, for example time.Second or 500 * time.Millisecond. Sub-second windows are supported.

GATE ORDER: Rate limiting is applied AFTER sampling. If you configure both WithSampleRate(10) and WithRateLimit(100), the rate limit applies to the *sampled* stream, not the raw input stream.

Example: 500 logs/s input, WithSampleRate(10), WithRateLimit(100):

  1. Sampling: 500/10 = 50 logs pass
  2. Rate limit: 50 < 100, so all 50 pass Result: ~50 logs/s output (not 100)

This design preserves hot-path performance by avoiding time.Now() syscalls (~34ns) on entries that would be discarded by sampling anyway.

If you need "max N raw attempts per second", do not combine sampling with rate limiting. Use rate limiting alone.

Exact counting supports limits up to rlMaxExactLimit (16,777,215). Larger limits are capped to that value.

func WithSampleRate

func WithSampleRate(rate int64) SubOption

WithSampleRate enables statistical sampling for this logType.

Only 1 out of every `rate` logs will be emitted. For example, WithSampleRate(10) means approximately 10% of logs pass through. Useful for high-volume debug traces where statistical representation is sufficient.

GATE ORDER: Sampling is applied BEFORE rate limiting. This is a performance optimization: sampling uses atomic.Add (~1ns), while rate limiting uses time.Now() (~34ns) + CAS (~2.5ns). By sampling first, we avoid the expensive syscall on the majority of logs that would be discarded anyway.

If you need rate limiting on the raw input stream (not the sampled subset), do not combine sampling with rate limiting. Use rate limiting alone.

The sampling counter is atomic and lock-free, supporting concurrent logging.

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.

Directories

Path Synopsis
tools
benchgate command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL