glow

package module
v0.0.0-...-e956e22 Latest Latest
Warning

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

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

README

Glow

Developer-focused structured logging for Go.

CI Go Reference Release

Beautiful logs by default. Structured logs when you need them.

logger := glow.New()
logger.Info("Server started")

Glow keeps a small, immutable API for call sites. Handlers own serialization, so switching from development output to JSON never changes how you log. v1.0.0 locks the minimal API and behavioral contracts under semantic versioning.

What it looks like

Default theme — colored levels, service, PID, timestamp

Default theme with colored DEBUG, INFO, WARN, and ERROR lines

Structured fields — aligned keys, mixed types

Aligned structured fields including int, string, bool, float, duration, and time

Contexts — component labels inherit cleanly

Context labels for AuthService, Database, and QueueWorker with inherited fields

Safety — sensitive values redacted by default

Sensitive apiKey and authorization fields rendered as REDACTED

Errors — first-class rendering with unwrap chain

Error field showing wrapped message and root reason

Diagnostics — caller + trace/span correlation

Caller path with traceID and spanID fields

Themes — same log call, different presentation

Default, Minimal, Compact, and Classic themes side by side

Production JSON — same fields, machine-readable

JSON log line with typed fields and redacted secrets

Install

go get github.com/adamreaksmey/glow

Requires Go 1.23 or newer. After the v1.0.0 tag is published, pin with @v1.0.0 or @v1.

Quick start

package main

import "github.com/adamreaksmey/glow"

func main() {
	logger := glow.New()
	auth := logger.Context("AuthService")
	auth.Info("User logged in", glow.Int("id", 42), glow.String("role", "admin"))
}

Core API

Method Purpose
New(opts...) Construct a logger
Debug / Info / Warn / Error / Fatal / Panic Emit at a level
Context(name) Label the component
With(fields...) Inherit structured fields
Clone() Copy without sharing field storage

Typed fields include String, Int, Int64, Uint64, Float64, Bool, Duration, Time, Err, Sensitive, Caller, TraceID, SpanID, and Any.

Guides

Doc Topic
Stability What is guaranteed in v1.x
Security Sensitive fields and redaction
Migration Moving from log/slog/zap and interoperability
Performance Benchmarks, allocation gates, I/O caveats
Changelog Release history

Tracing

Attach correlation IDs with TraceID / SpanID. They inherit through With, Context, and Clone like any other field:

logger = logger.With(glow.TraceID("abc-123-def"), glow.SpanID("xyz-789"))
auth := logger.Context("AuthService")
auth.Info("Processing request") // includes traceID and spanID

Optional OpenTelemetry extraction lives in integrations/otel so the core module stays dependency-light:

import glowotel "github.com/adamreaksmey/glow/integrations/otel"

logger = glowotel.Enrich(logger, request.Context()) // explicit IDs already on logger win
// or: logger = glowotel.With(logger, ctx)          // extracted IDs override matching keys

Call-site fields always override inherited IDs. Prefer Enrich when request-scoped explicit IDs should beat span extraction.

Safety and diagnostics

  • Sensitive: glow.Sensitive("apiKey", secret) is redacted as [REDACTED] by default in development and JSON output. Reveal values only with the conspicuous WithUnsafeShowSensitive() option (and AllowUnsafeSensitiveValues() on standalone handlers). See SECURITY.md.
  • Caller: pass glow.Caller() to capture file:line. Use WithProjectRoot to trim absolute prefixes.
  • Errors: glow.Err(err) renders a structured detail (message, reason, unwrap chain). Optional stacks via WithErrorStacks when the error exposes StackTrace() []uintptr. Register custom renderers with RegisterErrorRenderer so third-party packages stay out of core.

Production controls

Pipeline order is always level → filter → sampling → handlers.

logger := glow.New(
    glow.WithLevel(glow.DebugLevel),
    glow.WithSampling(glow.DebugLevel, 0.1),
    glow.WithFilter(func(entry glow.Entry) bool {
        return entry.Message != "health check passed"
    }),
    glow.WithHandler(glow.NewDevelopmentHandler(os.Stdout).SetColor(false)),
    glow.WithHandler(glow.NewJSONHandler(logFile)),
)
  • Fan-out: repeated WithHandler (or glow.Fanout(...)) delivers each entry to every enabled sink. One sink failure does not stop the others; the logger discards handler errors after fan-out.
  • Writers: glow.MultiWriter(a, b) duplicates bytes to multiple io.Writers and continues after errors/short writes (unlike io.MultiWriter).
  • Rotating files: use the optional rotate package so core stays dependency-light. Pass an empty path (or rotate.WithEnabled(false)) to turn dump files off without branching logger setup:
file, err := rotate.Open(os.Getenv("LOG_FILE"), rotate.WithMaxBytes(10<<20), rotate.WithMaxBackups(3))
if err != nil {
    return err
}
defer file.Close()
logger := glow.New(glow.WithHandler(glow.NewJSONHandler(file)))

Testing

Use the glowtest package to capture structured entries in unit tests without pulling helpers into the production hot path:

logger, logs := glowtest.NewLogger()
logger.Context("AuthService").Info("User logged in", glow.Int("userId", 42))

logs.AssertContains(t, "User logged in")
logs.AssertFieldEquals(t, "userId", 42)
logs.AssertLevel(t, glow.InfoLevel)
logs.AssertContext(t, "AuthService")

Capture is a concurrency-safe glow.Handler with Reset, level/context filters, and assertion failures that dump what was captured (sensitive values stay redacted).

HTTP middleware and framework integrations

Framework-neutral request logging lives in middleware/http:

handler := glowhttp.Middleware(logger)(mux)
// or: glowhttp.Middleware(logger, glowhttp.WithSkipPaths("/healthz"), glowhttp.WithRequestBody(true))

Each request logs method, path, status, and latency (4xx → warn, 5xx → error). Status capture is panic-safe. Optional body capture is size- and content-type-limited; sensitive headers are redacted by default and bodies use Sensitive unless you pass WithRedactBodies(false).

Isolated adapters keep framework dependencies out of the core module:

Import Framework
github.com/adamreaksmey/glow/integrations/chi Chi
github.com/adamreaksmey/glow/integrations/gin Gin
github.com/adamreaksmey/glow/integrations/echo Echo
github.com/adamreaksmey/glow/integrations/fiber Fiber
github.com/adamreaksmey/glow/integrations/cobra Cobra
github.com/adamreaksmey/glow/integrations/bubbletea Bubble Tea
github.com/adamreaksmey/glow/integrations/otel OpenTelemetry span → traceID/spanID
import glowgin "github.com/adamreaksmey/glow/integrations/gin"

router.Use(glowgin.Middleware(logger))

slog compatibility

Glow implements a complete log/slog.Handler so standard-library and third-party loggers can use Glow's formatters:

handler := glow.NewHandler(glow.WithHandler(glow.NewJSONHandler(os.Stdout)))
slog.SetDefault(slog.New(handler))

Or adapt an existing logger with logger.SlogHandler(). Use NewHandlerWithSource when you want source file/line/function attributes. Compatibility is verified with Go's testing/slogtest suite (groups, WithAttrs, zero time, empty PC, and resolved values). See MIGRATION.md for more.

Behavioral contracts

These are part of the public API:

  • Errors: pass glow.Err(err); an empty message uses the error text
  • Duplicates: last value wins; first-seen key order is preserved
  • Reserved keys: timestamp, level, message, service, context, pid, caller are rewritten to user.<key>
  • Context: empty/whitespace clears the label; names replace, they do not nest
  • Nil errors: Err(nil) is omitted
  • Fatal: logs, then calls an injectable exit function (default os.Exit(1))
  • Panic: logs, then panics with the message
  • Concurrency: loggers are safe for concurrent use; derived loggers never mutate parents

Development

make test
make race
make vet
make fmt
make bench
make fuzz        # seed corpus
make fuzz-long   # timed fuzzing
make check       # fmt + vet + test + race + fuzz seeds

CI runs format checks, go vet, unit tests, race tests, benchmark compilation, and fuzz seeds on Go 1.23 and Go 1.25 (Linux), plus compatibility smoke tests on macOS and Windows. See PERFORMANCE.md for allocation gates and I/O caveats.

License

MIT

Documentation

Overview

Package glow is a developer-focused structured logging library for Go.

Glow prioritizes readable development output while remaining production-ready through interchangeable handlers. The logging call sites stay identical when switching between development and structured (JSON) output.

Minimal API

The stable call-site surface is intentionally small:

logger := glow.New()
logger.Info("Server started")
logger.Warn("Redis disconnected")
logger.Error("Database timeout", glow.Err(err))
logger.Debug("cache miss", glow.String("key", key))
logger.Fatal("unrecoverable configuration error")
logger.Panic("invariant violated")

auth := logger.Context("AuthService")
req := auth.With(glow.String("requestID", id))
child := req.Clone()

Behavioral Contracts

The following contracts are part of Glow's public API stability guarantees. Implementations and handlers must honor them.

Message methods and the error path: Level methods accept a message string plus zero or more typed fields. First-class error rendering uses Err. When a log call includes an Err field and the message is empty, handlers use the error's message as the entry message. A nil error passed to Err is omitted from the entry (see Nil errors).

Duplicate keys: Within a single log call, and when merging logger-inherited fields with call-site fields, the last value for a given key wins. The winning field keeps the position of the key's first occurrence so field order remains stable and deterministic.

Reserved fields: Keys named by ReservedFieldKeys are owned by Glow metadata (timestamp, level, message, service, context, pid, caller). User fields that collide with a reserved key are emitted under the "user." prefix (for example, "user.level") and never overwrite metadata.

Invalid contexts: Logger.Context treats an empty or whitespace-only name as clearing the context (no component label). Non-empty names replace any prior context; contexts are not nested.

