logger

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 8 Imported by: 0

README ΒΆ

go-logger

A zero-dependency, production-ready structured logging library for Go, built entirely on top of log/slog.

Go Reference Go Report Card License Go Version


go-logger extends the Go standard library's slog with production-grade middleware handlersβ€”async buffering, sensitive data redaction, probabilistic sampling, and per-component filteringβ€”without introducing a custom logger type. You keep using *slog.Logger everywhere in your codebase.

πŸ“‘ Table of Contents


✨ Features

Feature Description
πŸ›‘οΈ Zero Dependencies Core module uses only the Go standard library. No third-party imports, no supply chain risks.
🀝 slog-Native Works directly with *slog.Logger. No wrapper types, 100% compatible with the ecosystem.
⚑ AsyncHandler Non-blocking I/O via a background goroutine with configurable buffer and drop policies.
πŸ•΅οΈ RedactionHandler Protects sensitive data (PII) via key-based, pattern-based (Regex), and type-based redaction.
🎲 SamplingHandler Reduces log volume and storage costs with probabilistic filtering and per-level rates.
πŸŽ›οΈ ModuleHandler Per-component log level filtering with runtime hot-reload capabilities.
πŸ”€ MultiHandler Fan-out capability to write logs to multiple outputs simultaneously (e.g., stdout + file).
🧱 Builder API Fluent, safe API for composing middleware chains in the correct topological order.
♻️ Lifecycle Propagation Graceful shutdown with Flush() and Close() traversing safely through all middleware.
πŸšͺ Async-Safe Fatal Fatal()/Exit() flush and close the chain before terminating, so buffered records are never lost.

πŸ“¦ Installation

go get github.com/amhrmsn/go-logger

πŸš€ Quick Start

package main

import (
	"log/slog"
	"os"

	logger "github.com/amhrmsn/go-logger"
)

func main() {
	// Create a JSON logger with source location
	log := logger.NewJSON(os.Stdout,
		logger.WithLevel(slog.LevelDebug),
		logger.WithSource(true),
	)

	log.Info("application started",
		slog.String("version", "1.0.0"),
		logger.Component("api"),
	)

	log.Error("request failed",
		logger.Err(os.ErrNotExist),
		logger.TraceID("abc-12345"),
		slog.Int("status", 404),
	)
}

πŸ› οΈ Middleware Handlers

go-logger provides powerful slog.Handler wrappers that you can compose together.

AsyncHandler

Decouples log production from I/O by buffering records in a background goroutine. Crucial for high-throughput applications.

import "github.com/amhrmsn/go-logger/handler"

h := handler.NewAsyncHandler(
	slog.NewJSONHandler(os.Stdout, nil),
	handler.WithBufferSize(4096),
	handler.WithDropPolicy(handler.Block),         // Options: Block | DropNewest | SyncFallback
	handler.WithAsyncBypassLevel(slog.LevelError), // Errors are written synchronously to ensure they are never lost
)
defer h.Close()

log := slog.New(h)
log.Info("this writes instantly to memory channel")
log.Error("this bypasses the channel and writes synchronously")

RedactionHandler

Protects sensitive data using three complementary strategies: key matching, regex pattern matching, and strong typing.

h := handler.NewRedactionHandler(
	slog.NewJSONHandler(os.Stdout, nil),
	handler.WithRedactKeys("password", "ssn", "auth.token"), // Exact keys or dotted paths
	handler.WithRedactPatterns(`(?i)secret`, `(?i)token`),   // Regex patterns
)
log := slog.New(h)

log.Info("user login",
	slog.String("username", "alice"),
	slog.String("password", "s3cret"),                  // β†’ [REDACTED]
	slog.String("api_token", "eyJhb..."),               // β†’ [REDACTED] (pattern match)
	slog.Any("key", logger.Redacted("sk-1234")),        // β†’ [REDACTED] (type-based)
	slog.Any("cert", logger.SensitiveBytes(certBytes)), // β†’ [REDACTED:256 bytes]
)

SamplingHandler

Reduces log volume by probabilistically dropping log records. Extremely useful for high-traffic environments where storing 100% of logs is cost-prohibitive.

h := handler.NewSamplingHandler(
	slog.NewJSONHandler(os.Stdout, nil),
	handler.WithSampleRate(0.1),                    // Keep only 10% of records
	handler.WithSampleBypassLevel(slog.LevelError), // Never sample errors (keep 100%)
	handler.WithSampleByLevel(map[slog.Level]float64{
		slog.LevelDebug: 0.01, // Keep 1% of debug logs
		slog.LevelInfo:  0.1,  // Keep 10% of info logs
	}),
)

