DocsLogFiltering

Filtering

Drop noisy records or shorten fat attribute values by level, message, or attribute match.

log.WithFilters wraps your outputs in a filter handler that runs an ordered list of filters over each record before it reaches the outputs. A filter selects records by level, message, or attribute, then either drops them, passes them through, or shortens a fat attribute value. Filters are built fluently.

log.New(
	log.WithText(os.Stdout),
	log.WithFilters(
		// drop everything below Info, a level floor
		log.Deny().Below(slog.LevelInfo),
		// drop a chatty subsystem, * is a prefix match
		log.Deny().Attr("component", "cache-*"),
		// truncate the fat "body" attr to 200 chars wherever it appears
		log.Shorten("body").Limit(200),
	),
)

Actions

Every filter starts from one of three actions.

Match criteria

Chain any number of criteria onto a filter. A record must match every criterion you set for the action to apply, and a criterion you leave unset is ignored.

log.Deny().Message("http response").Attr("status", "200")

.Message("...") matches a record whose message is exactly that string. .Attr(key, val) matches when the record's attribute equals val, and a val ending in * matches by prefix. The record's own message is available under the synthetic "msg" key. .Below(level) matches records strictly below level, so paired with Deny it acts as a floor.

Note

.Below is a floor, not a ceiling. Below(slog.LevelInfo) matches DEBUG and TRACE, the levels underneath INFO, so Deny().Below(slog.LevelInfo) drops them and lets everything from INFO up through.

Shortening values

Shorten rewrites matching attribute values in place rather than dropping the record. It replaces each named attribute with a truncated copy, using a trailing ... when there is room for it. This is how you keep a noisy field, such as a response body, without letting it bloat every line.

log.Shorten("body", "payload").Limit(120)

Changing filters at runtime

log.WithFilters builds a *FilterHandler internally. If you hold one directly, its filter list can change while logging. AddFilter appends one and SetFilters replaces the whole list, and both are safe to call concurrently with active logging.

fh := log.NewFilterHandler(base, log.Deny().Below(slog.LevelInfo))
logger := log.Wrap(slog.New(fh))

// later, quiet a subsystem without rebuilding the logger
fh.AddFilter(log.Deny().Attr("component", "cache-*"))