Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

nlog

Go Reference Go Report Card License: MIT

nlog is a lightweight, high-performance structured logging library for Go with zero-allocation optimizations, async processing, and flexible configuration.

nlog is designed around immutable logger instances using the Builder pattern, making it inherently thread-safe. Logging is async by default with configurable overflow policies, and the hot path achieves zero allocations.

Features

Feature Description
Zero-Alloc Hot Path Entry pooling and level-check-before-allocation eliminate heap allocations
Async by Default Bounded queues with per-level overflow policies (Drop, Block)
Immutable Loggers Builder pattern ensures thread safety without read-path locking
Flexible Handlers Console, File (with rotation), MultiHandler fan-out
log/slog Integration Drop-in slog.Handler adapter for standard library compatibility
Structured Logging Type-safe field constructors (String, Int, Float64, Bool, Time, Duration, Err, Any)
JSON & Text Formatters Zero-copy WriterFormatter interface for both built-in formatters
File Rotation Built-in rotation by size, age, or interval with backup management
Telemetry Runtime statistics for monitoring drops, blocks, and throughput
CoarseClock Optional coarse-grained clock for reduced timestamp overhead

Motivation

Go's standard log package is minimal and log/slog focuses on compatibility across backends. nlog fills the gap for applications that need maximum throughput with zero allocations on the hot path, while still offering structured logging, async processing, and flexible output routing — all in a single, cohesive library.

Installation

go get github.com/philipp01105/nlog

Requires Go 1.24 or later.

Quick Start

The simplest way to use nlog is via the package-level default logger:

package main

import (
	"github.com/philipp01105/nlog/logger"
)

func main() {
	logger.Info("Application started")
	logger.Info("User login",
		logger.String("username", "alice"),
		logger.Int("user_id", 123),
	)
}

For more advanced usage, create a custom logger instance with the Builder:

package main

import (
	"github.com/philipp01105/nlog/formatter"
	"github.com/philipp01105/nlog/handler/consolehandler"
	"github.com/philipp01105/nlog/logger"
)

func main() {
	ch := consolehandler.NewConsoleHandler(consolehandler.ConsoleConfig{
		Async:      true,
		BufferSize: 1000,
		Formatter: formatter.NewTextFormatter(formatter.Config{
			IncludeCaller: true,
		}),
	})

	myLogger := logger.NewBuilder().
		WithHandler(ch).
		WithLevel(logger.DebugLevel).
		WithCaller(true).
		WithFields(logger.String("service", "api")).
		Build()

	myLogger.Debug("Debug message with caller info")
	myLogger.Info("Info message", logger.Int("count", 42))
}

With formatter.NewJSONFormatter, for easy parsing by logstash or Splunk:

{"level":"INFO","message":"JSON formatted log","response_time":0.123,"service":"api","time":"2026-02-18T13:00:00Z","timestamp":"2026-02-18T13:00:00Z"}

Fields

nlog encourages structured logging through type-safe field constructors instead of format strings:

logger.Info("User action",
	logger.String("username", "alice"),
	logger.Int("age", 30),
	logger.Int64("user_id", 123456789),
	logger.Float64("score", 98.5),
	logger.Bool("admin", true),
	logger.Time("login_time", time.Now()),
	logger.Duration("elapsed", 5*time.Second),
	logger.Err(err),
	logger.Any("custom", customObject),
)

Default Fields

Often it's helpful to have fields always attached to log statements in an application or parts of one. Instead of repeating fields on every line, use With() to create a child logger with persistent context (immutable operation):

requestLogger := logger.With(
	logger.String("request_id", "req-12345"),
	logger.String("method", "GET"),
)

requestLogger.Info("Processing request", logger.String("path", "/api/users"))
requestLogger.Info("Request completed", logger.Int("status", 200))

Logging Method Name

If you wish to add the calling method as a field, enable caller reporting:

myLogger := logger.NewBuilder().
	WithCaller(true).
	Build()

Note that this does add measurable overhead.

Level Logging

nlog has six logging levels: Debug, Info, Warning, Error, Fatal and Panic.

logger.Debug("Useful debugging information.")
logger.Info("Something noteworthy happened!")
logger.Warn("You should probably take a look at this.")
logger.Error("Something failed but I'm not quitting.")
// Calls os.Exit(1) after logging
logger.Fatal("Bye.")
// Calls panic() after logging
logger.Panic("I'm bailing.")

You can set the logging level on a Logger, then it will only log entries with that severity or anything above it:

myLogger := logger.NewBuilder().
	WithLevel(logger.InfoLevel). // Default. Will log Info and above.
	Build()

Formatters

The built-in logging formatters are:

  • formatter.NewTextFormatter — Human-readable text output with optional caller info.
  • formatter.NewJSONFormatter — Logs fields as JSON.

Both formatters support the zero-copy WriterFormatter interface for zero-allocation formatting.

You can define your own formatter by implementing the Formatter interface.

Handlers

nlog ships with multiple handler implementations, organized in sub-packages:

  • consolehandler.ConsoleHandler — Writes to stdout/stderr. Async by default.
  • filehandler.FileHandler — Writes to files with built-in rotation (by size, age, or interval).
  • multihandler.MultiHandler — Fan-out to multiple handlers simultaneously.
  • sloghandler.SlogHandler — Drop-in slog.Handler adapter for log/slog compatibility.