Note: Rates can be adjusted at runtime lock-free using h.SetRate(0.5).

ModuleHandler

Provides fine-grained, per-component log level filtering that can be updated at runtime without restarting the application.

config := handler.NewModuleConfig(slog.LevelInfo) // Default fallback level
config.SetLevel("database", slog.LevelDebug)      // Verbose DB logging
config.SetLevel("auth", slog.LevelWarn)           // Quiet auth logging

h := handler.NewModuleHandler(
	slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}),
	config,
)

dbLog := slog.New(h).With(logger.Component("database"))
dbLog.Debug("query executed")  // βœ… Appears (database is set to Debug)

authLog := slog.New(h).With(logger.Component("auth"))
authLog.Info("user logged in") // ❌ Filtered out (auth is set to Warn)

// Runtime hot-reload:
config.SetLevel("auth", slog.LevelDebug)
authLog.Debug("now visible")   // βœ… Appears immediately

// Or drive levels from an env var / config file / admin endpoint:
_ = config.SetLevels("database=debug,auth=warn,*=info")

MultiHandler

Fans out log records to multiple destinations (e.g., console and file) simultaneously.

h := handler.NewMultiHandler(
	slog.NewJSONHandler(os.Stdout, nil),           // Structured JSON to stdout
	slog.NewTextHandler(logFile, nil),             // Human-readable Text to file
)
log := slog.New(h)

πŸ—οΈ Builder Pattern

Composing multiple middlewares manually can be error-prone (e.g., placing Async before Redaction might leak PII into memory channels). The Builder API enforces the optimal topological order:

import (
	logger "github.com/amhrmsn/go-logger"
	"github.com/amhrmsn/go-logger/handler"
)

config := handler.NewModuleConfig(slog.LevelInfo)
config.SetLevel("database", slog.LevelDebug)

// Base handler
base := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})

// Build the chain safely
h := logger.NewBuilder(base).
	WithAsync(handler.WithBufferSize(4096), handler.WithDropPolicy(handler.Block)).
	WithRedaction(handler.WithRedactKeys("password", "token")).
	WithSampling(handler.WithSampleRate(1.0)).
	WithModuleFilter(config).
	Build()

log := slog.New(h)
defer logger.Close(h) // Safely cascades through all layers to close the AsyncHandler

Execution Order: ModuleHandler β†’ SamplingHandler β†’ RedactionHandler β†’ AsyncHandler β†’ JSONHandler


🧩 Request-Scoped Loggers

Attach a logger to a context.Context once, retrieve it anywhere below β€” the standard pattern for per-request logging with correlation IDs:

// In middleware:
log := logger.FromContext(ctx).With("request_id", reqID)
ctx = logger.NewContext(ctx, log)

// Deep inside a handler or service:
logger.FromContext(ctx).Info("query executed", "rows", n)
// Falls back to slog.Default() when the context carries no logger.

πŸ›‘ Graceful Shutdown

When using asynchronous buffering, it is critical to flush remaining logs to disk before the application exits.

import (
	"os"
	"os/signal"
	"syscall"
	logger "github.com/amhrmsn/go-logger"
)

// ... setup logger ...

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh // Wait for termination signal

// 1. Flush ensures all buffered records are written (blocks until queue is empty)
logger.Flush(h)

// 2. Close stops the background worker to prevent goroutine leaks
logger.Close(h)

logger.Flush() and logger.Close() automatically traverse the entire middleware chain to find the AsyncHandler.

Fatal Errors

Calling os.Exit directly after logging loses any records still queued in the async buffer. Use the async-safe helpers instead:

// Logs at Error level, flushes & closes the whole chain, then exits with code 1:
logger.Fatal(log, "cannot bind listener", "addr", addr, logger.Err(err))

// Non-logging variant with a custom exit code:
logger.Exit(log.Handler(), 2)

Both are best-effort with a 5-second timeout, so a stuck sink cannot hang a dying process.


πŸ“Š Benchmarks

Measured on AMD Ryzen AI 9 HX 370 (24 cores), Windows, amd64.

