logutil

package
v1.153.0 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package logutil provides configuration-driven logging utilities built around Go's standard log/slog package.

It wraps slog with a composable configuration model:

Output can be JSON, text console, or discard mode. The severity model extends the syslog-style levels (emergency through debug) with a trace level.

Notes

The handlers built by Config.SlogHandler and NewSlogTraceIDHandler write a record through the standard library's JSON or text handler, but they filter it first (see the sanitizing handler documented on NewSlogTraceIDHandler), so the output deliberately differs from a bare slog handler's in three ways. Two are repairs of shapes the standard library encodes incorrectly:

  • A group whose members all render nothing is dropped. slog rolls the buffer back past such a group without closing it, so the next attribute is written with no separator: invalid JSON, or, in text format, a field silently renamed with the dead group's prefix.
  • A time.Time whose year falls outside [0,9999] is rewritten as an RFC 3339 string. slog's JSON encoder writes an "!ERROR:" string for it and then writes the value as well, putting two JSON strings under one key.

The third keeps the two backends of this module interchangeable, since logsrv encodes through zerolog:

  • A nil-pointer error writes no field, where slog renders it as the string "<nil>". A group left empty by one is dropped with it, and a typed nil logged under the trace ID key no longer suppresses the injected trace ID, which would otherwise correlate the record by the string "<nil>". A nil error of any other kind (a nil slice, map, func or channel, the shape of aggregate errors such as validator.ValidationErrors) still renders as its message.

A group that resolves to zero members is likewise dropped rather than written as a bare "{}".

Index

Constants

View Source
const TraceIDKey = "trace_id"

TraceIDKey is the record attribute key used to carry the trace ID. It is the single source of truth for the field name across nurago's logging packages.

Variables

This section is empty.

Functions

func LevelName

func LevelName(l LogLevel) string

LevelName returns the string name of the specified log level (e.g., "error", "debug").

slog levels are arbitrary integers, so a level that is not one of the named severities has no name here. It falls back to slog's own banded form ("INFO+1", "WARN-2"), never to a bare number: a bare number would collide with the numeric syslog vocabulary that ParseLevel accepts, where "1" means alert, so a record one notch above info would be read back as a near-fatal severity.

func NewLogFromSlog

func NewLogFromSlog(logger *slog.Logger) *log.Logger

NewLogFromSlog constructs a standard log.Logger that routes writes to an slog.Logger.

func NewSlogTraceIDHandler

func NewSlogTraceIDHandler(h slog.Handler, f TraceIDFunc) slog.Handler

NewSlogTraceIDHandler wraps h so each record gains a trace ID attribute resolved, per record, from f. A nil f returns h unchanged (no trace ID attribute is added), mirroring how a nil TraceIDFunc is treated elsewhere in the package. Resolving the trace ID per record (rather than once at construction) lets a dynamic TraceIDFunc reflect the current request/context on every line. The trace ID is emitted at the root of the record even when the logger is derived with WithGroup.

A nil h falls back to the handler of the current slog.Default, captured now, so the returned handler never panics on first use.

The result is wrapped in the sanitizing handler (see slogSanitizeHandler), so records and derived attributes are stripped of the groups that render nothing before the trace ID is injected: that injected attribute is exactly the one the standard library's elided-group separator bug would leave without its comma. Attributes already applied to h before it is wrapped do not pass through the filter, and are not covered.

Neither is a slog.HandlerOptions.ReplaceAttr callback installed on h: it runs below this handler and can empty a group the filter has already passed by deleting its members (returning the zero slog.Attr), which re-creates the separator bug and can strip the trace ID. Use Config.SlogHandler, whose only callback never deletes, or supply a handler whose ReplaceAttr does not delete attributes.

And the filter repairs a time.Time that slog's JSON encoder cannot write (a year outside [0,9999], which it renders as an "!ERROR:" string followed by the value, making the line invalid) only where it is an *attribute*. A record's own timestamp is not an attribute and never reaches the filter; it is repaired by the ReplaceAttr callback Config.SlogHandler installs (see replaceLevelName), which this constructor cannot install on a handler it merely wraps. A hand-built record carrying such a timestamp (slog.Logger always stamps time.Now(), so only a middleware, tee or replay handler can produce one) therefore still yields an invalid line here. Use Config.SlogHandler, or give h a ReplaceAttr that rewrites slog.TimeKey.