import (
	"github.com/philipp01105/nlog/handler/consolehandler"
	"github.com/philipp01105/nlog/handler/filehandler"
	"github.com/philipp01105/nlog/handler/multihandler"
)

ch := consolehandler.NewConsoleHandler(consolehandler.ConsoleConfig{Async: true})

fh, _ := filehandler.NewFileHandler(filehandler.FileConfig{
	Filename: "/var/log/app.log",
	Async:    true,
})

multi := multihandler.NewMultiHandler(ch, fh)

myLogger := logger.NewBuilder().
	WithHandler(multi).
	Build()

myLogger.Info("This goes to both console and file")

Synchronous Logging

Async is the default. To opt out, disable it per handler:

syncHandler := consolehandler.NewConsoleHandler(consolehandler.ConsoleConfig{
	Async: false,
})

File Rotation

Unlike many logging libraries, file rotation is built-in. Multiple rotation triggers and backup management are supported:

filehandler.NewFileHandler(filehandler.FileConfig{
	Filename:       "/var/log/app.log",
	MaxSize:        100 * 1024 * 1024,   // Rotate at 100MB
	MaxAge:         7 * 24 * time.Hour,   // Rotate after 7 days
	RotateInterval: 24 * time.Hour,       // Also rotate daily
	MaxBackups:     30,                    // Keep 30 old files
})

Overflow Policies

Control what happens when async queues fill up:

h := consolehandler.NewConsoleHandler(consolehandler.ConsoleConfig{
	OverflowPolicy: map[core.Level]handler.OverflowPolicy{
		core.DebugLevel: handler.DropNewest,
		core.ErrorLevel: handler.Block,
	},
	BlockTimeout: 100 * time.Millisecond,
	DrainTimeout: 5 * time.Second,
})

Available policies:

  • DropNewest — Drop incoming log entry (default for DEBUG/INFO/WARN)
  • DropOldest — Remove oldest entry from queue
  • Block — Block caller with timeout, fallback to sync write (default for ERROR)

Telemetry

Monitor logging behavior in real-time to detect slow I/O, tune buffer sizes, and observe application load:

stats := handler.Stats()
fmt.Printf("Processed: %d\n", stats.ProcessedTotal)
fmt.Printf("Dropped: %d\n", stats.DroppedTotal[core.InfoLevel])
fmt.Printf("Blocked: %d\n", stats.BlockedTotal)

log/slog Compatibility

nlog provides a drop-in slog.Handler adapter for seamless integration with Go's standard log/slog package:

package main

import (
	"log/slog"

	"github.com/philipp01105/nlog/core"
	"github.com/philipp01105/nlog/formatter"
	"github.com/philipp01105/nlog/handler/consolehandler"
	"github.com/philipp01105/nlog/handler/sloghandler"
)

func main() {
	ch := consolehandler.NewConsoleHandler(consolehandler.ConsoleConfig{
		Async:     false,
		Formatter: formatter.NewTextFormatter(formatter.Config{}),
	})

	sh := sloghandler.NewSlogHandler(ch, core.InfoLevel)
	logger := slog.New(sh)

	logger.Info("Hello from slog", "user", "alice", "count", 42)
	logger.Warn("Something might be wrong", "component", "auth")

	ch.Close()
}

Thread Safety

Logger instances are immutable after construction via the Builder pattern, making them inherently safe for concurrent use. Async handlers use bounded queues with configurable overflow policies. Shutdown is handled via an idempotent Close() with timeout-based queue draining.

Performance

nlog achieves zero allocations on the hot path through entry pooling, level-check-before-allocation, and the WriterFormatter zero-copy interface.

BenchmarkInfoNoFields              144 ns/op       0 B/op    0 allocs/op
BenchmarkInfoWith2Fields           202 ns/op       0 B/op    0 allocs/op
BenchmarkJSON                      445 ns/op       0 B/op    0 allocs/op
BenchmarkZeroAllocFiltered         0.3 ns/op       0 B/op    0 allocs/op
BenchmarkFilteredDebug             8.8 ns/op       0 B/op    0 allocs/op
BenchmarkMultiGoroutineContention   29 ns/op       5 B/op    0 allocs/op
BenchmarkQueueFullStress            18 ns/op       0 B/op    0 allocs/op

Filtered logs (level too low) cause zero allocations — the level check happens before GetEntry():

myLogger := logger.NewBuilder().
	WithLevel(logger.InfoLevel).
	Build()

myLogger.Debug("filtered") // 0.3 ns/op, 0 allocs

Package Structure

Package Description
core/ Core types (Entry, Field, Level) shared across packages
logger/ Main Logger API, Builder, and convenience functions
handler/ Handler interface, StatsProvider, OverflowPolicy, and Stats types
handler/consolehandler/ Console handler (sync/async) writing to io.Writer
handler/filehandler/ File handler (sync/async) with rotation support
handler/multihandler/ Fan-out handler dispatching to multiple children
handler/sloghandler/ Adapter for log/slog compatibility
formatter/ Formatter interface and implementations (Text, JSON, WriterFormatter)

Testing

Run tests:

go test ./...

Run benchmarks:

go test -bench=. -benchmem ./...

License

This project is open source and available under the MIT License.

About

A fast and low weight Golang logger

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages