logging

package
v1.12.0 Latest Latest
Warning

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

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package logging is structured logging for Tern apps, on log/slog. A logger from New adds the request's ID to every record logged in a request, redacts passwords, tokens and other secrets, can sample floods of the same message, and ships records to collectors and log services through sinks, such as github.com/Unfield/Tern/logging/otlp, in the background:

logger := logging.New(logging.Options{
	Sinks: []logging.Sink{otlp.New(otlp.Options{})},
})
slog.SetDefault(logger)
server, err := ternApp(kit.WithLogger(logger))

kit flushes the sinks when the server shuts down. Under tern dev, records also appear in the dev server's log view, at /__tern/dev/logs.

The defaults come from the environment: TERN_LOG_LEVEL (debug, info, warn, error) and TERN_LOG_FORMAT (text, json). Without TERN_LOG_FORMAT, output is text on a terminal and under tern dev, JSON otherwise.

Index

Constants

View Source
const (
	// RequestIDKey is the key of the request's ID.
	RequestIDKey = "request_id"
	// TraceIDKey and SpanIDKey are the W3C trace context's IDs, from a
	// traceparent header the app trusts. The OTLP sink sends them as the
	// record's trace and span.
	TraceIDKey = "trace_id"
	SpanIDKey  = "span_id"
)

The keys kit uses for a request's attributes.

View Source
const Redacted = "[redacted]"

Redacted is what a redacted value is replaced with.

Variables

View Source
var DefaultRedact = []string{
	"password", "passwd", "new_password", "current_password",
	"secret", "client_secret",
	"token", "access_token", "refresh_token", "id_token", "bearer",
	"api_key", "apikey", "private_key", "secret_key", "signing_key",
	"authorization", "proxy_authorization",
	"cookie", "set_cookie",
	"session", "session_id", "sessionid",
	"csrf", "csrf_token", "xsrf_token",
	"otp", "totp", "recovery_code", "recovery_codes",
	"credit_card", "card_number", "cvv", "cvc",
}

DefaultRedact is the list of keys whose values are redacted unless Options.Keep names them. Keys match without regard to case, with "-" and "_" the same, and so do keys ending in "_token", "_secret", "_password" or "_key" after one of these words: "access_token" and "stripe_secret" are redacted, "cache_key" is not.

Functions

func Attrs

func Attrs(ctx context.Context) []slog.Attr

Attrs returns the attributes ctx carries, nil if none. The slice must not be changed.

func AttrsMap

func AttrsMap(attrs []slog.Attr) map[string]any

AttrsMap returns attrs as a map for encoding/json: groups as nested maps, times in RFC 3339, durations as strings, errors as their messages. A key that appears twice keeps its last value.

func Close

func Close(ctx context.Context, l *slog.Logger) error

Close flushes l's sinks and stops them; records logged after it are written to the output but not shipped.

func Flush

func Flush(ctx context.Context, l *slog.Logger) error

Flush waits until the records l logged so far have been sent to its sinks, or ctx ends. l is a logger from New or Wrap; for others it does nothing. kit calls it when the server shuts down.

func IsPermanent

func IsPermanent(err error) bool

IsPermanent reports whether err was marked with Permanent.

func New

func New(opts Options) *slog.Logger

New returns a logger as opts say.

func NewHandler

func NewHandler(opts Options) slog.Handler

NewHandler returns the handler of a logger from New, for use with handlers of other packages.

func Permanent

func Permanent(err error) error

Permanent marks an error from Export as one retrying won't fix, such as a rejected request: the batch is dropped.

func RedactURL

func RedactURL(u *url.URL) string

RedactURL returns u as a string with its password and the values of query parameters DefaultRedact names replaced, as log records have URLs.

func RequestID

func RequestID(ctx context.Context) string

RequestID returns the request ID kit gave the request ctx belongs to, or "".

func RetryAfter

func RetryAfter(err error, d time.Duration) error

RetryAfter marks an error from Export as one to retry after d.

func ValueOf

func ValueOf(v slog.Value) any

ValueOf returns v as a value for encoding/json.

func With

func With(ctx context.Context, attrs ...slog.Attr) context.Context

With returns a copy of ctx that carries attrs: every record logged with that context, through a logger from New or a handler from Wrap, includes them. kit adds the request's ID this way.

ctx = logging.With(ctx, slog.String("user", id))
slog.InfoContext(ctx, "invoice sent") // ... user=… request_id=…

Attributes with the same key as one already carried replace it.

func Wrap

func Wrap(h slog.Handler) slog.Handler

Wrap makes h add the attributes of the context a record is logged with, as handlers from NewHandler do, and under tern dev send records to the dev server's log view. A handler from NewHandler is returned as it is. kit wraps the handler of the logger it is given with it.

Types

type Batch

type Batch = batch.Options

Batch says how entries are sent to sinks. Entries wait in a queue; a batch goes out when Size have gathered or Interval has passed. A batch that fails is retried MaxRetries times, pausing 1s, 2s, 4s… up to 30s between tries; meanwhile new entries queue. When the queue holds Queue entries, new ones are dropped and counted (see Stats). The defaults: 512 entries, 1s, a queue of 16384, 5 retries and a 15s timeout.

type Entry

type Entry struct {
	Time    time.Time
	Level   slog.Level
	Message string
	// Attrs are the record's attributes. Groups are slog.KindGroup values.
	Attrs []slog.Attr
	// Source is where the record was logged, when [Options.AddSource] is
	// set.
	Source *slog.Source
}