Nil errors: Err(nil) produces no field. Handlers never render a synthetic nil-error value for that constructor.

Fatal: Logger.Fatal emits a log entry at FatalLevel, then calls the configured exit function (default: os.Exit(1)). Tests should inject WithExitFunc so Fatal does not terminate the process.

Panic: Logger.Panic emits a log entry at PanicLevel, then panics with the message string. The panic always occurs after the entry has been handed to handlers, even if a handler returns an error.

Concurrency: A Logger value is safe for concurrent use. Logger.With, Logger.Context, and Logger.Clone return new loggers and never mutate the receiver. Concurrent writes through a shared logger are synchronized by the logger before reaching handlers.

Immutability: Derived loggers inherit fields and configuration by copy. Changes to a child logger never affect its parent or siblings.

Handlers and themes

By default, New writes development-formatted output using ThemeDefault. Use WithTheme to switch presentation (Minimal, Compact, Classic) without changing call sites. NewJSONHandler produces deterministic structured output; register it with WithHandler. Color is enabled automatically for terminals unless WithColor or NO_COLOR overrides it.

Production controls

Pipeline order for every log call is fixed: level gating → WithFilterWithSampling → handler fan-out. Filters must be concurrency-safe and must not mutate entry fields. Sampling is per-level and probabilistic; unconfigured levels are never sampled.

Register multiple destinations with repeated WithHandler calls (or wrap them with Fanout). Sink failures are isolated across handlers; the logger discards handler errors after fan-out so a broken destination cannot interrupt callers. MultiWriter mirrors that isolation for raw io.Writer sinks and continues after short writes or errors (unlike io.MultiWriter).

Size-based rotating files live in the optional subpackage github.com/adamreaksmey/glow/rotate so rotation stays out of the core module path.

slog interoperability

NewHandler returns a complete log/slog.Handler adapter. Records flow through Glow's pipeline so the same development and JSON handlers apply. Logger.SlogHandler adapts an existing logger. Use NewHandlerWithSource (or Logger.SlogHandlerWithSource) to attach slog source attributes when Record.PC is non-zero. Groups become nested maps; typed slog values map to Glow fields without reflection on the native kinds.

Safety and diagnostics

Sensitive fields are redacted as RedactedValue in every built-in handler and in Field.FormatValue. Opting out requires an intentionally conspicuous API: WithUnsafeShowSensitive on the logger and/or AllowUnsafeSensitiveValues on a handler.

Caller captures the call-site file and line when present on a log call (or inherited via Logger.With). Paths are trimmed with WithProjectRoot when set.

Err fields render as structured ErrorDetail values (message, reason, unwrap chain, optional location/stack). Enable stacks with WithErrorStacks when errors expose StackTrace() []uintptr. Register third-party formatting through RegisterErrorRenderer without importing those packages into Glow.

Testing

Package github.com/adamreaksmey/glow/glowtest provides an in-memory, concurrency-safe capture Handler and assertions for messages, fields, levels, and contexts. Keep it out of production code paths; wire it with WithHandler (or glowtest.NewLogger) only in tests.

Tracing

TraceID and SpanID attach correlation fields that inherit through Logger.With, Logger.Context, and Logger.Clone. Empty IDs are omitted. Optional OpenTelemetry extraction lives in github.com/adamreaksmey/glow/integrations/otel: Enrich fills missing IDs from the active span without overriding explicit values; With applies extraction with normal last-write-wins merge semantics. Call-site fields always override inherited IDs.

HTTP middleware and integrations

Package github.com/adamreaksmey/glow/middleware/http provides framework-neutral net/http request logging (method, path, status, latency) with panic-safe status capture, optional body limits/content-type filters, and sensitive header/body redaction. Framework adapters live in separate modules under github.com/adamreaksmey/glow/integrations so Gin, Echo, Fiber, Chi, Cobra, Bubble Tea, and OpenTelemetry never become dependencies of the core module.

Performance

Benchmarks cover disabled logs, simple messages, typed fields, contexts, development and JSON output, caller capture, and handler fan-out. Allocation regression gates live alongside those benchmarks. Numbers measured against io.Discard are regression signals only: real writer I/O (files, terminals, network) dominates latency and can prevent strict allocation or timing guarantees. Prefer typed fields over Any on hot paths. See docs/PERFORMANCE.md in the repository.

Stability

Glow follows semantic versioning. v1.0.0 graduates the minimal API above, typed field constructors, and these behavioral contracts to a stable surface. Breaking changes require a new major version. See docs/STABILITY.md, docs/SECURITY.md, and docs/MIGRATION.md in the repository.

Example
package main

import (
	"os"

	"github.com/adamreaksmey/glow"
)

func main() {
	logger := glow.New(
		glow.WithService("api"),
		glow.WithLevel(glow.InfoLevel),
		glow.WithWriter(os.Stdout),
		glow.WithTheme(glow.ThemeClassic),
		glow.WithColor(false),
		glow.WithExitFunc(func(int) {}),
	)

	auth := logger.Context("AuthService")
	auth.Info("User logged in", glow.Int("id", 42), glow.String("role", "admin"))
}
Output:
INFO [AuthService] User logged in
  id:   42
  role: admin