Benchmark ns/op B/op allocs/op
Disabled (Filtered out) 1.5 0 0
JSON 10 fields (Baseline) 451.6 218 2
Full Chain (All Middleware) 2439 1435 9
AsyncHandler 10 fields 2055 825 5
RedactionHandler 10 fields 913.7 833 6
SamplingHandler (Sampled out) 130.4 208 1
MultiHandler 2 outputs 1210 842 7
ModuleHandler 10 fields 509.5 218 2

Note: The full chain overhead comes primarily from deep cloning records (vital for async stack safety), regex pattern matching in redaction, and sync.RWMutex locks ensuring 100% thread safety.


πŸ“‚ Project Structure

go-logger/
β”œβ”€β”€ logger.go           # Core constructors: New(), NewJSON(), NewText(), SetDefault()
β”œβ”€β”€ options.go          # Config options: WithLevel(), WithSource()
β”œβ”€β”€ attributes.go       # Attribute helpers: Err(), Component(), TraceID()
β”œβ”€β”€ redact.go           # Strong types: Redacted, SensitiveBytes
β”œβ”€β”€ lifecycle.go        # Closer/Flusher/Unwrapper interfaces and chain traversal
β”œβ”€β”€ exit.go             # Exit()/Fatal(): flush-then-terminate helpers
β”œβ”€β”€ context.go          # NewContext()/FromContext(): request-scoped loggers
β”œβ”€β”€ builder.go          # Fluent Builder API
β”œβ”€β”€ handler/            # Core Middlewares
β”‚   β”œβ”€β”€ multi.go        # Fan-out
β”‚   β”œβ”€β”€ redaction.go    # PII masking
β”‚   β”œβ”€β”€ sampling.go     # Probabilistic dropping
β”‚   β”œβ”€β”€ module.go       # Component filtering
β”‚   └── async.go        # Non-blocking background worker
└── examples/           # Ready-to-run demonstration code

πŸ“ License

go-logger is distributed under the MIT License. See LICENSE for more details.

Documentation ΒΆ

Overview ΒΆ

Package logger provides a system-agnostic, reusable, modular logging library built on top of Go's standard library log/slog.

go-logger does not wrap or replace slog β€” it extends slog through composable slog.Handler middleware. The public API produces and consumes standard *slog.Logger instances, ensuring full ecosystem compatibility.

Core Features ΒΆ

  • Async logging with configurable backpressure (drop, block, sync-fallback)
  • Sensitive data redaction (type-level, key-based, pattern-based, nested groups)
  • Probabilistic and per-level log sampling
  • Per-module/component log level filtering with runtime hot-reload
  • Multi-output fan-out to multiple handlers simultaneously
  • Builder pattern for composing handler middleware chains
  • Graceful shutdown lifecycle (Flush/Close) with cascade support

Design Principles ΒΆ

  • Standard library first: zero third-party dependencies in the core module
  • System-agnostic: no domain-specific types (blockchain, HTTP, IoT, etc.)
  • All domain metadata is represented as generic slog.Attr key-value pairs
  • Composable middleware: all features are slog.Handler implementations
  • Immutable handlers: slog.Handler.WithAttrs and slog.Handler.WithGroup always return new instances; receivers are never mutated

Quick Start ΒΆ

log := logger.NewJSON(os.Stdout, logger.WithLevel(slog.LevelInfo))
log.Info("server started", "port", 8080)

Builder Pattern ΒΆ

log := logger.NewBuilder(slog.NewJSONHandler(os.Stdout, nil)).
    WithRedaction(handler.WithRedactKeys("password", "token")).
    WithAsync(handler.WithBufferSize(4096)).
    BuildLogger()
defer logger.Close(log.Handler())

Index ΒΆ

Examples ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

This section is empty.

Functions ΒΆ

func Close ΒΆ

func Close(h slog.Handler) error

Close attempts to close the given handler by checking if it implements Closer. If it does not, Close recursively unwraps the handler chain (via the Unwrap() method) to find a Closer in the middleware stack.

Usage in application shutdown:

h := log.Handler()
if err := logger.Close(h); err != nil {
    fmt.Fprintf(os.Stderr, "logger close: %v\n", err)
}

func CloseContext ΒΆ

func CloseContext(ctx context.Context, h slog.Handler) error

CloseContext is like Close but accepts a context for deadline and cancellation support. It prefers ContextCloser if implemented, falling back to Closer.

CloseContext propagates through the entire middleware chain: after calling a handler's lifecycle method, it continues unwrapping to close inner handlers as well. This ensures that all handlers in the chain (including MultiHandler children) receive the close signal.

