loq

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 29, 2026 License: MPL-2.0 Imports: 20 Imported by: 0

README

Loq: Logging for Humans

This package provides components for use with Go's slog framework, intended to make logs easy to write and easier to read.

Highlights:

  • Uncluttered line-oriented output
  • Printf-style message formatting
  • Color coding by severity level
  • Named severity levels beyond those defined by slog
  • Handler trees with inheritable severity levels

The handlers and wrappers in loq are interchangeable parts that can be used with each other, combined with slog types, or passed to code that accepts slog types. In many cases, they are also faster than what slog offers.

The only dependencies are from Go's standard library and X-Repositories.

Printf-style Message Formatting

Logger methods and top-level functions support Printf-style formatting.

package main

import "loq"
import "os"

func main() {
    logger := loq.AsLogger(loq.NewHandler(os.Stderr, nil))
    logger.Infof("Hello, %s.", "world")
}
2025-09-01 13:30:00 Hello, world.
Simple Logger + Handler Creation

For brevity, a Logger and Handler can be created together with a single function call.

package main

import "loq"
import "os"

func main() {
    logger := loq.New(os.Stderr, nil)
    logger.Info("Hello, logger.")
}
2025-09-01 13:30:00 Hello, logger.
Output Fields

Log record fields and attributes can be chosen and placed with a format string.

package main

import "loq"
import "os"

func main() {
    opts := loq.Options{ Format: "{message}" }
    logger := loq.New(os.Stderr, &opts)
    logger.Info("Hello, friend.")

    opts = loq.Options{ Format: "{source} {message}" }
    logger = loq.New(os.Stderr, &opts)
    logger.Info("Hello, source code.")

    opts = loq.Options{ Format: "{time} {level} {source} {message} {attrs}" }
    logger = loq.New(os.Stderr, &opts)
    logger.Info("Hello, all.", "myattr", 123)

    opts = loq.Options{ Format: "{attr.myattr} {message}" }
    logger = loq.New(os.Stderr, &opts)
    logger.Info("Hello, attribute.", "myattr", 123)
}
Hello, friend.
/build/hello/main.go:13 Hello, source code.
2025-09-01 13:30:00 INFO /build/hello/main.go:17 Hello, all. myattr=123
123 Hello, attribute.
Color

Severity level color coding can be applied to any field, or all of them.

package main

import "loq"
import "os"

func main() {
    opts := loq.Options{ Format: "{time} {color}{level}{nocolor} {message}" }
    logger := loq.New(os.Stderr, &opts)
    logger.Info("Doing work.")
    logger.Warn("Low memory.")
    logger.Error("Work failed.")
}

(The following example output is encoded as TeX because Markdown lacks color. Viewing it may require JavaScript.)