Index

Examples

Constants

View Source
const (
	ReservedKeyTimestamp = "timestamp"
	ReservedKeyLevel     = "level"
	ReservedKeyMessage   = "message"
	ReservedKeyService   = "service"
	ReservedKeyContext   = "context"
	ReservedKeyPID       = "pid"
	ReservedKeyCaller    = "caller"

	// ReservedKeyPrefix is prepended to user field keys that collide with
	// reserved metadata keys (for example, "level" becomes "user.level").
	ReservedKeyPrefix = "user."
)

Reserved metadata field keys owned by Glow. User fields that collide with these keys are rewritten with the ReservedKeyPrefix by MergeFields.

View Source
const (
	Debug = DebugLevel
	Info  = InfoLevel
	Warn  = WarnLevel
	Error = ErrorLevel
	Fatal = FatalLevel
	Panic = PanicLevel
)

Ergonomic aliases matching the documented call-site API.

View Source
const (
	FieldTraceID = "traceID"
	FieldSpanID  = "spanID"
)

Canonical field keys for distributed tracing metadata.

These are ordinary user fields (not reserved metadata). Prefer TraceID and SpanID so keys stay consistent across call sites, HTTP middleware, and OpenTelemetry extraction.

View Source
const RedactedValue = "[REDACTED]"

RedactedValue is the placeholder emitted for Sensitive fields when redaction is active (the default).

View Source
const Version = "v1.0.0"

Version is the current stable release of the core module.

Variables

This section is empty.

Functions

func MultiWriter

func MultiWriter(writers ...io.Writer) io.Writer

MultiWriter returns an io.Writer that duplicates each write to every non-nil destination.

Partial-write / sink-failure contract: each destination is attempted independently. An error or short write on one destination does not stop writes to the others. After all destinations are attempted, Write returns (len(p), firstErr) where firstErr is the first failure observed, or nil.

This differs from io.MultiWriter, which stops at the first error.

func NewHandler

func NewHandler(options ...Option) slog.Handler

NewHandler returns an slog.Handler backed by New with the given options.

handler := glow.NewHandler(glow.WithHandler(glow.NewJSONHandler(os.Stdout)))
logger := slog.New(handler)
Example
package main

import (
	"bytes"
	"fmt"
	"log/slog"

	"github.com/adamreaksmey/glow"
)

func main() {
	var output bytes.Buffer
	handler := glow.NewHandler(
		glow.WithService("api"),
		glow.WithHandler(glow.NewDevelopmentHandler(&output).
			SetTheme(glow.ThemeClassic).
			SetColor(false)),
	)
	slog.New(handler).Info("via slog", "role", "admin")
	fmt.Print(output.String())
}
Output:
INFO via slog
  role: admin

func NewHandlerWithSource

func NewHandlerWithSource(options ...Option) slog.Handler

NewHandlerWithSource is like NewHandler but includes a slog.SourceKey attribute (file, line, function) when Record.PC is non-zero.

func RegisterErrorRenderer

func RegisterErrorRenderer(renderer ErrorRenderer)

RegisterErrorRenderer appends a renderer consulted before the default unwrap-based rendering. Renderers are tried in registration order; the first that returns ok=true wins. Safe for concurrent use; typically called from init.

func ReservedFieldKeys

func ReservedFieldKeys() []string

ReservedFieldKeys lists every metadata key that user fields must not overwrite.

Types

type DevelopmentHandler

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

DevelopmentHandler writes human-readable, theme-aware log lines. Color is applied only when writing to a terminal unless overridden.

func NewDevelopmentHandler

func NewDevelopmentHandler(writer io.Writer) *DevelopmentHandler

NewDevelopmentHandler constructs a development handler writing to writer. Defaults match New: ThemeDefault with timestamps and PID enabled.

func (*DevelopmentHandler) AllowUnsafeSensitiveValues

func (handler *DevelopmentHandler) AllowUnsafeSensitiveValues() *DevelopmentHandler

AllowUnsafeSensitiveValues disables redaction for Sensitive fields handled by this handler. Intentionally conspicuous; prefer the default redaction.

func (*DevelopmentHandler) Enabled

func (handler *DevelopmentHandler) Enabled(level Level) bool

Enabled reports whether the handler accepts entries at level.

func (*DevelopmentHandler) Handle

func (handler *DevelopmentHandler) Handle(entry Entry) error

Handle formats and writes a development log line.

func (*DevelopmentHandler) SetColor

func (handler *DevelopmentHandler) SetColor(enabled bool) *DevelopmentHandler

SetColor forces ANSI color on or off.

func (*DevelopmentHandler) SetColorAuto

func (handler *DevelopmentHandler) SetColorAuto() *DevelopmentHandler