Errors from multiple handlers are aggregated using errors.Join.

func Component ΒΆ

func Component(name string) slog.Attr

Component creates a slog.Attr with the key "component" for module or subsystem identification.

This attribute is used by [ModuleHandler] to apply per-component log level filtering. The component name should identify the subsystem, not the application domain.

log := slog.New(h).With(logger.Component("networking"))
log.Info("listening", "port", 9000)
Example ΒΆ
package main

import (
	"log/slog"
	"os"

	logger "github.com/amhrmsn/go-logger"
	"github.com/amhrmsn/go-logger/handler"
)

// removeTime strips the time attribute so example output is deterministic.
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey && len(groups) == 0 {
		return slog.Attr{}
	}
	return a
}

func main() {
	config := handler.NewModuleConfig(slog.LevelInfo)
	config.SetLevel("database", slog.LevelDebug)

	base := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level:       slog.LevelDebug,
		ReplaceAttr: removeTime,
	})
	log := slog.New(handler.NewModuleHandler(base, config))

	dbLog := log.With(logger.Component("database"))
	dbLog.Debug("query executed") // logged: database is set to Debug

	apiLog := log.With(logger.Component("api"))
	apiLog.Debug("parsing request") // filtered: api uses the Info default
	apiLog.Info("request handled")

}
Output:
{"level":"DEBUG","msg":"query executed","component":"database"}
{"level":"INFO","msg":"request handled","component":"api"}

func Default ΒΆ

func Default() *slog.Logger

Default returns the current default *slog.Logger.

This is a convenience wrapper around slog.Default.

func Err ΒΆ

func Err(err error) slog.Attr

Err creates a slog.Attr for an error value with the key "error".

This is a convenience helper that ensures consistent key naming for error attributes across an application.

if err != nil {
    log.Error("operation failed", logger.Err(err))
}

func Exit ΒΆ

func Exit(h slog.Handler, code int)

Exit flushes and closes the handler chain, then terminates the process with the given status code.

Calling os.Exit directly after logging loses any records still queued in an handler.AsyncHandler buffer: the process dies before the background worker drains them. Exit closes that gap by running FlushContext and CloseContext over the entire middleware chain first.

The flush and close are best-effort: they share a 5-second timeout and their errors are discarded, because the process is terminating either way.

log.Error("unrecoverable", logger.Err(err))
logger.Exit(log.Handler(), 1)

func Fatal ΒΆ

func Fatal(log *slog.Logger, msg string, args ...any)

Fatal logs the message at slog.LevelError with the given arguments, then calls Exit with status code 1.

This is the async-safe replacement for the common pattern of logging an error followed by os.Exit: buffered records β€” including the fatal message itself β€” are flushed before the process terminates.

logger.Fatal(log, "cannot bind listener", "addr", addr, logger.Err(err))

func Flush ΒΆ

func Flush(h slog.Handler) error

Flush attempts to flush the given handler by checking if it implements Flusher. If it does not, Flush recursively unwraps the handler chain (via the Unwrap() method) to find a Flusher in the middleware stack.

After Flush returns, all records submitted before the Flush call are guaranteed to have been written to the underlying output.

func FlushContext ΒΆ

func FlushContext(ctx context.Context, h slog.Handler) error

FlushContext is like Flush but accepts a context for deadline and cancellation support. It prefers ContextFlusher if implemented, falling back to Flusher.

FlushContext propagates through the entire middleware chain: after calling a handler's lifecycle method, it continues unwrapping to flush inner handlers as well.

Errors from multiple handlers are aggregated using errors.Join.

func FromContext ΒΆ

func FromContext(ctx context.Context) *slog.Logger

FromContext returns the *slog.Logger stored in ctx by NewContext.

If ctx is nil or carries no logger, slog.Default is returned, so the result is always safe to use.

func New ΒΆ

func New(h slog.Handler) *slog.Logger

New creates a *slog.Logger with the given handler.

This is a thin convenience wrapper around slog.New. The handler is typically built using the Builder or composed manually from handler middleware.

func NewContext ΒΆ

func NewContext(ctx context.Context, log *slog.Logger) context.Context

NewContext returns a copy of ctx that carries log.

This enables the common request-scoped logger pattern: attach a logger enriched with request attributes once, then retrieve it anywhere below with FromContext.

log := logger.FromContext(ctx).With("request_id", id)
ctx = logger.NewContext(ctx, log)