A caller-supplied root-level trace ID wins over the injected one (see Handle). Attributes already applied to h before it is wrapped are invisible to that check too: apply them through the returned handler's WithAttrs, or use Config.SlogHandler, which accounts for Config.CommonAttr.

func ValidFormat

func ValidFormat(f LogFormat) bool

ValidFormat reports whether the given log format is recognized.

func ValidLevel

func ValidLevel(l LogLevel) bool

ValidLevel reports whether the given log level is recognized.

Types

type Attr

type Attr = slog.Attr

Attr aliases slog.Attr for package-local configuration types.

type Config

type Config struct {
	Out        io.Writer
	Format     LogFormat
	Level      LogLevel
	CommonAttr []Attr
	HookFn     HookFunc
	TraceIDFn  TraceIDFunc
	Source     bool
}

Config holds common logger parameters.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a pre-initialized Config with stderr output, JSON format, info level, and empty trace ID.

func NewConfig

func NewConfig(opts ...Option) (*Config, error)

NewConfig constructs a Config by applying options to DefaultConfig.

func (*Config) OutWriter added in v1.149.0

func (c *Config) OutWriter() io.Writer

OutWriter returns the effective output destination: Config.Out, or os.Stderr when Out is unusable: nil, or a typed nil (a nil *os.File, say, held in a non-nil io.Writer interface), which Out being an exported field allows even though WithOutWriter rejects it. Both backends resolve the destination through it, so a hand-built Config never yields a handler that panics on the first write.

func (*Config) SlogDefaultLogger

func (c *Config) SlogDefaultLogger() *slog.Logger

SlogDefaultLogger constructs a slog.Logger from Config settings and installs it as the process default.

As a side effect of slog.SetDefault, this also redirects the standard library log package's default output through the returned logger's handler. Use SlogLogger to obtain a logger without mutating that global state.

func (*Config) SlogHandler

func (c *Config) SlogHandler() slog.Handler

SlogHandler constructs a slog.Handler from Config settings with optional hook interception.

func (*Config) SlogLogger

func (c *Config) SlogLogger() *slog.Logger

SlogLogger constructs a slog.Logger from Config settings (format, level, common attributes, hooks).

type HookFunc

type HookFunc func(level LogLevel, message string)

HookFunc is used to intercept the log message before passing it to the underlying handler.

type LogFormat

type LogFormat int8

LogFormat selects how log records are encoded for output.

const (
	FormatNone    LogFormat = -1 // Discard the logs.
	FormatJSON    LogFormat = 0  // Prints the logs in JSON format.
	FormatConsole LogFormat = 1  // Prints the logs in a human friendly format.
)

func ParseFormat

func ParseFormat(f string) (LogFormat, error)

ParseFormat converts a string ("json", "console", "none"/"discard"/"noop") to a log format. For unrecognized input it returns FormatJSON together with an error, so a caller that ignores the error degrades to visible JSON logs rather than silently discarding output (which returning FormatNone would cause).

type LogLevel

type LogLevel = slog.Level

LogLevel is an alias for slog.Level to represent extended log severity levels.

const (
	LevelEmergency LogLevel = 64 // (+) 0 - Emergency - System is unusable.
	LevelAlert     LogLevel = 32 // (+) 1 - Alert - Immediate action required.
	LevelCritical  LogLevel = 16 // (+) 2 - Critical - Critical conditions.
	LevelError     LogLevel = 8  // (=) 3 - Error - Error conditions.
	LevelWarning   LogLevel = 4  // (=) 4 - Warning - Warning conditions.
	LevelNotice    LogLevel = 2  // (+) 5 - Notice - Normal but noteworthy events.
	LevelInfo      LogLevel = 0  // (=) 6 - Informational - General informational messages.
	LevelDebug     LogLevel = -4 // (=) 7 - Debug - Detailed debugging information.
	LevelTrace     LogLevel = -8 // (+) Additional Trace level when supported.
)

Extended slog levels.

func ParseLevel

func ParseLevel(l string) (LogLevel, error)

ParseLevel converts syslog-style level strings ("0"-"7", syslog names, or "trace") to log levels. For unrecognized input it returns LevelInfo together with an error, so a caller that ignores the error degrades to a safe, non-verbose level rather than to debug output.

type Option

type Option func(*Config) error