SetColorAuto restores terminal auto-detection (and NO_COLOR) for color.

func (*DevelopmentHandler) SetErrorStacks

func (handler *DevelopmentHandler) SetErrorStacks(enabled bool) *DevelopmentHandler

SetErrorStacks enables stack output for error fields when available.

func (*DevelopmentHandler) SetMinLevel

func (handler *DevelopmentHandler) SetMinLevel(level Level) *DevelopmentHandler

SetMinLevel sets the minimum severity this handler accepts. Entries below the minimum are skipped by DevelopmentHandler.Enabled.

func (*DevelopmentHandler) SetShowPID

func (handler *DevelopmentHandler) SetShowPID(show bool) *DevelopmentHandler

SetShowPID controls Default-theme process ID display.

func (*DevelopmentHandler) SetShowTimestamp

func (handler *DevelopmentHandler) SetShowTimestamp(show bool) *DevelopmentHandler

SetShowTimestamp controls Default-theme timestamps.

func (*DevelopmentHandler) SetTheme

func (handler *DevelopmentHandler) SetTheme(theme Theme) *DevelopmentHandler

SetTheme selects the presentation theme. Returns the handler for chaining.

type Entry

type Entry struct {
	Time    time.Time
	Level   Level
	Message string
	Service string
	Context string
	PID     int
	Caller  string
	Fields  []Field
}

Entry is the immutable unit of work passed through the logging pipeline. Handlers serialize Entries; changing a handler never changes call sites.

func (Entry) Clone

func (entry Entry) Clone() Entry

Clone returns a shallow copy of the entry with an independent Fields slice.

type ErrorDetail

type ErrorDetail struct {
	Message  string
	Reason   string
	Chain    []string
	Location string
	Stack    string
}

ErrorDetail is the structured rendering of an error for handlers.

func RenderError

func RenderError(err error, options ErrorRenderOptions) ErrorDetail

RenderError builds an ErrorDetail for err. Registered renderers run first; otherwise Glow walks the unwrap chain. A nil error yields a zero detail.

type ErrorRenderOptions

type ErrorRenderOptions struct {
	IncludeStack bool
}

ErrorRenderOptions controls optional parts of RenderError.

type ErrorRenderer

type ErrorRenderer interface {
	Render(err error) (ErrorDetail, bool)
}

ErrorRenderer converts a specific error type into ErrorDetail. Register renderers for third-party error packages in an integration's init so Glow's core never imports those packages.

type ErrorRendererFunc

type ErrorRendererFunc func(err error) (ErrorDetail, bool)

ErrorRendererFunc adapts a function to ErrorRenderer.

func (ErrorRendererFunc) Render

func (render ErrorRendererFunc) Render(err error) (ErrorDetail, bool)

Render calls the function.

type Field

type Field struct {
	Key       string
	Kind      FieldKind
	Integer   int64
	Unsigned  uint64
	Float     float64
	String    string
	Boolean   bool
	Interface any
}

Field is an ordered, typed key/value pair attached to a log entry or logger. Fields are values; copying a Field does not share mutable state.

func Any

func Any(key string, value any) Field

Any stores an arbitrary value. Prefer typed constructors on hot paths; Any may require reflection in handlers and is not allocation-free.

func Bool

func Bool(key string, value bool) Field

Bool constructs a boolean field.

func Caller

func Caller() Field

Caller requests capture of the call-site file and line. The key is ignored by handlers; caller metadata uses the reserved "caller" key. Prefer the zero-argument form at call sites.

func Duration

func Duration(key string, value time.Duration) Field

Duration constructs a time.Duration field.

func Err

func Err(err error) Field

Err constructs a first-class error field.

A nil error returns a zero Field with Kind FieldKindInvalid, which MergeFields and entry builders omit. Non-nil errors use key "error" unless a custom key is needed via NamedErr.

func Float64

func Float64(key string, value float64) Field

Float64 constructs a float64 field.

func Int

func Int(key string, value int) Field

Int constructs a signed integer field.

func Int64

func Int64(key string, value int64) Field

Int64 constructs an int64 field.

func MergeFields

func MergeFields(inherited, callSite []Field) []Field

MergeFields combines inherited and call-site fields under Glow's field contracts: invalid fields are dropped, reserved keys are prefixed, duplicate keys keep first-seen order with last-write-wins values, and call-site fields override inherited fields with the same key.

func NamedErr

func NamedErr(key string, err error) Field

NamedErr constructs an error field with an explicit key. A nil error returns a zero Field that callers must treat as absent.

func Sensitive

func Sensitive(key, value string) Field

Sensitive marks a string value for redaction in every built-in handler. Redaction is enabled by default; opting out requires an explicit handler configuration that is intentionally conspicuous.

Example
package main

import (
	"os"

	"github.com/adamreaksmey/glow"
)

