Documentation
¶
Overview ¶
Package loggy is a small, fast, dependency-free structured logger with a chained, zero-allocation builder API.
l := loggy.New(loggy.WithFormat(loggy.JSONFormat))
l.Info().Str("user", "ada").Int("n", 3).Msg("request handled")
// {"time":"...","level":"info","msg":"request handled","user":"ada","n":3}
Design ¶
The public surface is interface-driven (Logger, Entry, Hook, Sampler) with a concrete *Event and *Context builder on the hot path for speed. Fields are encoded straight into a pooled buffer as they are chained — no per-call slice, no map, and no reflection for common types — so an enabled log costs zero heap allocations and a disabled one is a cheap no-op.
Levels ¶
Six levels are available: Debug, Info, Warn, Error, Fatal, and Panic. Fatal logs then calls os.Exit(1); Panic logs then panics with the message. The active threshold is set with WithLevel or SetLevel and checked cheaply via Enabled.
Child loggers ¶
With begins a child-logger builder that carries pre-encoded persistent fields:
reqLog := l.With().Str("request_id", "r-123").Logger()
reqLog.Info().Msg("received") // includes request_id
Formats and color ¶
JSONFormat (default) emits one JSON object per line; TextFormat emits a human-readable line and, when writing to a terminal, colorizes the level.
Concurrency ¶
All methods are safe for concurrent use. By default writes are guarded by a mutex so any io.Writer works. If the writer is itself concurrency-safe, pass WithConcurrentWriter to drop the lock for higher parallel throughput.
Index ¶
- func ErrorPkg(msg string)
- func InfoPkg(msg string)
- func SetDefault(l Logger)
- func WithContext(ctx context.Context, l Logger) context.Context
- type Context
- func (c *Context) Any(key string, val any) *Context
- func (c *Context) Bool(key string, val bool) *Context
- func (c *Context) Dur(key string, d time.Duration) *Context
- func (c *Context) Err(err error) *Context
- func (c *Context) Float64(key string, val float64) *Context
- func (c *Context) Int(key string, val int) *Context
- func (c *Context) Int64(key string, val int64) *Context
- func (c *Context) Logger() Logger
- func (c *Context) Str(key, val string) *Context
- func (c *Context) Stringer(key string, val fmt.Stringer) *Context
- type ContextExtractor
- type Entry
- type Event
- func (e *Event) Any(key string, val any) *Event
- func (e *Event) Bool(key string, val bool) *Event
- func (e *Event) Ctx(ctx context.Context) *Event
- func (e *Event) Dur(key string, d time.Duration) *Event
- func (e *Event) Err(err error) *Event
- func (e *Event) Float64(key string, val float64) *Event
- func (e *Event) Int(key string, val int) *Event
- func (e *Event) Int64(key string, val int64) *Event
- func (e *Event) Msg(msg string)
- func (e *Event) Msgf(format string, args ...any)
- func (e *Event) Str(key, val string) *Event
- func (e *Event) Stringer(key string, val fmt.Stringer) *Event
- type Format
- type Hook
- type Level
- type Logger
- type Option
- func WithCaller(enabled bool) Option
- func WithColor(enabled bool) Option
- func WithConcurrentWriter() Option
- func WithContextExtractor(fn ContextExtractor) Option
- func WithFormat(f Format) Option
- func WithHook(h Hook) Option
- func WithLevel(lvl Level) Option
- func WithName(name string) Option
- func WithOutput(w io.Writer) Option
- func WithSampler(s Sampler) Option
- func WithStackTrace(minLevel Level) Option
- func WithTimeFunc(fn func() time.Time) Option
- type Sampler
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ErrorPkg ¶
func ErrorPkg(msg string)
ErrorPkg logs a message at ErrorLevel through the default logger.
Types ¶
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context builds a child logger carrying pre-encoded persistent fields. Finish with Logger(). Field methods mirror Event's.
func (*Context) Logger ¶
Logger returns a child Logger carrying the accumulated fields. It shares the parent's writer, hooks, sampler, and write lock, and does not mutate the parent.
type ContextExtractor ¶
ContextExtractor pulls values out of a context.Context and adds them to the event — e.g. attaching trace_id/span_id. It writes directly through the Event's chainable methods, so no intermediate field type is needed:
func(ctx context.Context, e *Event) {
if id, ok := ctx.Value(traceKey).(string); ok {
e.Str("trace_id", id)
}
}
type Entry ¶
type Entry interface {
Time() time.Time
Level() Level
Message() string
Caller() string
Stack() string
}
Entry is one assembled log record, handed to Hooks and Samplers before it is written. Fields are not exposed here: on the streaming builder path they are already encoded to bytes, so an Entry carries only the record's scalar metadata.
type Event ¶
type Event struct {
// contains filtered or unexported fields
}
Event is a chained, zero-allocation log entry. Fields are encoded directly into a pooled buffer as they are added; the terminal Msg/Msgf writes the line and recycles the Event. Begin one with a Logger level method (l.Info() etc).
An Event must not be used after Msg/Msgf. When the level is disabled the level method returns a shared no-op Event whose methods do nothing.
func (*Event) Any ¶
Any adds an arbitrary value, falling back to reflection (json.Marshal / fmt) for types without a dedicated method.
func (*Event) Ctx ¶
Ctx runs the logger's ContextExtractor (if any) against ctx, letting it add fields such as a trace id to this event.
func (*Event) Msgf ¶
Msgf formats the message with fmt.Sprintf, then writes the entry.
Example ¶
package main
import (
"os"
"time"
"github.com/subhanjanops/loggy"
)
// fixedClock keeps example output deterministic.
func fixedClock() loggy.Option {
return loggy.WithTimeFunc(func() time.Time {
return time.Date(2026, 7, 24, 10, 30, 0, 0, time.UTC)
})
}
func main() {
l := loggy.New(loggy.WithOutput(os.Stdout), loggy.WithFormat(loggy.JSONFormat), fixedClock())
l.Warn().Int("pct", 87).Msgf("%d%% of quota used", 87)
}
Output: {"time":"2026-07-24T10:30:00Z","level":"warn","msg":"87% of quota used","pct":87}
type Hook ¶
Hook lets external code react to every emitted entry (e.g. shipping errors to Sentry, incrementing metrics counters).
type Level ¶
type Level int32
Level is the severity of a log entry. Higher values are more severe.
const ( DebugLevel Level = iota // verbose diagnostic detail InfoLevel // normal operational messages WarnLevel // something unexpected but non-fatal ErrorLevel // a failure that needs attention FatalLevel // terminal failure (Fatal exits, Panic panics) )
The available severity levels, in increasing order.
type Logger ¶
type Logger interface {
Debug() *Event
Info() *Event
Warn() *Event
Error() *Event
Fatal() *Event // Msg/Msgf then os.Exit(1)
Panic() *Event // Msg/Msgf then panic(msg)
// With begins a child-logger builder; finish it with Logger():
// child := l.With().Str("component", "db").Logger()
With() *Context
SetLevel(lvl Level)
Level() Level
Enabled(lvl Level) bool // cheap pre-check for hot paths
Sync() error // flush the underlying writer if it supports it
Close() error // close the underlying writer if it is an io.Closer
}
Logger is a structured logger. All methods are safe for concurrent use. The level methods begin a chained entry finished by Msg/Msgf.
func Default ¶
func Default() Logger
Default returns the package-level default Logger, creating a plain one on first access.
func FromContext ¶
FromContext returns the Logger stored in ctx, or the package default logger if none is present.
func New ¶
New builds a Logger with sane defaults (stdout, InfoLevel, JSON, time.Now) and applies the given options in order.
Example ¶
package main
import (
"os"
"time"
"github.com/subhanjanops/loggy"
)
// fixedClock keeps example output deterministic.
func fixedClock() loggy.Option {
return loggy.WithTimeFunc(func() time.Time {
return time.Date(2026, 7, 24, 10, 30, 0, 0, time.UTC)
})
}
func main() {
l := loggy.New(loggy.WithOutput(os.Stdout), loggy.WithFormat(loggy.JSONFormat), fixedClock())
l.Info().Str("user", "ada").Int("n", 3).Msg("request handled")
}
Output: {"time":"2026-07-24T10:30:00Z","level":"info","msg":"request handled","user":"ada","n":3}
type Option ¶
type Option func(*logger)
Option configures a Logger at construction time. It is deliberately opaque: its underlying func operates on the unexported *logger, so callers can only obtain Options from the WithX constructors below and pass them to New — they cannot forge one or reach into a Logger's internals.
func WithCaller ¶
WithCaller enables attaching the "file:line" of the call site to each entry.
func WithColor ¶
WithColor forces ANSI level colors on (true) or off (false) for text output. The default is automatic: colors are emitted only when the output is a terminal, so files and pipes stay clean. JSON output is never colorized.
func WithConcurrentWriter ¶
func WithConcurrentWriter() Option
WithConcurrentWriter declares that the output io.Writer is safe for concurrent Write calls and writes each buffer atomically. The logger then skips its internal write mutex, giving lock-free throughput under contention (each log line is a single Write, so the writer serializes them).
Enable this ONLY for such writers — os.File, os.Stdout/Stderr, io.Discard, or a writer you have wrapped with your own synchronization. Enabling it with a writer whose Write is not concurrency-safe (e.g. a bare bytes.Buffer) will interleave or corrupt output.
func WithContextExtractor ¶
func WithContextExtractor(fn ContextExtractor) Option
WithContextExtractor sets the function used by Event.Ctx to pull fields out of a context.Context (e.g. a trace id).
func WithFormat ¶
WithFormat selects TextFormat or JSONFormat. The default is JSONFormat.
func WithHook ¶
WithHook registers a Hook invoked for every emitted entry. It may be called multiple times to add several hooks.
func WithLevel ¶
WithLevel sets the minimum level to emit. The default is InfoLevel.
Example ¶
package main
import (
"os"
"time"
"github.com/subhanjanops/loggy"
)
// fixedClock keeps example output deterministic.
func fixedClock() loggy.Option {
return loggy.WithTimeFunc(func() time.Time {
return time.Date(2026, 7, 24, 10, 30, 0, 0, time.UTC)
})
}
func main() {
l := loggy.New(loggy.WithOutput(os.Stdout), loggy.WithFormat(loggy.JSONFormat),
loggy.WithLevel(loggy.WarnLevel), fixedClock())
l.Info().Msg("filtered out") // below Warn, dropped
l.Warn().Msg("shown")
}
Output: {"time":"2026-07-24T10:30:00Z","level":"warn","msg":"shown"}
func WithOutput ¶
WithOutput sets the destination writer. The default is os.Stdout.
func WithSampler ¶
WithSampler sets a Sampler that can drop entries before they are written.
func WithStackTrace ¶
WithStackTrace attaches a stack trace to entries at or above minLevel. The default threshold is FatalLevel.
func WithTimeFunc ¶
WithTimeFunc overrides the clock used for timestamps. Handy for deterministic tests. The default is time.Now.