Entry is a log record as sinks get it: its attributes resolved, redacted and nested in their groups, those of the context first.

func (Entry) Lookup

func (e Entry) Lookup(key string) (slog.Value, bool)

Lookup returns the value of the top-level attribute key.

func (Entry) Walk

func (e Entry) Walk(fn func(key string, v slog.Value))

Walk calls fn for each attribute of e that isn't a group, with the keys of the groups it is in joined by ".": "http.status" for status in group http.

type JSONEntry

type JSONEntry struct {
	Time    time.Time      `json:"time"`
	Level   string         `json:"level"`
	Message string         `json:"msg"`
	Attrs   map[string]any `json:"attrs,omitempty"`
	Source  *slog.Source   `json:"source,omitempty"`
}

JSONEntry is an entry as JSON: the shape tern dev's log view reads, and a plain one for sinks that send JSON objects.

func EncodeJSON

func EncodeJSON(entries []Entry) []JSONEntry

EncodeJSON returns entries as JSONEntry values.

type Options

type Options struct {
	// Level is the lowest level logged. Pass a *slog.LevelVar to change it
	// while the app runs. The default is TERN_LOG_LEVEL's, or Info.
	Level slog.Leveler
	// Format is "text" or "json". See the package documentation for the
	// default.
	Format string
	// Output is where formatted records go. The default is os.Stderr; use
	// io.Discard to only ship records to sinks.
	Output io.Writer
	// AddSource adds the file and line that logged each record.
	AddSource bool

	// Redact adds keys to [DefaultRedact]; Keep takes keys out of it.
	// NoDefaultRedact leaves only Redact.
	Redact          []string
	Keep            []string
	NoDefaultRedact bool

	// Sampling, when set, samples floods of records with the same message.
	Sampling *Sampling

	// Sinks get every record at or above Level (or [MinLevel]'s level), in
	// batches, from a goroutine each.
	Sinks []Sink
	// Batch says how records are batched and retried for sinks.
	Batch Batch
}

Options configures a logger.

type Sampling

type Sampling struct {
	First      int
	Thereafter int // 0 keeps none after the first First
	Tick       time.Duration
	// MaxLevel is the highest level sampled. The zero value is Info.
	MaxLevel slog.Level
}

Sampling keeps a burst of log records from drowning the rest and from costing what they cost to ship. Within each Tick, the first First records with the same level and message are kept, then every Thereafter-th one. Records above MaxLevel are always kept.

The default, when Options.Sampling is set with zero fields: 100 a second, then 1 in 100, for Info and below; warnings and errors are never sampled.

type Secret

type Secret string

Secret is a string that never shows in logs: its value logs as Redacted, whatever its key and whatever the handler.

logger.Info("calling the API", "key", logging.Secret(key))

func (Secret) LogValue

func (Secret) LogValue() slog.Value

LogValue implements slog.LogValuer.

func (Secret) String

func (Secret) String() string

String keeps the value out of fmt output too.

type Sink

type Sink interface {
	Export(ctx context.Context, entries []Entry) error
}

A Sink sends log entries somewhere: a collector, a log service, a file. Its Export gets them in batches from one goroutine, in the order they were logged; see Options.Batch for how batches are made and retried.

Export returns an error to have the batch sent again, after a growing pause, or Permanent to drop it. RetryAfter asks for a pause of its own, as a service's Retry-After header does.

func MinLevel

func MinLevel(s Sink, level slog.Leveler) Sink

MinLevel sends s only entries at level or above.

type SinkFunc

type SinkFunc func(ctx context.Context, entries []Entry) error

SinkFunc makes a function a Sink.

func (SinkFunc) Export

func (f SinkFunc) Export(ctx context.Context, entries []Entry) error

Export implements Sink.

type Stats

type Stats struct {
	// Sampled is the records sampling left out.
	Sampled uint64
	// Dropped is the entries left out because a sink's queue was full.
	Dropped uint64
	// Failed is the entries a sink failed to take after retrying.
	Failed uint64
}

Stats counts what a logger didn't ship.

func StatsOf

func StatsOf(l *slog.Logger) Stats

StatsOf returns l's counts, for a logger from New.

Directories

Path Synopsis
Package datadog is a logging sink that sends records to Datadog's log intake (HTTP API v2):
Package datadog is a logging sink that sends records to Datadog's log intake (HTTP API v2):
Package file is a logging sink that writes records to a file as JSON lines, rotating it by size:
Package file is a logging sink that writes records to a file as JSON lines, rotating it by size:
internal
sinktest
Package sinktest has helpers for the sinks' tests.
Package sinktest has helpers for the sinks' tests.
Package loki is a logging sink that pushes records to Grafana Loki:
Package loki is a logging sink that pushes records to Grafana Loki:
Package otlp is a logging sink that sends records to an OpenTelemetry collector, or any service that takes OTLP over HTTP (Grafana Cloud, Honeycomb, New Relic, SigNoz, Axiom…), as OTLP/HTTP JSON:
Package otlp is a logging sink that sends records to an OpenTelemetry collector, or any service that takes OTLP over HTTP (Grafana Cloud, Honeycomb, New Relic, SigNoz, Axiom…), as OTLP/HTTP JSON:

Jump to

Keyboard shortcuts

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