func main() {
	logger := glow.New(
		glow.WithService("api"),
		glow.WithWriter(os.Stdout),
		glow.WithTheme(glow.ThemeClassic),
		glow.WithColor(false),
	)
	logger.Info("API request", glow.Sensitive("apiKey", "super-secret"))
}
Output:
INFO API request
  apiKey: [REDACTED]

func SpanID

func SpanID(id string) Field

SpanID constructs a string field with key FieldSpanID. An empty id is omitted (same contract as Err(nil)).

func String

func String(key, value string) Field

String constructs a string field.

func Time

func Time(key string, value time.Time) Field

Time constructs a time.Time field stored in Interface.

func TraceID

func TraceID(id string) Field

TraceID constructs a string field with key FieldTraceID. An empty id is omitted (same contract as Err(nil)).

Example
package main

import (
	"os"

	"github.com/adamreaksmey/glow"
)

func main() {
	logger := glow.New(
		glow.WithService("api"),
		glow.WithWriter(os.Stdout),
		glow.WithTheme(glow.ThemeClassic),
		glow.WithColor(false),
	)
	logger = logger.With(glow.TraceID("abc-123-def"), glow.SpanID("xyz-789"))
	logger.Context("AuthService").Info("Processing request")
}
Output:
INFO [AuthService] Processing request
  traceID: abc-123-def
  spanID:  xyz-789

func Uint64

func Uint64(key string, value uint64) Field

Uint64 constructs a uint64 field.

func (Field) ErrorValue

func (field Field) ErrorValue() error

ErrorValue returns the error payload when Kind is FieldKindError.

func (Field) FormatValue

func (field Field) FormatValue() string

FormatValue renders a debug representation of the field value. FieldKindSensitive values are always shown as RedactedValue here so accidental debug printing cannot leak secrets; use the field's String only after an explicit unsafe override.

func (Field) IsValid

func (field Field) IsValid() bool

IsValid reports whether the field carries a usable kind and key (or is a caller marker, which has a reserved key).

type FieldKind

type FieldKind uint8

FieldKind identifies the typed payload stored in a Field.

const (
	FieldKindInvalid FieldKind = iota
	FieldKindString
	FieldKindInt
	FieldKindInt64
	FieldKindUint64
	FieldKindFloat64
	FieldKindBool
	FieldKindDuration
	FieldKindTime
	FieldKindError
	FieldKindSensitive
	FieldKindCaller
	FieldKindAny
)

Supported typed field kinds. Native constructors never use reflection.

type FilterFunc

type FilterFunc func(entry Entry) bool

FilterFunc decides whether an entry should be emitted. Returning false drops the entry.

Pipeline order: level gating → filter → sampling → handlers. Filters run only for enabled levels and before sampling. A filter that returns false skips sampling and handler fan-out entirely.

FilterFuncs must be safe for concurrent use and must not mutate entry.Fields.

type Handler

type Handler interface {
	Enabled(level Level) bool
	Handle(entry Entry) error
}

Handler serializes and writes log entries to one or more destinations. Handlers own formatting; themes affect only development handlers.

When multiple handlers are registered with WithHandler, each enabled handler receives every emitted entry. Sink failures are isolated: one handler error does not prevent later handlers from running. The logger discards handler errors after fan-out (best-effort logging).

func Fanout

func Fanout(handlers ...Handler) Handler

Fanout returns a Handler that delivers each entry to every non-nil handler in registration order.

Sink failures are isolated: an error from one handler does not prevent later handlers from running. After every handler is attempted, Fanout returns the first non-nil error, or nil if all handlers succeeded.

Prefer registering multiple handlers with WithHandler when configuring a Logger. Fanout is useful when a single Handler value must wrap several sinks (for example, when composing handlers for adapters).

Example
package main

import (
	"bytes"
	"fmt"

	"github.com/adamreaksmey/glow"
)

func main() {
	var development bytes.Buffer
	var jsonOut bytes.Buffer

	logger := glow.New(
		glow.WithService("api"),
		glow.WithHandler(glow.NewDevelopmentHandler(&development).
			SetTheme(glow.ThemeClassic).
			SetColor(false)),
		glow.WithHandler(glow.NewJSONHandler(&jsonOut)),
	)
	logger.Info("dual sink")

	fmt.Print(development.String())
}
Output:
INFO dual sink

type JSONHandler

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

JSONHandler writes deterministic, machine-readable JSON log lines. Output never includes ANSI styling. Metadata keys use stable names and order; user fields follow call order with typed JSON values.

func NewJSONHandler

func NewJSONHandler(writer io.Writer) *JSONHandler

NewJSONHandler constructs a JSON handler writing one object per line.

Example
package main

import (
	"bytes"
	"encoding/json"
	"fmt"

	"github.com/adamreaksmey/glow"
)

func main() {
	var output bytes.Buffer
	logger := glow.New(
		glow.WithService("api"),
		glow.WithHandler(glow.NewJSONHandler(&output)),
	)
	logger.Context("AuthService").Info("User logged in", glow.Int("id", 42))

	var payload map[string]any
	if err := json.Unmarshal(output.Bytes(), &payload); err != nil {
		panic(err)
	}
	fmt.Println(payload["level"], payload["message"], payload["id"])
}
Output:
info User logged in 42

