Filter¶
Kelora can drop data at several points on its way through the pipeline. They differ in what they can see and what they cost:
| Filter on | Sees | Option | Example |
|---|---|---|---|
| raw lines | unparsed text — cheapest | --keep-lines, --ignore-lines |
--ignore-lines 'healthz' |
| position | where a line sits in the file | --section-*, --skip-lines, --head |
--section-from 'stage: test' |
| time | the parsed timestamp | --since, --until |
--since 1h |
| level | the level field | -l, -L |
-l error,warn |
| any field | everything, via a Rhai expression | --filter |
--filter 'e.status >= 500' |
| count, sample | event order, a hash of a value | -n, bucket() |
-n 20 |
| neighbours | events around each match | -A, -B, -C |
-l error -C 2 |
| history | earlier events | state in --exec |
first occurrence per user |
The rule of thumb: filter as early as the information allows. A line dropped
before parsing costs almost nothing; a --filter that runs after an expensive
--exec pays for both.
Raw lines, before parsing¶
--keep-lines REGEX keeps only matching lines; --ignore-lines REGEX drops
matching lines. They run before parsing, so they work even on lines that
wouldn't parse, and they are the fastest way to cut a big file down.
ts='2024-03-02 09:01:12' level='ERROR'
msg='test payments::refund_partial failed: expected 200, got 500'
ts='2024-03-02 09:01:13' level='ERROR'
msg='test payments::refund_full failed: timeout after 30s'
The regex is unanchored: use ^…$ to match a whole line. Both options act on
physical lines, so with multiline
grouping they can remove a line from the middle of an event — use --filter
for whole events.
Regions of a file¶
To process only part of a file — one test run, one deploy, everything after a restart — mark the start and end with regexes:
ts='2024-03-02 09:00:40' level='INFO' msg='==> stage: test'
ts='2024-03-02 09:00:41' level='INFO' msg='running 1532 tests'
ts='2024-03-02 09:01:12' level='ERROR'
msg='test payments::refund_partial failed: expected 200, got 500'
ts='2024-03-02 09:01:13' level='ERROR'
msg='test payments::refund_full failed: timeout after 30s'
ts='2024-03-02 09:01:30' level='INFO' msg='1530 passed, 2 failed'
| Option | Section starts or ends |
|---|---|
--section-from RE |
starts at the matching line (included) |
--section-after RE |
starts after the matching line |
--section-before RE |
ends before the matching line |
--section-through RE |
ends after the matching line (included) |
--max-sections N |
stop after N sections |
Without an end option, a section runs to the end of the input. --skip-lines N
skips the first N lines (a preamble); --head N reads only the first N lines
and stops reading — useful for trying a command on a huge file.
Time ranges¶
timestamp='2024-01-15T10:20:00Z' level='INFO' message='Disk cleanup initiated'
timestamp='2024-01-15T10:25:00Z' level='INFO' message='Cleanup completed'
--since and --until accept absolute times (2024-01-15 10:00), relative
times (1h, -30m, yesterday), and anchors (--until since+15m). They read
the timestamp the parser found and run before every event filter and script, so events
without a timestamp are dropped (Kelora warns when that happens). Details and
time zones: Work with Time.
Log levels¶
level='ERROR' message='Query timeout'
level='ERROR' message='Account locked'
level='CRITICAL' message='Disk space critical'
level='ERROR' message='Service unavailable'
-l keeps the listed levels, -L drops them (-L debug,trace). Matching is
case-insensitive. They read the event's level field, whichever of level,
lvl, severity, loglevel, … it has. -l takes names, not a threshold: see
the FAQ for "WARN and above".
Expressions¶
--filter keeps an event when a Rhai expression is true. e
is the current event:
kelora examples/web_access_large.log.gz \
--filter 'e.status >= 500 && e.method == "POST"' -k ts,status,method,path -n 3
ts='04/Oct/2025:10:00:46 +0200' status=500 method='POST'
path='/clicks-and-mortar/synergistic/benchmark'
ts='04/Oct/2025:10:02:45 +0200' status=504 method='POST'
path='/recontextualize/revolutionize/proactive/recontextualize'
ts='04/Oct/2025:10:05:03 +0200' status=500 method='POST'
path='/functionalities/magnetic/platforms/whiteboard'
Expressions you will use most:
| Test | Expression |
|---|---|
| equal, compare | e.level == "ERROR", e.duration_ms > 1000 |
| one of several values | e.status in [500, 502, 503] |
| substring | e.msg.contains("timeout") |
| regex | e.msg.matches("time ?out") |
| glob, case-insensitive glob | e.path.like("/api/*"), e.host.ilike("WEB-*") |
| prefix, suffix | e.path.starts_with("/admin"), e.file.ends_with(".php") |
| IP ranges | e.ip.is_in_cidr("10.0.0.0/8"), e.ip.is_private_ip() |
| field present / absent | e.has("user"), !e.has("user") |
| nested field | e.get_path("user.role", "") == "admin" |
| which file | meta.filename.contains("api") |
| negate | !(e.msg.contains("healthz")) |
A missing field compares as false, so --filter 'e.duration_ms > 1000'
skips events without duration_ms. Calling a method on a missing field is an
error; Kelora reports it and treats the event as not matching. See
missing fields.
--filter takes a single expression; for logic that needs let statements,
use --exec and drop events there with e = ().
Repeat --filter for AND. Filters and --exec stages run in the order you
write them, so a filter can use a field computed by an earlier --exec:
kelora examples/simple_json.jsonl \
-e 'e.duration_s = e.get("duration_ms", 0) / 1000.0' \
--filter 'e.duration_s > 60' -k service,duration_s
Inside an --exec script, e = () drops the current event — handy when the
decision is part of a longer script.
First N and samples¶
-n N (--take) stops after N events have been output. To sample, hash a
stable value, so the same requests or users are kept on every run and in every
file:
--filter 'sample_every(10)' keeps every tenth event; --filter 'sample_prob(0.1)'
a random 10 %.
Context around matches¶
Like grep -A/-B/-C: show events before and after each match. In the default
format, a marker shows each event's role: ◉ (or *) for a match, / before,
\ after, | between two matches.
/ ts='2024-03-02 09:00:41' level='INFO' msg='running 1532 tests'
◉ ts='2024-03-02 09:01:12' level='ERROR'
msg='test payments::refund_partial failed: expected 200, got 500'
◉ ts='2024-03-02 09:01:13' level='ERROR'
msg='test payments::refund_full failed: timeout after 30s'
\ ts='2024-03-02 09:01:30' level='INFO' msg='1530 passed, 2 failed'
A match is an event that passes the filters (--filter, -l, -L) placed
before any --exec. With --parallel, context is ignored (with a warning).
Duplicates and first occurrences¶
Filters that depend on earlier events — the first error per service, drop
repeated messages — need memory across events. That is what state is for:
kelora examples/simple_json.jsonl \
--filter '!state.contains(e.service)' \
--exec 'state[e.service] = true' -k service,message
service='api' message='Application started'
service='database' message='Connection pool initialized'
service='cache' message='Cache hit'
service='auth' message='Failed login attempt'
service='scheduler' message='Cron job started'
service='disk' message='Disk space critical'
service='monitoring' message='Alert sent'
service='admin' message='Disk cleanup initiated'
service='health' message='Health check passed'
See Cross-Event Logic.
Order matters¶
Kelora runs the time range first, then your --filter, -l, -L, and
--exec stages in the order you typed them, then -n. -k/-K choose which
fields are printed, after all filtering; they hide an event only if none of its
fields are left. The complete order is in
How It Works.