$\color{#999} \texttt{2025-09-01 14:30:00 } \texttt{INFO} \texttt{ Doing work.}\newline$ $\color{#999} \texttt{2025-09-01 14:30:00 } {\color{#db0}\texttt{WARN}} \texttt{ Low memory.}\newline$ $\color{#999} \texttt{2025-09-01 14:30:00 } {\color{#e22}\texttt{ERROR}} \texttt{ Work failed.}\newline$

Not shown: Custom level-to-color mappings are possible, as are custom colors if supported by the terminal.

Color works on all major desktop operating systems. It queries terminal capabilities rather than hard-coding ANSI escape sequences, and disables itself when writing to a device that does not support color.

Severity Levels

Supplementing the slog severity levels, three new named levels are supported: LevelTrace, LevelNotice, and LevelCrit.

package main

import "loq"
import "os"

func main() {
    opts := loq.Options{ Format: "{level}", Level: loq.LevelTrace }
    logger := loq.New(os.Stderr, &opts)
    for level := loq.LevelTrace; level <= loq.LevelCrit; level += 2 {
        logger.Log(nil, level, "message")
    }
}
TRACE
TRACE+2
DEBUG
DEBUG+2
INFO
NOTICE
WARN
WARN+2
ERROR
ERROR+2
CRIT

Not shown: All severity levels can be given custom names, to appear when a record's level field is written.

Handler Trees

Any slog.Handler can be wrapped in a hierarchy of named TreeHandlers, each with a separately chosen or inherited minimum severity level.

package main

import "loq"
import "os"

func main() {
    opts := loq.Options{ Format: "{level} {msg}" }
    root := loq.AsTreeLogger(loq.NewHandler(os.Stderr, &opts), "")

    branchlevel := loq.LevelVar{}  // defaults to LevelInfo
    // Logger exposes TreeHandler methods like Child, for easy access.
    branch := root.Child("branch", &branchlevel)

    leaf := branch.Child("leaf", loq.LevelInherit)

    root.Info("hello root")
    branch.Info("hello branch")
    leaf.Info("hello leaf")

    branchlevel.Set(loq.LevelWarn)
    // Since our leaf handler inherits the minimum severity level
    // of its parent, which is now at LevelWarn,
    // this lower-severity message will be dropped.
    leaf.Info("hello leaf (dropped by branch severity level)")

    branchlevel.Set(loq.LevelInfo)
    leaf.Info("hello leaf (allowed by branch severity level)")

    // A TreeHandler (or a Logger using it)
    // can find any of its relatives by name.
    leaf2 := root.Named("branch.leaf", nil)
    if leaf2.Handler() == leaf.Handler() {
        leaf2.Info("hello leaf (retrieved by its full name)")
    }
}
INFO hello root
INFO hello branch
INFO hello leaf
INFO hello leaf (allowed by branch severity level)
INFO hello leaf (retrieved by its full name)
Field Framing

Text fields can be terminated with a special character (Tab by default) to allow reliable parsing when followed by other data.

If the special character appears in the field text, it is doubled or (optionally) preceded by an escape character when written.

The terminator is not written at the end of a line.

package main

import "loq"
import "os"

func main() {
    opts := loq.Options{ Format: "{message:t}{attrs}" }
    logger := loq.New(os.Stderr, &opts)
    logger.Info("Tab terminator", "myattr", 123)
    logger.Info("Tab terminator")

    opts = loq.Options{ Format: "{message:t|}{attrs}" }
    logger = loq.New(os.Stderr, &opts)
    logger.Info("Pipe terminator", "myattr", 123)
    logger.Info("Pipe|terminator", "myattr", 123)
    logger.Info("Pipe terminator")
}
Tab terminator	myattr=123
Tab terminator
Pipe terminator|myattr=123
Pipe||terminator|myattr=123
Pipe terminator

Not shown: Alternatively, text fields can be framed in quotes with escape sequences, for consumption by Go string literal parsers. A third framing style that replaces blank fields with a special character is also available.

Time Format

Time fields and attributes can use any layout supported by Go's time package.

package main

import "loq"
import "os"
import "time"

func main() {
    opts := loq.Options{ Time: time.TimeOnly }
    logger := loq.New(os.Stderr, &opts)
    logger.Info("Something happened.")

    opts = loq.Options{ Time: time.RFC3339 }
    logger = loq.New(os.Stderr, &opts)
    logger.Info("Something happened.")
}
13:30:00 Something happened.
2025-09-01T13:30:00+01:00 Something happened.
Compatibility

Types implement the usual slog interfaces, so they can be mixed and matched with other logging components.

package main

import "log/slog"
import "loq"
import "os"

func main() {
    logger1 := loq.New(os.Stderr, nil)
    slog.SetDefault(logger1.Logger)
    slog.Info("loq.Logger passed as slog.Logger")

    handler2 := loq.NewHandler(os.Stderr, nil)
    logger2 := slog.New(handler2)
    logger2.Info("slog.Logger + loq.Handler")

    handler3 := slog.NewTextHandler(os.Stderr, nil)
    logger3 := loq.AsLogger(handler3)
    logger3.Infof("loq.Logger + %s", "TextHandler")

    handler4 := slog.NewJSONHandler(os.Stderr, nil)
    logger4 := loq.AsTreeLogger(handler4, "")
    logger4.Info("TreeHandler + JSONHandler")
}
2025-09-01 13:30:00 loq.Logger passed as slog.Logger
2025-09-01 13:30:00 slog.Logger + loq.Handler
time=2025-09-01T13:30:00.123+01:00 level=INFO msg="loq.Logger + TextHandler"
{"time":"2025-09-01T13:30:00.123456789+01:00","level":"INFO","msg":"TreeHandler + JSONHandler"}

Copyright (C) 2025-2026 Forest <forestix@kcat.cc>

This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/

Documentation

Overview

Package loq provides components for use with Go's log/slog library, intended to make logs easy to write and easier to read.

Handler offers:

  • Uncluttered line-oriented output, without giving up machine-parsable structure.
  • Color coding by severity level.
  • Named severity levels beyond those defined by slog.
  • Flexible order and selection of fields.
  • Customizable time format.
  • Generally better performance and fewer allocations than slog's handlers.

TreeHandler offers:

  • A handler hierarchy with inheritable log levels, useful for filtering log output from components within a program. (Works with any slog-compatible handler.)

Logger offers:

  • Printf-style message formatting. (Works with any slog-compatible handler.)
  • An embedded *slog.Logger for API compatibility.

Index

Constants

View Source
const LevelInherit slog.Level = math.MinInt

LevelInherit tells a TreeHandler to use its parent's severity level.

Variables

This section is empty.

Functions

func Crit

func Crit(msg string, args ...any)

Crit is equivalent to Log at LevelCrit.

func CritAf

func CritAf(attrs []slog.Attr, format string, args ...any)

CritAf is equivalent to LogAf at LevelCrit.

func Critaf

func Critaf(attrs []any, format string, args ...any)

Critaf is equivalent to Logaf at LevelCrit.

func Critf

func Critf(format string, args ...any)

Critf is equivalent to Logf at LevelCrit.

func Debug

func Debug(msg string, args ...any)

Debug is equivalent to Log at LevelDebug.

func DebugAf

func DebugAf(attrs []slog.Attr, format string, args ...any)

DebugAf is equivalent to LogAf at LevelDebug.

func Debugaf

func Debugaf(attrs []any, format string, args ...any)

Debugaf is equivalent to Logaf at LevelDebug.

func Debugf

func Debugf(format string, args ...any)

Debugf is equivalent to Logf at LevelDebug.

func Error

func Error(msg string, args ...any)

Error is equivalent to Log at LevelError.

func ErrorAf

func ErrorAf(attrs []slog.Attr, format string, args ...any)

ErrorAf is equivalent to LogAf at LevelError.

func Erroraf

func Erroraf(attrs []any, format string, args ...any)

Erroraf is equivalent to Logaf at LevelError.

func Errorf

func Errorf(format string, args ...any)

Errorf is equivalent to Logf at LevelError.

func Info

func Info(msg string, args ...any)

Info is equivalent to Log at LevelInfo.

func InfoAf

func InfoAf(attrs []slog.Attr, format string, args ...any)

InfoAf is equivalent to LogAf at LevelInfo.

func Infoaf

func Infoaf(attrs []any, format string, args ...any)

Infoaf is equivalent to Logaf at LevelInfo.

func Infof

func Infof(format string, args ...any)

Infof is equivalent to Logf at LevelInfo.

func Log

func Log(ctx context.Context, level slog.Level, msg string, args ...any)

Log mirrors slog.Log, with optimizations for our Handler type.

func LogAf

func LogAf(
	level slog.Level,
	attrs []slog.Attr,
	format string,
	args ...any)

LogAf logs to slog.Default with attributes and Printf-style message formatting.

func LogAttrs

func LogAttrs(
	ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)

LogAttrs mirrors slog.LogAttrs, with optimizations for our Handler type.

func Logaf

func Logaf(
	level slog.Level,
	attrs []any,
	format string,
	args ...any)

Logaf logs to slog.Default with attributes processed as slog.Logger.Log does, and Printf-style message formatting.

func Logf

func Logf(level slog.Level, format string, args ...any)

Logf logs to slog.Default with Printf-style message formatting.

func Notice

func Notice(msg string, args ...any)

Notice is equivalent to Log at LevelNotice.

func NoticeAf

func NoticeAf(attrs []slog.Attr, format string, args ...any)

NoticeAf is equivalent to LogAf at LevelNotice.

func Noticeaf

func Noticeaf(attrs []any, format string, args ...any)

Noticeaf is equivalent to Logaf at LevelNotice.

func Noticef

func Noticef(format string, args ...any)

Noticef is equivalent to Logf at LevelNotice.

func Trace

func Trace(msg string, args ...any)

Trace is equivalent to Log at LevelTrace.

func TraceAf

func TraceAf(attrs []slog.Attr, format string, args ...any)

TraceAf is equivalent to LogAf at LevelTrace.

func Traceaf

func Traceaf(attrs []any, format string, args ...any)

Traceaf is equivalent to Logaf at LevelTrace.

func Tracef

func Tracef(format string, args ...any)

Tracef is equivalent to Logf at LevelTrace.

func Warn

func Warn(msg string, args ...any)

Warn is equivalent to Log at LevelWarn.

func WarnAf

func WarnAf(attrs []slog.Attr, format string, args ...any)

WarnAf is equivalent to LogAf at LevelWarn.

func Warnaf

func Warnaf(attrs []any, format string, args ...any)

Warnaf is equivalent to Logaf at LevelWarn.

func Warnf

func Warnf(format string, args ...any)

Warnf is equivalent to Logf at LevelWarn.

Types

type Handler

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

Handler handles log records produced by a Logger, writing each record to an io.Writer as a line of text. Output formatting follows the Options passed to NewHandler. A *Handler implements the slog.Handler interface. All of its methods are safe for concurrent use.

func NewHandler

func NewHandler(out io.Writer, opts *Options) *Handler

NewHandler creates a Handler that writes to out, using the given Options. If opts is nil, uses default options. It may run the tput command (unix) or change console mode (Windows) if the Options.Format string contains a {color} placeholder. (Handler.RestoreTerminal will restore the original console mode.)

func (*Handler) Enabled

func (h *Handler) Enabled(_ context.Context, level slog.Level) bool

func (*Handler) Handle

func (h *Handler) Handle(_ context.Context, rec slog.Record) error

func (*Handler) RestoreTerminal

func (h *Handler) RestoreTerminal()

RestoreTerminal restores the terminal to its original mode if NewHandler() changed it. This is not strictly necessary, since terminal mode is only changed to enable color support on Windows, where leaving it enabled after a program exits generally does no harm. Nevertheless, programs are encouraged to call this method when exiting, out of respect for the next program to run in the same Windows console. It does nothing on unix.

func (*Handler) WithAttrs

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

func (*Handler) WithGroup

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

type Level

type Level = slog.Level
const (
	LevelTrace  Level = slog.LevelDebug - 4
	LevelDebug  Level = slog.LevelDebug
	LevelInfo   Level = slog.LevelInfo
	LevelNotice Level = slog.LevelInfo + 2
	LevelWarn   Level = slog.LevelWarn
	LevelError  Level = slog.LevelError
	LevelCrit   Level = slog.LevelError + 4
)

type LevelVar

type LevelVar = slog.LevelVar

type Logger

type Logger struct {
	*slog.Logger
}

Logger wraps *slog.Logger to add Printf-style formatting, as well as optimizations when used with our Handler type. All of its methods are safe for concurrent use.

func AsLogger

func AsLogger(h slog.Handler) Logger

AsLogger creates a Logger with the given handler.

func AsTreeLogger

func AsTreeLogger(h slog.Handler, key string) Logger

AsTreeLogger creates a Logger with a new TreeHandler wrapping the given handler.

If the given attribute key is not empty, named descendants of the TreeHandler will add their name to each log record in an attribute with this key. (Note: This feature and WithGroup are not recommended for use together, since that would place the attribute in the group instead of at top level. Instead, consider exposing a name via With before calling WithGroup.)

func New

func New(out io.Writer, opts *Options) Logger

New creates a Logger with a new Handler. Arguments are passed to NewHandler.

func NewTreeLogger

func NewTreeLogger(out io.Writer, opts *Options, key string) Logger

NewTreeLogger creates a Logger with a new TreeHandler wrapping a new Handler.

If the given attribute key is not empty, named descendants of the TreeHandler will add their name to each log record in an attribute with this key. (Note: This feature and WithGroup are not recommended for use together, since that would place the attribute in the group instead of at top level. Instead, consider exposing a name via With before calling WithGroup.)

func (Logger) Child

func (l Logger) Child(name string, level slog.Leveler) Logger

Child creates a TreeHandler-based logger descended from an existing one, with the handler returned by TreeHandler.Child.

func (Logger) Crit

func (l Logger) Crit(msg string, args ...any)

Crit is equivalent to Logger.Log at LevelCrit.

func (Logger) CritAf

func (l Logger) CritAf(attrs []slog.Attr, format string, args ...any)

CritAf is equivalent to Logger.LogAf at LevelCrit.

func (Logger) Critaf

func (l Logger) Critaf(attrs []any, format string, args ...any)

Critaf is equivalent to Logger.Logaf at LevelCrit.

func (Logger) Critf

func (l Logger) Critf(format string, args ...any)

Critf is equivalent to Logger.Logf at LevelCrit.

func (Logger) Debug

func (l Logger) Debug(msg string, args ...any)

Debug is equivalent to Logger.Log at LevelDebug.

func (Logger) DebugAf

func (l Logger) DebugAf(attrs []slog.Attr, format string, args ...any)

DebugAf is equivalent to Logger.LogAf at LevelDebug.

func (Logger) Debugaf

func (l Logger) Debugaf(attrs []any, format string, args ...any)

Debugaf is equivalent to Logger.Logaf at LevelDebug.

func (Logger) Debugf

func (l Logger) Debugf(format string, args ...any)

Debugf is equivalent to Logger.Logf at LevelDebug.

func (Logger) Error

func (l Logger) Error(msg string, args ...any)

Error is equivalent to Logger.Log at LevelError.

func (Logger) ErrorAf

func (l Logger) ErrorAf(attrs []slog.Attr, format string, args ...any)

ErrorAf is equivalent to Logger.LogAf at LevelError.

func (Logger) Erroraf

func (l Logger) Erroraf(attrs []any, format string, args ...any)

Erroraf is equivalent to Logger.Logaf at LevelError.

func (Logger) Errorf

func (l Logger) Errorf(format string, args ...any)

Errorf is equivalent to Logger.Logf at LevelError.

func (Logger) Info

func (l Logger) Info(msg string, args ...any)

Info is equivalent to Logger.Log at LevelInfo.

func (Logger) InfoAf

func (l Logger) InfoAf(attrs []slog.Attr, format string, args ...any)

InfoAf is equivalent to Logger.LogAf at LevelInfo.

func (Logger) Infoaf

func (l Logger) Infoaf(attrs []any, format string, args ...any)

Infoaf is equivalent to Logger.Logaf at LevelInfo.

func (Logger) Infof

func (l Logger) Infof(format string, args ...any)

Infof is equivalent to Logger.Logf at LevelInfo.

func (Logger) Log

func (l Logger) Log(
	ctx context.Context, level slog.Level, msg string, args ...any)

Log overrides slog.Logger.Log, with optimizations for our Handler type.

func (Logger) LogAf

func (l Logger) LogAf(
	level slog.Level,
	attrs []slog.Attr,
	format string,
	args ...any)

LogAf logs with attributes and Printf-style message formatting.

func (Logger) LogAttrs

func (l Logger) LogAttrs(
	ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)

LogAttrs overrides slog.Logger.LogAttrs, with optimizations for our Handler type.

func (Logger) LogWrapped

func (l Logger) LogWrapped(
	ctx context.Context,
	level slog.Level,
	attrs []slog.Attr,
	anyattrs []any,
	msg string,
	fmtargs []any)

LogWrapped builds and handles a slog.Record on behalf of methods like Logf. It must be called through exactly one wrapper function in order for the Record.PC field (representing the log call site) to be correct.

This method is exported for the sake of applications wishing to log through their own helper/wrapper functions.

func (Logger) Logaf

func (l Logger) Logaf(
	level slog.Level,
	attrs []any,
	format string,
	args ...any)

Logaf logs with attributes processed as slog.Logger.Log does, and Printf-style message formatting.

func (Logger) Logf

func (l Logger) Logf(level slog.Level, format string, args ...any)

Logf logs with Printf-style message formatting.

func (Logger) Named

func (l Logger) Named(name string, level slog.Leveler) Logger

Named creates a TreeHandler-based logger related to an existing one, with the handler returned by TreeHandler.Named.

func (Logger) Notice

func (l Logger) Notice(msg string, args ...any)

Notice is equivalent to Logger.Log at LevelNotice.

func (Logger) NoticeAf

func (l Logger) NoticeAf(attrs []slog.Attr, format string, args ...any)

NoticeAf is equivalent to Logger.LogAf at LevelNotice.

func (Logger) Noticeaf

func (l Logger) Noticeaf(attrs []any, format string, args ...any)

Noticeaf is equivalent to Logger.Logaf at LevelNotice.

func (Logger) Noticef

func (l Logger) Noticef(format string, args ...any)

Noticef is equivalent to Logger.Logf at LevelNotice.

func (Logger) RestoreTerminal

func (l Logger) RestoreTerminal()

RestoreTerminal calls Handler.RestoreTerminal if this Logger uses a Handler (or a TreeHandler wrapping one). It does nothing if this Logger uses some other handler type. Programs are encouraged to call it when exiting.

func (Logger) Trace

func (l Logger) Trace(msg string, args ...any)

Trace is equivalent to Logger.Log at LevelTrace.

func (Logger) TraceAf

func (l Logger) TraceAf(attrs []slog.Attr, format string, args ...any)

TraceAf is equivalent to Logger.LogAf at LevelTrace.

func (Logger) Traceaf

func (l Logger) Traceaf(attrs []any, format string, args ...any)

Traceaf is equivalent to Logger.Logaf at LevelTrace.

func (Logger) Tracef

func (l Logger) Tracef(format string, args ...any)

Tracef is equivalent to Logger.Logf at LevelTrace.

func (Logger) Warn

func (l Logger) Warn(msg string, args ...any)

Warn is equivalent to Logger.Log at LevelWarn.

func (Logger) WarnAf

func (l Logger) WarnAf(attrs []slog.Attr, format string, args ...any)

WarnAf is equivalent to Logger.LogAf at LevelWarn.

func (Logger) Warnaf

func (l Logger) Warnaf(attrs []any, format string, args ...any)

Warnaf is equivalent to Logger.Logaf at LevelWarn.

func (Logger) Warnf

func (l Logger) Warnf(format string, args ...any)

Warnf is equivalent to Logger.Logf at LevelWarn.

func (Logger) With

func (l Logger) With(args ...any) Logger

With overrides slog.Logger.With to return our Logger type

func (Logger) WithGroup

func (l Logger) WithGroup(name string) Logger

WithGroup overrides slog.Logger.WithGroup to return our Logger type

type Options

type Options struct {
	// Defines log record field placement in a line of output.
	// Each field is represented by a {fieldname} placeholder,
	// as described in the [Options] documentation.
	// Text between placeholders is preserved.
	// Any occurence of {{ double open braces outside of placeholders
	// reduces to one { brace.
	// Any occurence of }} double close braces within placeholders
	// reduces to one } brace.
	// If empty, Format defaults to "{time} {message:t\t}{attrs}".
	Format string

	// A [time.Layout] string for {time} placeholders
	// and [slog.KindTime] attributes.
	// If empty, defaults to [time.DateTime].
	Time string

	// Custom severity level color codes for use by {color} placeholders.
	//
	// Each mapped color applies to its assigned severity level
	// and levels that are numerically higher,
	// until the next level with a mapped color is reached.
	// The lowest level's color also applies to levels below it.
	//
	// A color code is a string that can be either a single letter or
	// a six-digit hexadecimal number preceded by a # character.
	//
	// Lowercase letters rgbcmykw represent the 8 basic palette colors:
	// r=red g=green b=blue c=cyan m=magenta y=yellow k=black w=white
	//
	// Uppercase letters RGBCMYKW represent brighter versions of those colors,
	// falling back to the basic colors if unsupported by the output terminal.
	//
	// Lowercase letter a represents gray or a dim version of the
	// default text color, depending on the terminal's capabilities.
	// This is generally a better choice than K (bright black) because
	// it adapts to a wider variety of terminals.
	//
	// A # followed by a numeric color code directly specifies
	// a color's red, green, and blue component intensities,
	// using two hexadecimal digits for each of red, green, and blue: #RRGGBB
	// If the output terminal (or terminfo entry) lacks direct color support,
	// it falls back to no change in color (no-op).
	//
	// An underscore instead of a letter or numeric code represents
	// no change in color relative to preceding text.
	//
	// If this map is empty, a default map will be used: {
	//     LevelTrace:  "a", // dim
	//     LevelInfo:   "_", // unchanged color
	//     LevelNotice: "g", // green
	//     LevelWarn:   "y", // yellow
	//     LevelError:  "r", // red
	// }
	Colors map[Level]string

	// Custom severity level names for use by {level} placeholders.
	// If this map is empty, default level names will be used.
	Levels map[Level]string

	// Determines the minimum [slog.Record.Level]
	// required for a record to be written.
	// Programs wishing to dynamically adjust this level
	// can use a [slog.LevelVar].
	// If nil, defaults to [slog.LevelInfo].
	Level slog.Leveler
	// contains filtered or unexported fields
}

Options contains options for a Handler...

Format String

The Format string contains static text and field placeholders, defining what information should be included in each log message.

Field Placeholders

A placeholder in the format string indicates where a log record field or other dynamic value should appear in log messages. It is written in the format string as {fieldname}.

Some placeholders accept optional subformats, written as {fieldname:subformat}.

  • {time}

    Represents a slog.Record.Time field.

    The subformat is a time.Layout string. If not given, it defaults to the value in Options.Time.

    Example: "{time:15:04:05}"

  • {level}

    Represents a slog.Record.Level field. Produces the name of a log record's severity level or a description of that level relative to a named level.

    Default level names can be overridden by Options.Levels.

  • {message} or {msg}

    Represents a slog.Record.Message field.

    The subformat is a framing style, described in the next section.

  • {attr.K}

    Represents a single attribute with the given key K. An attribute within a group can be chosen with a dotted key: {attr.G.K}

    The subformat is a framing style, described in the next section.

  • {attributes} or {attrs}

    Represents the attributes in a slog.Record field, except any that are referenced in an {attr.K} placeholder.

    Attributes are formatted as a space-separated sequence of group.name=value entries. Non-numeric values use the quoted framing style, described in the next section. (A bool value is numeric.)

  • {source}

    Represents a logger call's location within the source code.

    The subformat has two parts: a layout template and a framing style. Both are optional.

    The layout template uses letters n p l f to represent the file name, path, line, and function where the logger was called. All other runes in the layout template are written verbatim, so custom punctuation can be placed between those subfields.

    Example: "{source:n:l f}"

    Output: main.go:13 main.myfunc

    If no layout template is given, it defaults to p:l

    Example: "{source}"

    Output: /build/hello/main.go:13

    The framing style is described in the next section. If given, it must immediately follow the layout template. (In other words, it is the last part of the subformat.)

    Example: "{source:n:l fq}"

    Output: "main.go:13 main.myfunc"

  • {color}

    Represents the start of severity level color-coding according to Options.Colors.

    Emits nothing when the output device is not a terminal/console.

  • {nocolor}

    Represents the end of severity level color-coding.

    Emits nothing when the output device is not a terminal/console.

Framing Styles:

A framing style is a way of encoding a dynamic value so that a parser can reliably distinguish it from surrounding text. It is chosen with 1-3 characters at the end of a field placeholder's subformat.

These framing styles are available:

  • The q (quoted) framing style

    Writes a value surrounded by quotes, using single-character backslash escape codes \a\b\t\n\v\f\r\"\\ in place of their corresponding non-printable characters. Characters lacking single-character backslash escape codes (including ASCII 0-6, 14-31, 127) are written verbatim. Programs needing to write these as \xNN escape sequences should use methods like Logger.Infof("%q") rather than relying on this framing style.

    Example: "{message:q} bye"

    Message: hello-world

    Output: "hello-world" bye

  • The tC (terminator character) framing style

    Writes a value followed by the given character C. Any appearance of that character within the value is escaped in the output by doubling it. (C must be an ASCII character.)

    Example: "{message:t-} bye"

    Message: hello-world

    Output: hello--world- bye

  • The tBC (terminator with custom escape char) variant framing style

    Writes a value followed by the given character C. Any appearance of either B or C within the value is escaped in the output by prepending character B. (B and C must be ASCII characters.)

    This variant might be helpful with log parsers that expect the field terminator to be escaped by something other than itself, such as a backslash.

    Example: `{message:t\-} bye`

    Message: hello-world

    Output: hello\-world- bye

  • The t (terminator default) variant framing style, with no terminator character specified, defaults to the Tab character for B and C.

    This use of whitespace as a terminator avoids visual clutter in the output.

    Example: "{message:t}"

    Message: hello-world

    Output: hello-world

  • The bC (blank replacement character) framing style

    Writes a value, or the given character C if the value is an empty string.

    Example: "{message:b*} bye"

    Message:

    Output: * bye

  • The b (blank replacement default) variant framing style

    Writes a value, or the - character if the value is an empty string.

    Example: "{message:b} bye"

    Message:

    Output: - bye

The t framing style and all of its variants will omit the terminator character if it would appear at the end of a log line, to reduce visual clutter.

Framing styles are supported only in placeholders that can produce unpredictable values, such as {message}. Other placeholders can be made to match by surrounding/following them with static characters in the format string.

type TreeHandler

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

TreeHandler is a slog.Handler that wraps any other handler, and allows creating named children to form a tree. Each child can have its own minimum severity level or inherit that of its parent. (The root TreeHandler defers to the wrapped handler's severity level.)

Related TreeHandlers can be used by a program's various components and subcomponents, for individual control of their log output.

All TreeHandler methods are safe for concurrent use.

func AsTreeHandler

func AsTreeHandler(h slog.Handler, key string) *TreeHandler

AsTreeHandler wraps a slog.Handler in a TreeHandler, starting a new tree.

If the given handler is already a TreeHandler, it is simply returned.

If the given attribute key is not empty, named descendants of the TreeHandler will add their name to each log record as an attribute with that key. (Note: This feature and WithGroup are not recommended for use together, since that would place the attribute in a group instead of at top level. Instead, consider exposing a name via WithAttrs before calling WithGroup.)

func (*TreeHandler) Child

func (h *TreeHandler) Child(name string, level slog.Leveler) *TreeHandler

Child calls Named to create or retrieve a TreeHandler descended from h. A dot and the given name are appended to that of h to form the new name. See TreeHandler.Named for details.

func (*TreeHandler) Enabled

func (h *TreeHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled implements the slog.Handler method of the same name.

func (*TreeHandler) Handle

func (h *TreeHandler) Handle(ctx context.Context, rec slog.Record) error

Handle implements the slog.Handler method of the same name.

func (*TreeHandler) Named

func (h *TreeHandler) Named(name string, level slog.Leveler) *TreeHandler

Named creates or retrieves a named TreeHandler related to h. If a handler by the given name already exists, returns it and discards level. If creating a new one, assigns it the given Leveler.

Dots in the name can be used to indicate hierarchy, much like slashes in a filesystem path. For example, a TreeHandler named "parent.child" descends from the one named "parent", which descends from the root TreeHandler.

Named automatically creates any ancestors that do not yet exist, assigning them LevelInherit. Since a TreeHandler and its Leveler cannot be replaced once created, any TreeHandler requiring a level other than LevelInherit must be explicitly created before its descendants. Using TreeHandler.Child instead of this method can prevent mistakes here.

The root TreeHandler can be retrieved by passing an empty name.

func (*TreeHandler) WithAttrs

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

WithAttrs implements the slog.Handler method of the same name.

func (*TreeHandler) WithGroup

func (h *TreeHandler) WithGroup(name string) slog.Handler

WithGroup implements the slog.Handler method of the same name.

Jump to

Keyboard shortcuts

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