func NewJSON ΒΆ

func NewJSON(w io.Writer, opts ...Option) *slog.Logger

NewJSON creates a *slog.Logger with a slog.JSONHandler writing to w.

Options are applied to the underlying slog.HandlerOptions. If no options are provided, the handler uses default settings (Info level, no source).

Example ΒΆ
package main

import (
	"log/slog"
	"os"

	logger "github.com/amhrmsn/go-logger"
)

// removeTime strips the time attribute so example output is deterministic.
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey && len(groups) == 0 {
		return slog.Attr{}
	}
	return a
}

func main() {
	log := logger.NewJSON(os.Stdout, logger.WithReplaceAttr(removeTime))
	log.Info("server started", "port", 8080)
}
Output:
{"level":"INFO","msg":"server started","port":8080}

func NewText ΒΆ

func NewText(w io.Writer, opts ...Option) *slog.Logger

NewText creates a *slog.Logger with a slog.TextHandler writing to w.

Options are applied to the underlying slog.HandlerOptions. If no options are provided, the handler uses default settings (Info level, no source).

func SetDefault ΒΆ

func SetDefault(l *slog.Logger)

SetDefault sets the default *slog.Logger used by the top-level functions in log/slog.

This is a convenience wrapper around slog.SetDefault.

func SpanID ΒΆ

func SpanID(id string) slog.Attr

SpanID creates a slog.Attr with the key "span_id" for distributed trace span correlation.

slog.InfoContext(ctx, "handling request",
    logger.TraceID(tid),
    logger.SpanID(sid),
)

func TraceID ΒΆ

func TraceID(id string) slog.Attr

TraceID creates a slog.Attr with the key "trace_id" for distributed trace correlation.

In applications using OpenTelemetry or similar tracing systems, include the trace ID in log records to enable log-trace correlation in your observability backend.

slog.InfoContext(ctx, "processing",
    logger.TraceID(extractTraceID(ctx)),
)

Types ΒΆ

type Builder ΒΆ

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

Builder provides a fluent API for composing slog.Handler middleware chains.

The builder records configurations for each middleware layer and composes them in a fixed order when [Build] or [BuildLogger] is called.

Composition order (innermost β†’ outermost):

base β†’ AsyncHandler β†’ RedactionHandler β†’ SamplingHandler β†’ ModuleHandler β†’ custom middleware

This means ModuleHandler is checked first at call time (outermost), followed by SamplingHandler, RedactionHandler, and finally AsyncHandler wraps the base handler directly.

Example:

log := logger.NewBuilder(slog.NewJSONHandler(os.Stdout, nil)).
    WithRedaction(handler.WithRedactKeys("password", "token")).
    WithAsync(handler.WithBufferSize(4096)).
    BuildLogger()
defer logger.Close(log.Handler())

func NewBuilder ΒΆ

func NewBuilder(base slog.Handler) *Builder

NewBuilder creates a Builder with the given base handler.

The base handler is the innermost handler in the chain β€” typically a slog.JSONHandler or slog.TextHandler.

Example ΒΆ
package main

import (
	"log/slog"
	"os"

	logger "github.com/amhrmsn/go-logger"
	"github.com/amhrmsn/go-logger/handler"
)

// removeTime strips the time attribute so example output is deterministic.
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey && len(groups) == 0 {
		return slog.Attr{}
	}
	return a
}

func main() {
	base := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ReplaceAttr: removeTime})

	log := logger.NewBuilder(base).
		WithRedaction(handler.WithRedactKeys("password")).
		BuildLogger()

	log.Info("user login", "user", "alice", "password", "s3cret")
}
Output:
{"level":"INFO","msg":"user login","user":"alice","password":"[REDACTED]"}

func (*Builder) Build ΒΆ

func (b *Builder) Build() slog.Handler

Build composes the handler chain and returns the outermost handler.

Composition order (innermost β†’ outermost):

base β†’ AsyncHandler β†’ RedactionHandler β†’ SamplingHandler β†’ ModuleHandler β†’ custom middleware

Each middleware is only included if it was configured via the corresponding With*() method.

Call Build (or Builder.BuildLogger) at most once per Builder. Every call composes a fresh chain around the same base handler; with Builder.WithAsync configured, each call starts its own background worker goroutine that must be closed independently.

func (*Builder) BuildLogger ΒΆ

func (b *Builder) BuildLogger() *slog.Logger