Option configures a Config instance.

func WithCommonAttr

func WithCommonAttr(a ...Attr) Option

WithCommonAttr sets the attributes attached to every log record, replacing any previously configured common attributes.

func WithFormat

func WithFormat(f LogFormat) Option

WithFormat overrides the log output format (JSON, console, or discard).

func WithFormatStr

func WithFormatStr(f string) Option

WithFormatStr overrides the log format using a string ("json", "console", "none").

func WithHookFn

func WithHookFn(f HookFunc) Option

WithHookFn adds a callback that intercepts each log record before the underlying handler processes it.

func WithLevel

func WithLevel(l LogLevel) Option

WithLevel overrides the minimum log level to emit.

func WithLevelStr

func WithLevelStr(l string) Option

WithLevelStr overrides the log level using a string (e.g., "error", "debug", "trace").

func WithOutWriter

func WithOutWriter(w io.Writer) Option

WithOutWriter overrides the output destination for log messages. A nil writer is rejected so misconfiguration fails at construction instead of panicking on the first log write. A typed nil (a nil *os.File, say, held in a non-nil io.Writer interface) is rejected too: it panics just the same.

func WithSource

func WithSource(enabled bool) Option

WithSource enables or disables source location (file:line) annotation on each record. It is disabled by default to avoid the per-record runtime.CallersFrames cost.

func WithTraceIDFn

func WithTraceIDFn(f TraceIDFunc) Option

WithTraceIDFn adds a callback that dynamically retrieves the trace ID for each record.

type SlogHookHandler

type SlogHookHandler struct {
	slog.Handler
	// contains filtered or unexported fields
}

SlogHookHandler is a slog.Handler that wraps another handler to add custom logic.

func NewSlogHookHandler

func NewSlogHookHandler(h slog.Handler, f HookFunc) *SlogHookHandler

NewSlogHookHandler wraps an slog.Handler with a hook function invoked for each log record. A nil h falls back to the handler of the current slog.Default, captured now, so the returned handler never panics on first use. A nil f is tolerated too (see Handle).

func (SlogHookHandler) Handle

func (h SlogHookHandler) Handle(ctx context.Context, record slog.Record) error

Handle intercepts the log record, invokes the hook (when set), then passes the record to the underlying handler. A nil hook is tolerated so a handler built with NewSlogHookHandler(h, nil) does not panic.

func (SlogHookHandler) WithAttrs

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

WithAttrs returns a new SlogHookHandler whose underlying handler carries the given attributes, preserving the hook function so it keeps firing for derived loggers. Per the slog.Handler contract, an empty attribute list returns the receiver unchanged.

func (SlogHookHandler) WithGroup

func (h SlogHookHandler) WithGroup(name string) slog.Handler

WithGroup returns a new SlogHookHandler whose underlying handler opens the given group, preserving the hook function so it keeps firing for derived loggers. Per the slog.Handler contract, an empty group name returns the receiver unchanged.

type SlogWriter

type SlogWriter struct {
	// Logger is the destination. A nil Logger writes to slog.Default, resolved per write, so the
	// zero value works and a logger installed later is picked up.
	Logger *slog.Logger
	// Level is the severity used for every bridged line. The zero value is
	// LevelInfo; construct with NewSlogWriter for the Error default.
	Level LogLevel
}

SlogWriter is a custom io.Writer that writes to slog.Logger at a configurable level.

The zero value is usable: it writes to slog.Default at LevelInfo.

func NewSlogWriter

func NewSlogWriter(logger *slog.Logger) *SlogWriter

NewSlogWriter constructs an io.Writer that routes writes to an slog.Logger at error level.

func NewSlogWriterLevel

func NewSlogWriterLevel(logger *slog.Logger, level LogLevel) *SlogWriter

NewSlogWriterLevel constructs an io.Writer that routes writes to an slog.Logger at the given level. This is the level-aware counterpart to NewSlogWriter: bridged standard log.Logger output is not necessarily error-severity, so callers can pick the level that matches the source. A nil logger falls back to slog.Default so writes never panic.

func (SlogWriter) Write

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

Write logs the input bytes at the configured level, stripping trailing newlines, and returns bytes written.

type TraceIDFunc

type TraceIDFunc func() string

TraceIDFunc is the type of function used to retrieve a Trace ID.

Jump to

Keyboard shortcuts

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