Filtering
Excluding, including, matching helpers, sampling and rate limiting.
Filters run after enrichment and before the sinks, so they can see every property an event will carry. A filter is (event) => boolean; true keeps the event. All filters must agree.
import { configure, byExcluding, byIncludingOnly, Matching } from '@unhingged/logit';
configure({
filters: [
byExcluding(Matching.withProperty('RequestPath', (p: string) => p.startsWith('/health'))),
byExcluding(Matching.fromSource('next.telemetry')),
],
});The two filters#
| Filter | Serilog | Keeps |
|---|---|---|
byExcluding(predicate) | Filter.ByExcluding | events the predicate rejects |
byIncludingOnly(predicate) | Filter.ByIncludingOnly | events the predicate accepts |
Any function of the event is a filter on its own: filters: [(e) => e.level !== 'debug' || e.sourceContext === 'app.db'].
Matching#
Predicates for the usual cases, usable in filters and in conditional() sinks:
| Helper | Matches events… |
|---|---|
Matching.fromSource('app.db') | whose SourceContext is app.db or starts with app.db. |
Matching.withProperty('UserId') | that carry UserId |
Matching.withProperty('StatusCode', (s: number) => s >= 500) | whose StatusCode satisfies the predicate |
Matching.withTemplate('HTTP {RequestMethod} …') | written with exactly that template |
Matching.messageMatches(/timeout/i) | whose rendered message matches — renders the message, so keep it for rare paths |
Sampling#
Keep a fraction of a noisy kind of event:
import { sampled } from '@unhingged/logit';
configure({ filters: [sampled(0.1, Matching.fromSource('http'))] }); // one request log in tenEvents the predicate does not match pass untouched. The random source is injectable for tests: sampled(0.5, pred, () => 0.3).
Rate limiting#
For the error that fires ten thousand times a second:
import { rateLimited } from '@unhingged/logit';
configure({ filters: [rateLimited(100, 1000, Matching.withTemplate('Connection to {Host} refused'))] }); // at most 100 per secondA fixed window: the count resets windowMs after the first matching event of the window. Beyond max, matching events are dropped silently. The clock is injectable.
Level overrides are filters too#
A per-source minimum (Levels) is the cheapest filter there is — it runs before anything is captured. Use it for "the framework at warn, my code at debug"; use filters for anything that needs a property.
Where filtering happens#
call → level check → parse + capture → bound properties → enrichers → FILTERS → sinks (each with its own minimum)A sub-logger has its own filters, so routing — "errors to Seq, everything to the console" — is a sub-logger with a filter, see Sinks.
An expression language for all of this (RequestPath like '/health%'), Serilog.Expressions' role, is on the roadmap.