BuildLogger composes the handler chain and returns a *slog.Logger.

This is equivalent to calling slog.New(b.Build()).

func (*Builder) WithAsync ΒΆ

func (b *Builder) WithAsync(opts ...handler.AsyncOption) *Builder

WithAsync enables the handler.AsyncHandler middleware with the given options.

The async handler wraps the base handler directly (innermost middleware), buffering records in a channel for background processing.

func (*Builder) WithMiddleware ΒΆ

func (b *Builder) WithMiddleware(mw func(slog.Handler) slog.Handler) *Builder

WithMiddleware adds a custom middleware function to the chain.

Custom middleware is applied after all built-in middleware (outermost layer). Multiple calls to WithMiddleware append to the chain in registration order: the first registered middleware wraps the built-in chain, the second wraps the first, and so on. Therefore, the LAST registered middleware becomes the outermost handler and is executed FIRST at log time.

Example with two custom middlewares:

builder.WithMiddleware(mwA).WithMiddleware(mwB).Build()
// Composition: base β†’ ... β†’ ModuleHandler β†’ mwA β†’ mwB
// Execution:   mwB β†’ mwA β†’ ModuleHandler β†’ ... β†’ base

func (*Builder) WithModuleFilter ΒΆ

func (b *Builder) WithModuleFilter(cfg *handler.ModuleConfig) *Builder

WithModuleFilter enables the handler.ModuleHandler middleware with the given configuration.

The module handler applies per-component log level filtering. It is the outermost built-in middleware, so it is checked first.

func (*Builder) WithRedaction ΒΆ

func (b *Builder) WithRedaction(opts ...handler.RedactOption) *Builder

WithRedaction enables the handler.RedactionHandler middleware with the given options.

The redaction handler inspects and redacts sensitive attributes before they reach the base handler (or async handler).

func (*Builder) WithSampling ΒΆ

func (b *Builder) WithSampling(opts ...handler.SampleOption) *Builder

WithSampling enables the handler.SamplingHandler middleware with the given options.

The sampling handler applies probabilistic filtering to reduce log volume.

type Closer ΒΆ

type Closer interface {
	Close() error
}

Closer represents a slog.Handler that holds resources requiring cleanup.

Handlers that open files, network connections, or background goroutines should implement this interface to support graceful shutdown.

type ContextCloser ΒΆ

type ContextCloser interface {
	CloseContext(ctx context.Context) error
}

ContextCloser is like Closer but accepts a context for deadline and cancellation support. Handlers should prefer implementing this interface to allow callers to bound shutdown time.

type ContextFlusher ΒΆ

type ContextFlusher interface {
	FlushContext(ctx context.Context) error
}

ContextFlusher is like Flusher but accepts a context for deadline and cancellation support.

type Flusher ΒΆ

type Flusher interface {
	Flush() error
}

Flusher represents a slog.Handler that buffers data internally.

Handlers that buffer log records (such as [AsyncHandler]) should implement this interface to allow callers to ensure all buffered records are written before inspecting output or shutting down.

type Option ΒΆ

type Option func(*options)

Option configures the base slog.Handler created by NewJSON and NewText.

func WithLevel ΒΆ

func WithLevel(level slog.Leveler) Option

WithLevel sets the minimum log level for the handler.

Any slog.Leveler can be used, including a *slog.LevelVar for dynamic runtime level changes.

func WithLevelVar ΒΆ

func WithLevelVar(lv *slog.LevelVar) Option

WithLevelVar sets a dynamic log level that can be changed at runtime without restarting the application.

This is equivalent to calling WithLevel with the *slog.LevelVar, but communicates the intent more clearly.

The slog.LevelVar is safe for concurrent use by multiple goroutines.

func WithReplaceAttr ΒΆ

func WithReplaceAttr(fn func(groups []string, a slog.Attr) slog.Attr) Option

WithReplaceAttr sets a function that is called for each non-group slog.Attr before it is logged. The function can modify, replace, or remove attributes.

This is useful for:

  • Redacting sensitive fields by key name
  • Customizing timestamp or source location formatting
  • Removing unwanted built-in attributes

See slog.HandlerOptions.ReplaceAttr for details on the function signature.

func WithSource ΒΆ

func WithSource(enabled bool) Option

WithSource enables or disables source code location (file, line, function) in log output.

Enabling source location has a performance cost due to runtime.Caller stack introspection. Consider enabling it only in development or for specific debugging scenarios.