func (*JSONHandler) AllowUnsafeSensitiveValues

func (handler *JSONHandler) AllowUnsafeSensitiveValues() *JSONHandler

AllowUnsafeSensitiveValues disables redaction for Sensitive fields handled by this handler. Intentionally conspicuous; prefer the default redaction.

func (*JSONHandler) Enabled

func (handler *JSONHandler) Enabled(level Level) bool

Enabled reports whether the handler accepts entries at level.

func (*JSONHandler) Handle

func (handler *JSONHandler) Handle(entry Entry) error

Handle encodes the entry as a single JSON object followed by a newline.

func (*JSONHandler) SetErrorStacks

func (handler *JSONHandler) SetErrorStacks(enabled bool) *JSONHandler

SetErrorStacks enables stack output for error fields when available.

func (*JSONHandler) SetMinLevel

func (handler *JSONHandler) SetMinLevel(level Level) *JSONHandler

SetMinLevel sets the minimum severity this handler accepts. Entries below the minimum are skipped by JSONHandler.Enabled.

type Level

type Level int

Level represents the severity of a log entry.

const (
	DebugLevel Level = iota
	InfoLevel
	WarnLevel
	ErrorLevel
	FatalLevel
	PanicLevel
)

Severity levels in ascending order of importance.

func (Level) Enabled

func (level Level) Enabled(target Level) bool

Enabled reports whether an event at target should be emitted when the logger's minimum level is level. Higher-severity levels are always enabled when a lower minimum is configured.

func (Level) String

func (level Level) String() string

String returns the lowercase name of the level.

type Logger

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

Logger is an immutable, concurrency-safe structured logger.

Methods that change configuration or fields ([With], [Context], [Clone]) return a new Logger. Level methods emit entries through the configured handlers without mutating the receiver.

func New

func New(options ...Option) *Logger

New constructs a Logger with optional configuration. With no options, the logger writes at InfoLevel to os.Stdout using the executable base name as the service name.

Options are applied in order; later options override earlier ones for the same setting. WithHandler appends; it does not replace prior handlers.

func (*Logger) Clone

func (logger *Logger) Clone() *Logger

Clone returns a derived logger with the same configuration, context, and fields as the receiver. The parent and clone do not share field slices.

func (*Logger) Context

func (logger *Logger) Context(name string) *Logger

Context returns a derived logger labeled with name. Empty or whitespace-only names clear the context.

func (*Logger) Debug

func (logger *Logger) Debug(msg string, fields ...Field)

Debug logs a message at DebugLevel.

func (*Logger) Enabled

func (logger *Logger) Enabled(level Level) bool

Enabled reports whether an entry at level would be emitted after level gating.

func (*Logger) Error

func (logger *Logger) Error(msg string, fields ...Field)

Error logs a message at ErrorLevel.

Prefer attaching errors with Err. When msg is empty and an error field is present, the error's message becomes the entry message.

func (*Logger) Fatal

func (logger *Logger) Fatal(msg string, fields ...Field)

Fatal logs a message at FatalLevel, then calls the configured exit function with code 1. Use WithExitFunc in tests.

func (*Logger) Info

func (logger *Logger) Info(msg string, fields ...Field)

Info logs a message at InfoLevel.

func (*Logger) Panic

func (logger *Logger) Panic(msg string, fields ...Field)

Panic logs a message at PanicLevel, then panics with msg. The panic occurs even when the level is disabled, after a best-effort log.

func (*Logger) SlogHandler

func (logger *Logger) SlogHandler() slog.Handler

SlogHandler returns an slog.Handler that emits through this logger. Derived slog handlers share the logger's configuration, context, and fields.

func (*Logger) SlogHandlerWithSource

func (logger *Logger) SlogHandlerWithSource() slog.Handler

SlogHandlerWithSource is like Logger.SlogHandler but includes source location when Record.PC is non-zero.

func (*Logger) Warn

func (logger *Logger) Warn(msg string, fields ...Field)

Warn logs a message at WarnLevel.

func (*Logger) With

func (logger *Logger) With(fields ...Field) *Logger

With returns a derived logger that includes fields on every subsequent entry. Fields are merged with call-site fields under MergeFields contracts.

Example
package main

import (
	"os"

	"github.com/adamreaksmey/glow"
)

func main() {
	logger := glow.New(
		glow.WithService("api"),
		glow.WithWriter(os.Stdout),
		glow.WithTheme(glow.ThemeClassic),
		glow.WithColor(false),
	)
	request := logger.With(glow.String("requestID", "req-1"))
	request.Info("accepted")
}
Output:
INFO accepted
  requestID: req-1

func (*Logger) WithUnlessPresent

func (logger *Logger) WithUnlessPresent(fields ...Field) *Logger

