Documentation
¶
Overview ¶
Package zlog is an slog.Handler implementation focused on performant contextual logging.
Journald ¶
On Linux sytems, this package will automatically upgrade to speaking the native Journald protocol using the heuristic outlined on systemd.io. For this process, some information must be gathered via proc(5); exotic runtime configurations may not support this. The values "wmem_default" and "wmem_max" are consulted to determine optimal settings for the opened socket to journald and for when the memfd-based (see memfd_create(2) and unix(7)) protocol must be used.
Prose output ¶
If ProseFormat is set, output will be in prose rather than JSON. The field order is not configurable. ANSI color codes and terminal hyperlinks will be used when attached to a TTY. The environment variables "NO_COLOR" and "ZLOG_COLORS" can be used to control colors. Log records are separated by a ␞, fields are separated by a ␟, and the attributes are separated from the message with a ␝. These field separators may trip up incorrect programs.
ZLOG_COLORS ¶
The "ZLOG_COLORS" environment variable is akin to "LS_COLORS". It is a colon-delimited series of SGR parameters. Any characters outside of the range [0-;] will be ignored. The controllable colors are, in order:
- Error Level
- Warn Level
- Info Level
- Debug Level
- Source
- Timestamp
- Message
- Key
- string / fmt.Stringer
- bool (true)
- bool (false)
- Number (int64/uint64/float64)
- time.Time
- time.Duration
- error
- encoding.TextUnmarshaler
- fmt.GoStringer
- encoding.BinaryUnmarshaler / []byte
- json.Unmarshaler
- fmt.Print
All left-ward elements must be present, but may be empty. For example, to highlight only errors:
ZLOG_COLORS='::::::::::::::5'; export ZLOG_COLORS
See DefaultProseColors for the default colors.
Example ¶
h := NewHandler(os.Stdout, &ExampleOptions)
slog.New(h).With("a", "b").Info("test", "c", "d")
Output: {"level":"INFO","msg":"test","a":"b","c":"d"}
Example (Baggage) ¶
In this example, some values are extracted from the OpenTelemetry baggage and inserted into the record.
opts := ExampleOptions
opts.Baggage = func(_ string) bool { return true }
b := must(baggage.New(
must(baggage.NewMember("test_kind", "example")),
))
ctx := baggage.ContextWithBaggage(context.Background(), b)
h := NewHandler(os.Stdout, &opts)
slog.New(h).InfoContext(ctx, "test")
Output: {"level":"INFO","msg":"test","baggage":{"test_kind":"example"}}
Example (Pprof) ¶
In this example, some values are extracted from the pprof labels and inserted into the record.
ctx := pprof.WithLabels(context.Background(), pprof.Labels("test_kind", "example"))
pprof.SetGoroutineLabels(ctx)
h := NewHandler(os.Stdout, &ExampleOptions)
slog.New(h).InfoContext(ctx, "test")
Output: {"level":"INFO","msg":"test","goroutine":{"test_kind":"example"}}
Example (With_Attrs) ¶
In this example, there are values stored in the Context at a known key and then automatically retrieved and integrated into the record by the handler.
// With is a helper function to add values to the Context at the known key.
//
// Typically, a module would provide a helper to do this, and do it with
// less garbage. Any ordering or replacement semantics need to happen here;
// this example does not implement being able to remove keys from the
// Context.
with := func(ctx context.Context, args ...any) context.Context {
var s []slog.Attr
if v, ok := ctx.Value(SetAttrs).(slog.Value); ok {
s = v.Group()
}
s = append(s, slog.Group("", args...).Value.Group()...)
seen := make(map[string]struct{}, len(s))
del := func(a slog.Attr) bool {
_, ok := seen[a.Key]
seen[a.Key] = struct{}{}
return ok
}
slices.Reverse(s)
s = slices.DeleteFunc(s, del)
slices.Reverse(s)
return context.WithValue(ctx, SetAttrs, slog.GroupValue(s...))
}
// Setup:
ctx := context.Background()
h := NewHandler(os.Stdout, &ExampleOptions)
l := slog.New(h)
// Usage:
l.InfoContext(ctx, "without ctx attrs", "a", "b")
{
ctx := with(ctx, "contextual", "value")
l.InfoContext(ctx, "with ctx attrs", "a", "b")
{
ctx := context.WithValue(ctx, SetLevel, slog.LevelDebug)
ctx = with(ctx, "contextual", "level")
l.DebugContext(ctx, "with ctx attrs", "a", "b")
}
ctx = with(ctx, "appended", "value")
l.InfoContext(ctx, "with more ctx attrs")
}
l.InfoContext(ctx, "without ctx attrs", "a", "b")
Output: {"level":"INFO","msg":"without ctx attrs","a":"b"} {"level":"INFO","msg":"with ctx attrs","contextual":"value","a":"b"} {"level":"DEBUG","msg":"with ctx attrs","contextual":"level","a":"b"} {"level":"INFO","msg":"with more ctx attrs","contextual":"value","appended":"value"} {"level":"INFO","msg":"without ctx attrs","a":"b"}
Example (With_Level) ¶
In this example, the handler is configured with a very high minimum level, so without the per-record level filtering there would be no log messages.
// Per-record filter levels.
filters := []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError}
// Levels of records to emit.
levels := []slog.Level{slog.LevelDebug - 4, slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError}
// With is a helper function to add the log level to the Context at the known key.
//
// Typically, a module would provide a helper to do this.
with := func(ctx context.Context, l slog.Level) context.Context {
return context.WithValue(ctx, SetLevel, l)
}
// Setup:
ctx := context.Background()
opts := ExampleOptions
opts.Level = slog.Level(100)
h := NewHandler(os.Stdout, &opts)
log := slog.New(h)
// Usage:
a := slog.String("filter", "NONE")
for _, l := range levels {
log.LogAttrs(ctx, l, "", a)
}
for _, l := range filters {
a = slog.String("filter", l.String())
ctx := with(ctx, l)
for _, l := range levels {
log.LogAttrs(ctx, l, "", a)
}
}
Output: {"level":"DEBUG","msg":"","filter":"DEBUG"} {"level":"INFO","msg":"","filter":"DEBUG"} {"level":"WARN","msg":"","filter":"DEBUG"} {"level":"ERROR","msg":"","filter":"DEBUG"} {"level":"INFO","msg":"","filter":"INFO"} {"level":"WARN","msg":"","filter":"INFO"} {"level":"ERROR","msg":"","filter":"INFO"} {"level":"WARN","msg":"","filter":"WARN"} {"level":"ERROR","msg":"","filter":"WARN"} {"level":"ERROR","msg":"","filter":"ERROR"}
Index ¶
Examples ¶
Constants ¶
const DefaultProseColors = `31:33:32:3:96:93::36::1;32:1;31:1;33:32:95:33:4:34:35:21:91`
DefaultProseColors are the colors used when the "ZLOG_COLORS" environment variable isn't set.
Variables ¶
var ( // Everything is just a nice low number to almost certainly catch anything // emitted. LevelEverything = slog.Level(-100) SyslogDebug = slog.LevelDebug SyslogInfo = slog.LevelInfo SyslogNotice = slog.LevelInfo + 2 SyslogWarning = slog.LevelWarn SyslogError = slog.LevelError SyslogCritical = slog.LevelError + 4 SyslogAlert = slog.LevelError + 8 // Emergency is documented as "a panic condition". // // This package does no special handling for Go panics. SyslogEmergency = slog.LevelError + 12 )
Some extra slog.Level aliases and syslog(3) compatible levels (as implemented in this package).
The syslog mapping attempts to keep the slog convention of a 4-count gap between levels.
Functions ¶
func NewHandler ¶
NewHandler returns an slog.Handler emitting records to "w", according to the provided options.
If "nil" is passed for options, suitable defaults will be used. On Linux systems, the journald native protocol will be used if the process is launched with the appropriate environment variables and the passed io.Writer is os.Stderr. The default for a process running inside a Kubernetes container or as systemd service is to not emit timestamps.
Types ¶
type Options ¶
type Options struct {
// Level is the minimum level that a log message must have to be processed
// by the Handler.
//
// This can be overridden on a per-message basis by storing a [slog.Level]
// at [LevelKey].
Level slog.Leveler
// Baggage is a selection function for keys in the OpenTelemetry Baggage
// contained in the [context.Context] used with a log message.
Baggage func(key string) bool
// WriteError is a hook for receiving errors that occurred while attempting
// to write the log message.
WriteError func(context.Context, error)
// OmitSource controls whether source position information should be
// emitted.
OmitSource bool
// OmitTime controls whether a timestamp should be emitted.
OmitTime bool
// ProseFormat controls whether the lines should be emitted in prose or
// JSON format.
//
// When connected to the Journal, this setting has no effect.
ProseFormat bool
// ContextKey is a value to be used with [context.Context.Value] to retrieve a
// [slog.Value] Group.
//
// Setting this to a value that results in retrieving any other type will
// panic the program.
ContextKey any
// LevelKey is a value to be used with [context.Context.Value] to retrieve a
// [slog.Leveler] to use on a per-record basis.
//
// Setting this to a value that results in retrieving any other type will
// panic the program.
LevelKey any
// contains filtered or unexported fields
}
Options is used to configure the slog.Handler returned by NewHandler.
Notes ¶
Bugs ¶
The JSON encoding behavior for DEL (0x7F) seems wrong, but matches Go's encoding/json behavior.