type Redacted ΒΆ

type Redacted string

Redacted is a string type that always logs as "[REDACTED]".

Implementing slog.LogValuer, any value of this type will automatically have its contents hidden when logged, regardless of the handler or redaction middleware in use.

This provides compile-time safety: sensitive types redact themselves, and callers cannot accidentally bypass the redaction.

type Config struct {
    APIKey logger.Redacted
    Host   string
}
// When logged: {"APIKey":"[REDACTED]","Host":"example.com"}
Example ΒΆ
package main

import (
	"log/slog"
	"os"

	logger "github.com/amhrmsn/go-logger"
)

// removeTime strips the time attribute so example output is deterministic.
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey && len(groups) == 0 {
		return slog.Attr{}
	}
	return a
}

func main() {
	log := logger.NewJSON(os.Stdout, logger.WithReplaceAttr(removeTime))
	log.Info("config loaded",
		"api_key", logger.Redacted("sk-1234-secret"),
		"host", "example.com",
	)
}
Output:
{"level":"INFO","msg":"config loaded","api_key":"[REDACTED]","host":"example.com"}

func (Redacted) LogValue ΒΆ

func (r Redacted) LogValue() slog.Value

LogValue implements slog.LogValuer. It always returns "[REDACTED]", hiding the underlying string value.

type SensitiveBytes ΒΆ

type SensitiveBytes []byte

SensitiveBytes is a []byte type that logs its length but not its content.

This is useful for binary secrets (encryption keys, raw tokens, etc.) where knowing the size is helpful for debugging but the content must never appear in logs.

slog.Info("key loaded", "key", logger.SensitiveBytes(privateKeyBytes))
// Output: {"msg":"key loaded","key":"[REDACTED:32 bytes]"}
Example ΒΆ
package main

import (
	"log/slog"
	"os"

	logger "github.com/amhrmsn/go-logger"
)

// removeTime strips the time attribute so example output is deterministic.
func removeTime(groups []string, a slog.Attr) slog.Attr {
	if a.Key == slog.TimeKey && len(groups) == 0 {
		return slog.Attr{}
	}
	return a
}

func main() {
	log := logger.NewJSON(os.Stdout, logger.WithReplaceAttr(removeTime))
	log.Info("key loaded", "key", logger.SensitiveBytes([]byte{0xDE, 0xAD, 0xBE, 0xEF}))
}
Output:
{"level":"INFO","msg":"key loaded","key":"[REDACTED:4 bytes]"}

func (SensitiveBytes) LogValue ΒΆ

func (s SensitiveBytes) LogValue() slog.Value

LogValue implements slog.LogValuer. It returns a string indicating the byte length without exposing the content.

type Unwrapper ΒΆ

type Unwrapper interface {
	Unwrap() slog.Handler
}

Unwrapper is implemented by middleware handlers that wrap an inner handler.

Implementing Unwrapper lets Close, CloseContext, Flush, and FlushContext traverse past a handler to reach lifecycle-aware handlers (such as handler.AsyncHandler) deeper in the chain. All middleware in this library implements it; third-party middleware that wraps another handler should too, otherwise the chain traversal stops at that handler and inner resources are never flushed or closed.

Directories ΒΆ

Path Synopsis
examples
async command
Package main demonstrates the AsyncHandler with graceful shutdown.
Package main demonstrates the AsyncHandler with graceful shutdown.
basic command
Package main demonstrates the basic usage of go-logger.
Package main demonstrates the basic usage of go-logger.
full-chain command
Package main demonstrates the full go-logger middleware chain using the Builder.
Package main demonstrates the full go-logger middleware chain using the Builder.
module-filter command
Package main demonstrates the ModuleHandler for per-component log level filtering.
Package main demonstrates the ModuleHandler for per-component log level filtering.
multi-output command
Package main demonstrates the MultiHandler for fan-out to multiple outputs.
Package main demonstrates the MultiHandler for fan-out to multiple outputs.
redaction command
Package main demonstrates the RedactionHandler for protecting sensitive data.
Package main demonstrates the RedactionHandler for protecting sensitive data.
Package handler provides composable slog.Handler middleware for the go-logger library.
Package handler provides composable slog.Handler middleware for the go-logger library.
internal
record
Package record provides internal utilities for safe manipulation of slog.Record values.
Package record provides internal utilities for safe manipulation of slog.Record values.

Jump to

Keyboard shortcuts

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