WithUnlessPresent returns a derived logger that adds fields only for keys that are not already inherited. Existing keys keep their values and first-seen positions. Call-site fields still override inherited values via MergeFields.

Use this when attaching defaults such as extracted OpenTelemetry IDs so explicit TraceID/SpanID values set earlier on the logger win.

type Option

type Option func(*loggerConfig)

Option configures a Logger at construction time.

func WithColor

func WithColor(enabled bool) Option

WithColor forces ANSI color on (true) or off (false). When unset, color is enabled only when the writer is a terminal.

func WithErrorStacks

func WithErrorStacks() Option

WithErrorStacks enables stack traces on error fields when the error implements StackTrace() []uintptr (as used by popular error packages). Stacks are omitted when the error does not expose program counters.

func WithExitFunc

func WithExitFunc(exit func(int)) Option

WithExitFunc replaces os.Exit for Logger.Fatal. The function receives the process exit code (always 1 for Fatal). Tests must set this to avoid terminating the test process.

func WithFilter

func WithFilter(filter FilterFunc) Option

WithFilter installs a dynamic entry filter. A nil filter clears filtering. See FilterFunc for pipeline ordering and concurrency requirements.

func WithHandler

func WithHandler(handler Handler) Option

WithHandler appends a Handler to the logger's fan-out list.

When at least one handler is configured, the default development writer path (WithWriter) is not used unless that destination is also registered explicitly as a handler (for example, NewDevelopmentHandler(os.Stdout)). Later WithHandler calls append; they do not replace earlier handlers.

Fan-out isolates sink failures across handlers. See Handler and Fanout.

func WithLevel

func WithLevel(level Level) Option

WithLevel sets the minimum enabled severity. Entries below this level are discarded before fields are merged or handlers run.

func WithPID

func WithPID() Option

WithPID enables inclusion of the process ID in development output. PID is always present on structured Entry values for handlers.

func WithProjectRoot

func WithProjectRoot(path string) Option

WithProjectRoot sets the directory prefix stripped from Caller paths. When empty, caller paths are emitted as cleaned absolute (slash-normalized) paths. Trailing separators are optional.

func WithSampling

func WithSampling(level Level, rate float64) Option

WithSampling sets a per-level probabilistic sample rate in the range [0, 1]. Rates outside that range are clamped. A rate of 1.0 keeps every entry; 0.0 drops all entries at that level. Levels without a configured rate are never sampled (always kept).

Sampling runs after level gating and filtering, and is safe for concurrent use. Calling WithSampling repeatedly for the same level keeps the last rate.

Example
package main

import (
	"os"

	"github.com/adamreaksmey/glow"
)

func main() {
	logger := glow.New(
		glow.WithLevel(glow.DebugLevel),
		glow.WithSampling(glow.DebugLevel, 0),
		glow.WithWriter(os.Stdout),
		glow.WithTheme(glow.ThemeClassic),
		glow.WithColor(false),
		glow.WithService("api"),
	)
	logger.Debug("sampled out")
	logger.Info("always kept when unconfigured")
}
Output:
INFO always kept when unconfigured

func WithService

func WithService(name string) Option

WithService overrides the automatic executable-derived service name.

func WithTheme

func WithTheme(theme Theme) Option

WithTheme selects the development presentation theme. Themes affect only development formatting; JSON and custom handlers ignore them.

func WithTimestamp

func WithTimestamp() Option

WithTimestamp enables inclusion of timestamps in development output. Timestamps are always present on structured Entry values for handlers.

func WithUnsafeShowSensitive

func WithUnsafeShowSensitive() Option

WithUnsafeShowSensitive disables redaction of Sensitive fields for this logger. The name is intentionally conspicuous: secrets will appear in every handler that honors the entry as built. Prefer leaving redaction enabled.

func WithWriter

func WithWriter(writer io.Writer) Option

WithWriter sets the default destination for the built-in development handler. When no explicit handlers are configured, Glow writes theme-formatted development output to this writer (default: os.Stdout).

type Theme

type Theme int

Theme selects a development presentation style. Themes affect formatting only; logging behavior is unchanged.

const (
	// ThemeDefault is NestJS-inspired: service, PID, timestamp, level, context, message.
	ThemeDefault Theme = iota
	// ThemeMinimal shows time-of-day, level, and message.
	ThemeMinimal
	// ThemeCompact shows a one-letter level and the message.
	ThemeCompact
	// ThemeClassic shows an uppercase level and the message.
	ThemeClassic
)

Built-in development themes.

func (Theme) String

func (theme Theme) String() string

String returns the theme name.

Directories

Path Synopsis
Package glowtest provides concurrency-safe log capture and assertions for tests.
Package glowtest provides concurrency-safe log capture and assertions for tests.
middleware
http
Package http provides framework-neutral net/http request logging for Glow.
Package http provides framework-neutral net/http request logging for Glow.
Package rotate provides a size-based rotating file writer for Glow.
Package rotate provides a size-based rotating file writer for Glow.

Jump to

Keyboard shortcuts

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