Skip to content

CLI Options

Every command-line option, grouped as in kelora --help. This page is generated from the help text of the Kelora version it documents, so the two always agree. In the terminal, kelora --help KEYWORD shows only the options matching a keyword, and kelora -h prints a one-screen summary.

Topic references in the terminal: --help-formats, --help-time, --help-multiline, --help-regex, --help-rhai, --help-functions, --help-examples.

Arguments

[FILES]...
Input files (stdin if not specified, or use "-" to explicitly specify stdin)

General

-h, --help
Print help (see a summary with -h)
-V, --version
Print version

Input Options

--no-input
Run without reading input (useful for scripts that only use --begin/--end stages)
-f, --input-format <FORMAT>

Input format. Available formats: auto (default), auto-per-file, json, line, raw, logfmt, syslog, cef, csv, tsv, csvnh, tsvnh, combined, cri, apache-error, glog, haproxy, iso8601-level, log4j, nginx-error, postgres, python-logging, redis, s3, cols:<spec>, regex:<pattern>. Fields of each: --help-formats.

With auto, the format is detected from the first non-empty line (stdin) or a sample of the file (files: the head plus, for larger plain files, a few probe windows deeper in); a file that mixes formats is parsed with a cascade of the detected formats automatically. For mixed stdin streams pass a cascade (below) explicitly.

Use cols:<spec> for column parsing, regex:<pattern> for regex parsing with named groups, and csv/tsv with optional type annotations.

Cascade mode: pass a comma-separated list (e.g. json,logfmt,line) to try each parser in order; the first success wins, so put catch-all fallbacks like line or raw last. Adds an _format field to each event, unless the record already has one of its own (that value is kept).

Repeat -f to build a cascade that includes spec-based parsers: -f json -f 'cols:ts(2) level *msg'. Each -f is tried in order; put catch-alls (line, raw, cols:) last (regex declines non-matching lines, so it can sit earlier).

Examples:

  • -f json
  • -f json,line
  • -f json -f 'cols:ts level *msg'
  • -f 'regex:(?P<code:int>\\d+) (?P<msg>.*)'
  • -f 'csv status:int bytes:int'

Default: auto

-j
Shortcut for -f json
--file-order <ORDER>

File processing order

Default: cli ยท Values: cli name mtime

--merge-sorted
Merge already-sorted input files by timestamp; aborts on missing timestamps, parse failures, or per-file disorder
--skip-lines <N>
Skip the first N input lines
Read only the first N input lines (stops I/O early, complementing --take which limits output events)
--section-from <REGEX>
Start emitting sections from the line matching this regex (inclusive start). All --section-* regexes are unanchored; use ^...$ to match a whole line
--section-after <REGEX>
Start emitting sections after the line matching this regex (exclusive start)
--section-before <REGEX>
Stop before the line matching this regex (exclusive end)
--section-through <REGEX>
Stop after emitting the line matching this regex (inclusive end)
--max-sections <N>

Maximum number of sections to process (default: -1 for unlimited)

Default: -1

--keep-lines <REGEX>
Keep only input lines matching this regex pattern (applied before ignore-lines)
--ignore-lines <REGEX>
Ignore input lines matching this regex pattern
--ts-field <FIELD>
Custom timestamp field name for parsing
--ts-format <FORMAT>
Custom timestamp format for parsing (uses chrono format strings)
--input-tz <TZ>

Assume timezone for input timestamps without timezone info (default: the TZ environment variable if set, else UTC). Use local for system local time.

Examples:

  • Europe/Berlin
  • local
  • UTC
--input-year <YEAR>

Supply the year for year-less timestamps (syslog, glog, ...) instead of guessing it from the wall clock. Use auto for the ยฑ1yr heuristic (default).

Examples:

  • 2005
  • auto
-M, --multiline <STRATEGY>
Multi-line event detection strategy. Supply values like timestamp, timestamp:format=%Y-%m-%d %H-%M-%S, indent, blank, regex:match=^START, regex:match=^START:end=^END$, or a language stack-trace preset (java, python, go) for logs without timestamped headers. See kelora --help-multiline for details
--multiline-join <JOIN>

Join multiline lines with: space, newline, or empty. Default: space (newline for --multiline all)

Values: space newline empty

--multiline-timeout <DURATION>
Flush a buffered multiline event after this much input inactivity (e.g. 400ms, 2s; 0 = never). Default: off when reading regular files, 400ms when reading a stream (stdin, FIFO)
--multiline-max-lines <N>
Split a multiline event after this many buffered lines (safety cap; 0 = unlimited). Default: 10000. Ignored by --multiline all
--extract-prefix <FIELD>
Extract text before separator to specified field (runs before parsing)
--prefix-sep <SEP>

Separator string for prefix extraction (default: pipe |)

Default: |

--cols-sep <SEP>
Column separator for cols:<spec> format (default: whitespace)
--max-line-bytes <SIZE>

Cap the number of bytes a single input line may consume in memory (default 64MiB).

This is a safety circuit breaker against runaway memory: a newline-free stream โ€” including a tiny gzip/zstd payload that decompresses into one enormous line โ€” would otherwise grow without bound and exhaust RAM. No real log line approaches the default, so it normally never triggers.

When a line exceeds the cap it is truncated to the cap and a warning (๐Ÿ”ธ) reports how many lines were clipped; the run still succeeds (exit 0). With --strict an over-limit line is a hard error (exit 1) instead. Accepts a byte count or an IEC/SI suffix (64MiB, 1GiB, 1048576); 0/off/unlimited disables the cap.

Reading is streamed, so a large multi-line compressed file is unaffected โ€” only a single over-long line trips this.

Processing Options

--begin <EXPR>

Pre-run a Rhai script before any other stage runs.

Typical use: seed the global conf map with lookup tables or shared context.

Helpers available only here:

read_lines(path) -> Array<String>  # UTF-8, one entry per line
read_file(path)  -> String         # UTF-8, entire file contents

Data stored in conf becomes read-only afterwards. See --help-rhai for stage order.

--filter <EXPR>

Boolean filter expression; events where it evaluates to true are kept.

Can be combined with --include (-I) to call helper functions defined in an include file. Include files used with --filter must contain only function definitions โ€” top-level statements are rejected with an error.

Example:

kelora -I helpers.rhai --filter 'is_error(e.level)' app.log

See --help-rhai for expression syntax.

-e, --exec <EXPR>
Transform/process exec scripts evaluated on each event. See --help-rhai for stage semantics
-E, --exec-file <FILE>
Execute script from file (contents run in the exec stage)
--assert <EXPR>
Assertion expressions that must evaluate to true. Violations are reported to stderr; processing continues unless --strict is enabled. See --help-rhai for expression syntax
-I, --include <FILE>

Include a Rhai file of helper functions, loaded into the adjacent script stage.

Command-line position selects which stage the file applies to:

--include before a --filter/--exec    โ†’ that filter/exec stage
--include before the first stage       โ†’ the --begin stage (if present)
--include after the last stage         โ†’ the --end stage (if present)

Each stage has its own scope: an include's functions are only visible to the stage it is attached to. Repeat --include to share helpers across several stages. An include that attaches to begin/end has no effect unless that --begin/--end stage exists.

When used with --filter, the include file must contain only function definitions. Top-level statements (side effects) are rejected with an error.

Example:

kelora -I helpers.rhai --filter 'is_error(e.level)' app.log
--end <EXPR>
Run once after processing completes (post-processing stage). Ideal for summarising metrics or emitting reports. The global metrics map from track_*() calls is accessible here
--allow-fs-writes
Allow Rhai scripts to create directories and write files on disk (required for file helpers like append_file or mkdir)
--window <N>
Keep the current event and the N before it in the window array for scripts (window[0] is the current event)
--span <N|DURATION|FIELD>

Aggregate events into consecutive spans.

--span <N>         Close after every N events that pass filters.
--span <DURATION>  Close on aligned time windows (e.g. 30s, 5m, 1h, 1d).
                   Units: ms, s, m, h, d, w. Windows are fixed-length
                   and aligned to the Unix epoch in UTC, so weeks run
                   Thursday to Wednesday.
--span <FIELD>     Close when the specified field value changes.

Use with --span-close to run a Rhai snippet when each span finishes.

--span-idle <DURATION>
Close span after this duration of inactivity (e.g. --span-idle 5m). Requires timestamps and cannot be combined with --span.
--span-close <EXPR>

Run a Rhai snippet when each span closes. For a plain per-window rollup, use --span-summary instead โ€” no script, and its rows survive -m/--freq (a hook's print does not).

Within the hook, read span.start, span.end, span.first_ts, span.last_ts, span.id, span.label, span.events, span.size, and span.metrics for span context. span.start/span.end are the window bounds (() for count and field spans); span.first_ts/span.last_ts are the timestamps of the first and last events in the span, in every mode. span.metric("name") returns one metric's per-window value or 0 when the window produced none (() for an omitted non-additive metric), and takes a dotted path (span.metric("level.ERROR")); prefer it over indexing span.metrics, which yields () for an omitted zero delta. span.label is span.start as RFC3339 when the mode has one, else span.id, so a hook need not branch on the span mode.

span.metrics carries per-window values for additive aggregators (count, sum, avg, unique, bucket) and the window's own min and max; non-additive ones (percentiles, cardinality, top, bottom) are omitted with a warning, so use span.events for those. Events are only retained for span.events when this hook is present.

--span-summary[=<FORMAT>]

One rollup row per closed span (implies -q/--quiet). Requires --span or --span-idle.

Each row carries the span label, the event count, and every per-window metric from additive track_* calls and track_min/track_max (including those synthesized by --freq/--describe).

Formats: text (key=value lines), tsv (label<TAB>metric<TAB>key<TAB>value records), json (one object per line). Bare --span-summary auto-selects: text on a terminal, tsv when piped or redirected (like -m).

Rows are data on stdout, so --no-script-output and -m do not suppress them. A window that received no input produces no row, so a time series has gaps where nothing was logged; a window whose events were all removed by --filter/--levels still produces a row, with events=0. Windows outside --since/--until never appear.

Examples:

--span 1m --span-summary               Events per minute
-l error --span 5m --span-summary      Errors per 5 minutes
--span 1m --freq level --span-summary  Per-minute level breakdown
--span-idle 5m --span-summary          Session sizes
--span 1m --span-summary=tsv > series.tsv  Time series for a database or plot

Note the =: --span-summary=tsv (a space is read as a filename).

Values:

  • text: One key=value line per span; nested metrics flatten with a dot
  • tsv: Long/tidy label<TAB>metric<TAB>key<TAB>value records, one per value
  • json: One JSON object per span, preserving nested metrics
--normalize-ts
Normalize the primary timestamp field to RFC3339 (ISO 8601 compatible). Modifies event data - affects all output formats

Error Handling

--strict
Exit on first error (fail-fast behavior). Use --no-strict to force resilient mode, overriding a config default
--strict-utf8

Abort on the first non-UTF-8 byte instead of the default lossy decoding.

By default, kelora tolerates non-UTF-8 input the way grep does: invalid byte sequences are replaced with U+FFFD (๏ฟฝ) and a diagnostic reports how many lines were affected, so a single bad byte no longer truncates the rest of the stream. Pass --strict-utf8 to restore hard failure (exit 1) on invalid UTF-8.

-v, --verbose...
Show detailed error information (use multiple times for more verbosity: -v, -vv, -vvv)

Filtering Options

-l, --levels <LEVELS>

Include only events with these log levels (comma-separated, case-insensitive).

Use comma-separated values for OR logic: --levels ERROR,WARN Multiple flags create sequential AND filters (advanced).

On json and logfmt input, lines whose text contains none of the levels are skipped before parsing, so their parse errors go uncounted: the parse-error summary can report fewer errors than a run without -l. A run in which no line parses still fails with exit 1. Run without -l, or with -s/--stats, to count every parse error.

-L, --exclude-levels <EXCLUDE_LEVELS>
Exclude events with these log levels (comma-separated, case-insensitive).
-k, --keys <KEYS>

Output only these fields, in the order given (comma-separated list).

Acts on whole top-level fields. Nested values that --discover prints as dotted or bracketed paths (e.g. api.queries, tags[]) are NOT selectable here โ€” flatten them first, e.g. --exec 'e.val = e.get_path("api.queries")' then -k val. (A top-level field whose literal name contains a dot is matched as-is.)

-K, --exclude-keys <EXCLUDE_KEYS>

Exclude these fields from output (comma-separated list).

Acts on whole top-level fields; nested values (the dotted/bracketed paths shown by --discover) cannot be dropped here โ€” remove them in an --exec stage instead.

--since <TIME>

Keep only events at or after this time.

Accepts journalctl-style timestamps (e.g., 2024-01-15T12:00:00Z, 2024-01-15 12:00, 1h, -30m, yesterday). Can also use until+DURATION, until-DURATION, now+DURATION, or now-DURATION anchors.

Reads the timestamp the parser produced, before any script stage: use --ts-field/--ts-format to change what that is, and a --filter for a timestamp you build in a script. See --help-time.

--until <TIME>

Keep only events at or before this time.

Accepts journalctl-style timestamps (e.g., 2024-01-15T12:00:00Z, 2024-01-15 12:00, 1h, +30m, tomorrow). Can also use since+DURATION, since-DURATION, now+DURATION, or now-DURATION anchors.

Reads the timestamp the parser produced, before any script stage: use --ts-field/--ts-format to change what that is, and a --filter for a timestamp you build in a script. See --help-time.

-n, --take <N>
Limit output to the first N events
-B, --before-context <N>

Show N events before each match (requires filtering).

See -C for how context lines are marked and which filters define a match.

-A, --after-context <N>

Show N events after each match (requires filtering).

See -C for how context lines are marked and which filters define a match.

-C, --context <N>

Show N events before and after each match, like grep -C (requires filtering: --filter, --levels/-l, --exclude-levels/-L, --since/--until).

In the default output format each event carries a marker naming its role. Every event appears exactly once, with the marker that describes it:

โ—‰ / *   the match itself (โ—‰ on a color terminal, * otherwise)
/       before-context: run-up to a later match
\       after-context: trails an earlier match
|       both at once, between two matches close enough to overlap

Markers appear only in the default format; -F json/csv/logfmt emit the same events unmarked. They are part of the event line on stdout, so --no-warnings/--no-hints/--no-diagnostics leave them alone; -q and --silent drop the events entirely.

A match is an event that passes every filter in the pipeline's leading run of filters, so --filter A --filter B -C 2 and --filter A -l error -C 2 both mean "context around events passing both". The context lines that run selects are neighbours, not matches, so a later --filter (one placed behind an --exec or --assert) does not re-judge them; kelora warns when a filter sits there, since it can drop matches and leave their context behind.

--since/--until stay outside that run: they define which events exist at all, so context is drawn from inside the time window rather than across its edges.

Requires sequential mode; --parallel is ignored with a warning.

Output Options

-F, --output-format <FORMAT>

Output format.

Formats:

default   Colored key-value output
json      JSON Lines (one object per line)
logfmt    Key=value pairs on one line
inspect   Debug view with type information
levelmap  Compact level timeline
keymap    First-character map for one selected field
tailmap   Percentile map for one numeric field
csv       Comma-separated with header row
tsv       Tab-separated with header row
csvnh     CSV without header row
tsvnh     TSV without header row

See --help-formats for requirements, extracted fields, and examples.

Default: default ยท Values: default json logfmt inspect levelmap keymap tailmap csv tsv csvnh tsvnh

-J
Shortcut for -F json
-c, --core
Output only core fields
-o, --output-file <FILE>
Output file for formatted events
-q, --quiet
Suppress events (formatter output)
--warnings
Show warnings (๐Ÿ”ธ) โ€” problems that did not stop the run. Overrides a KELORA_NO_WARNINGS / config default and re-enables warnings in data-only modes like --metrics/--drain/--discover
--no-warnings
Suppress warnings (๐Ÿ”ธ). Errors still print; use --silent to hide those too
--hints
Show hints (๐Ÿ’ก) โ€” advisory suggestions (zero-result/typo/format tips). Overrides a KELORA_NO_HINTS / config default and re-enables hints in data-only modes like --metrics/--drain/--discover
--no-hints
Suppress hints (๐Ÿ’ก, zero-result hints, format/typo tips)
--diagnostics
Show both warnings and hints (shortcut for --warnings --hints), even in data-only modes. Error summaries always show unless --silent
--no-diagnostics
Suppress both warnings and hints (shortcut for --no-warnings --no-hints), plus per-line verbose errors. Error and parse summaries still print; use --silent to hide those too
--silent
Silence pipeline stdout/stderr emitters (events/diagnostics/stats/terminal metrics); script output still allowed. Metrics files still write. Use --no-silent to override a config default
--script-output
Enable Rhai print/eprint output
--legend
Append a legend to map outputs (levelmap/keymap/tailmap) even when piped. By default the legend shows only when stdout is a terminal
--no-legend
Suppress the legend on map outputs (levelmap/keymap/tailmap)

Default Format Options

-b, --brief
Output only field values (omit keys)
--expand-nested
Expand nested structures (maps/arrays) with indentation
--wrap
Always word-wrap wide events onto indented continuation lines, even when piped or redirected. By default wrapping is enabled only when stdout is a terminal, so piped output stays one line per event
--no-wrap
Never word-wrap; keep each event on a single line
-z, --show-ts-local
Display timestamps as local RFC3339 (ISO 8601 compatible). Display-only - only affects default formatter output
-Z, --show-ts-utc
Display timestamps as UTC RFC3339 (ISO 8601 compatible). Display-only - only affects default formatter output

Display Options

--force-color
Force colored output
--no-color
Disable colored output
--mark-gaps <DURATION>
Insert a centered marker when the time delta between events exceeds the given duration. Example: --mark-gaps 30s prints a divider when consecutive events are separated by >=30s.
--force-emoji
Force emoji prefixes (override auto-detection)
--no-emoji
Disable emoji prefixes

Performance Options

-P, --parallel
Enable parallel processing (default: sequential processing). Use --no-parallel to force sequential, overriding a config default
--threads <N>

Number of worker threads (0 = auto-detect, one per available core)

Default: 0

--batch-size <N>
Batch size (events per batch) for parallel processing
--batch-timeout <MS>

Flush partially full parallel batches after this idle period (milliseconds). Lower values reduce latency; higher values improve throughput.

Default: 200

--unordered
Disable ordered output

Metrics and Stats

-s, --stats[=<FORMAT>]

Show stats only (implies -q/--quiet).

Formats: table, json

Examples:

-s              Default table format
--stats=json    JSON output

Use --no-stats to override a config default. Note the =: --stats=json (a space, as in -s json, is read as a filename).

Values: table json

--with-stats
Show stats alongside events (rare case)
-m, --metrics[=<FORMAT>]

Show metrics only (implies -q/--quiet).

Formats: short (first 5), full, tsv, json. Bare -m auto-selects: the human-readable table on a terminal, tsv when piped or redirected (like ls).

tsv emits one tab-separated metric<TAB>key<TAB>value record per line, sorted by count descending, so | head is top-N and | tail is bottom-N.

Examples:

-m               Auto (table on a TTY, tsv when piped)
--metrics=full   Force the table even through a pipe
--metrics=tsv    Force the record stream even to a TTY
--metrics=short  Abbreviated (first 5 items)
--metrics=json   JSON output

Use --no-metrics to override a config default. Note the =: --metrics=json (a space, as in -m json, is read as a filename).

Values:

  • short
  • full
  • json
  • tsv: Tab-separated record stream (one metric<TAB>key<TAB>value row per line, sorted by count/score descending) for piping to head/tail/sort/awk
--with-metrics
Show metrics alongside events (rare case)
--metrics-file <FILE>
Persist the metrics map (populated by track_*()) to disk as JSON.
--freq <FIELD>

Frequency table: count occurrences per distinct value of FIELD.

Shorthand for track_freq("FIELD", e.FIELD). Runs after all filters/transforms and implies -m. Repeatable or comma-separated (--freq level,service). Nested fields use dotted paths (e.g. user.id). Results are sorted by count descending, so piping the tsv output to head gives the top-N and tail gives the bottom-N (no --top/--bottom flags needed). Control output with --metrics=short|full|tsv|json or --metrics-file.

Takes a field name, not an expression. To tally a derived value, call track_freq directly in a script stage โ€” it accepts any expression, names the metric itself, and needs no throwaway field:

-m --exec 'track_freq("hour", meta.parsed_ts.round_to("1h"))'   # per hour
-m --exec 'track_freq("class", e.status / 100)'                  # per 1xx/2xx/...

By itself --freq tallies the whole run: adding --span does NOT window it. For per-window tallies add --span-summary, which puts each window's deltas in a row:

--span 1m --freq level --span-summary

Examples:

--freq level
--filter 'e.status>=500' --freq url
--freq url | head        # top URLs (tsv auto-selected when piped)
--freq url | tail        # rarest URLs
--describe <FIELD>

Summarize a numeric FIELD: count, min, max, avg, p50/p95/p99.

Shorthand for track_stats("FIELD", e.FIELD). Runs after all filters/transforms and implies -m. Repeatable or comma-separated (--describe a,b). Non-numeric/missing values are skipped. Control output with --metrics=short|full|tsv|json or --metrics-file.

Takes a field name, not an expression. For a derived value, call track_stats directly in a script stage:

-m --exec 'track_stats("seconds", e.duration_ms / 1000.0)'

Example:

--describe duration_ms
--card <FIELD>

Estimate the number of distinct values of FIELD (HyperLogLog).

Shorthand for track_cardinality("FIELD", e.FIELD). Runs after all filters/transforms and implies -m. Repeatable or comma-separated (--card user_id,trace_id). Missing values are skipped. The count is approximate (~1% error) but uses constant memory, so it scales to high-cardinality fields where track_freq/track_unique would not. Control output with --metrics=short|full|tsv|json or --metrics-file.

Takes a field name, not an expression. For a derived value, call track_cardinality directly in a script stage:

-m --exec 'track_cardinality("prefix", e.client_ip.split(".")[0])'

Examples:

--card user.id
--filter 'e.status>=500' --card client_ip

Template Discovery

--drain[=<FORMAT>]

Summarize log templates using Drain (summary-only; requires --keys with exactly one field; sequential mode only).

Formats:

table (default)  Clean output: count + template
full             Detailed: adds line ranges + sample + template ID
id               Stable output: template_id + template (sorted by ID)
json             JSON with all metadata

Examples:

--drain          Clean table (count + template)
--drain=full     With line numbers, samples, and IDs
--drain=id       Stable ID list for diffs
--drain=json     JSON output for scripting

Values: table full id json

--drain-diff[=<FORMAT>]

Compare template frequencies between a baseline and a target log (summary-only; requires --keys with exactly one field; sequential mode only).

Three ways to define baseline and target:

kelora --drain-diff old.log new.log -k msg      First input is baseline, second is target
kelora --drain-diff --cut-at 2026-07-24T14:00Z incident.log -k msg   One input split at a timestamp
kelora --drain-diff --cut-before 'e.msg.contains("deploy")' incident.log -k msg   One input split at the first match

Diffs *which message templates occur and how often*, not what the messages say. Output reads like a unified diff:

--- / +++       name the baseline and target, with each side's event count
                and observed span (in the form --cut-at accepts back)
+               a template only the target has, with its event count
-               a template only the baseline had, with its event count
*               a template both have, at a materially different rate,
                shown as a plain multiple ("14x more")

A * row is reported when the change is bigger than sampling noise for the number of events on each side, and big enough to act on (>= 0.5pp of share, or a >= 1.5x change in rate). Templates that change less are this diff's context lines: tallied in the footer as unchanged, not printed. All comparisons use per-side shares, so sides of very different sizes compare fairly.

Rewording is invisible to it: "timeout after 5s" becoming "timed out after 5s" reads as one - plus one +, never as a modified template. Composes with the pipeline: --filter/--exec run before diffing.

A vacuous comparison is refused rather than reported (exit 1):

- No event carries the field named by -k: the error names the nearest field it
  did see, instead of printing an all-empty "nothing changed" report.
- One side got every event and the other none (usually a splitter that missed):
  every template would be + or - by construction, so the error names the
  resolved cut and the span the input covers, or says the predicate never
  matched, instead.

A field present on only some events warns with the excluded and compared counts.

Formats:

table   Diff-style rows + footer. The default on a terminal.
full    The same rows, each followed by indented detail: template id,
        both counts and shares, and a sample of a real line it matched.
tsv     One 'change<TAB>template_id<TAB>counts<TAB>percents<TAB>template'
        record per changed template; no header, no report around it.
        The default when stdout is piped or redirected (like -m), so
        --drain-diff=table forces the report through a pipe.
json    One JSON object (new/gone/freq_changed, per-side totals, the
        unchanged tally, spans and exclusion counts; each freq_changed
        entry carries delta_pp and a signed z_score for your own
        filtering)

Examples:

--drain-diff old.log new.log -k msg
--drain-diff=json before.log after.log -k msg
--drain-diff --cut-at 2026-07-24T14:00Z incident.log -k msg
--drain-diff --cut-before 'e.msg.contains("deploy started")' incident.log -k msg

Values:

  • table
  • full: The same rows, each followed by indented detail: template id, the counts and shares the compact row leaves out, and a sample line
  • json
  • tsv: One tab-separated record per changed template, no header and no surrounding report, for piping to awk/sort/duckdb
--cut-at <TIME>

Timestamp splitting one --drain-diff input into baseline (before the cut) and target (at/after).

Accepts the same timestamp formats as --since/--until (e.g., 2026-07-24T14:00:00Z, 2026-07-24 14:00, 1h, yesterday). See --help-time. Events without a parseable timestamp are excluded from the comparison and counted in a warning.

The cut resolves against the clock, not the log: 1h means an hour before now, and a bare 14:00 means today at 14:00. For a historical log, give an absolute timestamp โ€” a cut that lands outside the data leaves one side empty, which is refused (exit 1) with the span the input actually covers, so a missed first attempt tells you what to use. When you know what the change looks like but not when it happened, --cut-before / --cut-after split on a predicate instead and need no clock.

Every report states where the split landed (baseline spans .. / target spans ..), in the same form --cut-at accepts back.

Example:

--drain-diff --cut-at 2026-07-24T14:00Z incident.log -k msg
--cut-before <EXPR>

Boolean Rhai expression whose first match splits one --drain-diff input, with the cut placed immediately BEFORE the match: the matching event is the first target event.

Use this when the matching line is the start of the new regime โ€” a deploy marker, the first error of an incident. Use --cut-after instead when the match is the end of the old one.

Needs no timestamps and no clock: you say what the change looks like, not when it happened. The boundary latches on the first match, so a predicate that keeps matching still splits the log exactly once, and no events are buffered.

Same expression syntax as --filter (see --help-rhai), and -I/--include helpers are available. A predicate that never matches leaves the target side empty, and one matching the first event leaves the baseline empty; both are refused (exit 1) rather than reported as change. One matching the last event leaves a one-event target, which prints with a warning (๐Ÿ”ธ) rather than being refused.

Examples:

--drain-diff --cut-before 'e.msg.contains("deploy started")' incident.log -k msg
--drain-diff --cut-before 'e.status >= 500' access.log -k msg
--drain-diff --cut-before 'e.version != "1.4.2"' rollout.log -k msg
--cut-after <EXPR>

Boolean Rhai expression whose first match splits one --drain-diff input, with the cut placed immediately AFTER the match: the matching event is the last baseline event.

Use this when the matching line closes out the old regime โ€” deploy finished, migration complete, the last healthy heartbeat. Use --cut-before when the match opens the new one.

Otherwise identical to --cut-before: same expression syntax as --filter (see --help-rhai), -I/--include helpers available, boundary latches on the first match, nothing buffered. A predicate that never matches, or one matching the last event, leaves the target side empty and is refused (exit 1) rather than reported as change. One matching the first event leaves a one-event baseline, which is well-formed enough to report: it prints, with a warning (๐Ÿ”ธ) that the NEW section reflects that shortfall rather than a change.

Examples:

--drain-diff --cut-after 'e.msg.contains("deploy finished")' incident.log -k msg
--drain-diff --cut-after 'e.phase == "warmup"' rollout.log -k msg

Field Discovery

-d, --discover[=<FORMAT>]

Profile fields: names, inferred types, cardinality estimates, and sample values. Nested maps and arrays are flattened to 3 levels (e.g. user.name, user.roles[]). Implies -q/--quiet (events suppressed). Sequential mode only.

Formats: table (default), json

Examples:

-d, --discover      Table summary
-d=json, --discover=json     Machine-readable JSON

Values: table json

-D, --discover-final[=<FORMAT>]

Profile final emitted fields after scripts and filters. Implies -q/--quiet (events suppressed). Sequential mode only.

Formats: table (default), json

Examples:

-D, --discover-final          Table summary of final fields
-D=json, --discover-final=json     Machine-readable JSON of final fields

Values: table json

--discover-depth <N>

Maximum depth for flattening nested maps/arrays into dotted keys (default: 3). Depth counts descents from the event root: a.b.c is depth 3. Use a higher value to inspect deeply nested JSON; use 1 to see only top-level fields; use 0 for unlimited depth.

Examples:

--discover --discover-depth=5    Descend up to 5 levels deep
--discover --discover-depth=1    Top-level fields only
--discover --discover-depth=0    Unlimited depth

Configuration Options

--config-file <FILE>
Specify custom configuration file path
--ignore-config
Ignore configuration file
-a, --alias <NAME>
Use alias from configuration file
--save-alias <NAME>
Save current command as alias to configuration file
--show-config
Show configuration file and exit
--edit-config
Edit configuration file in default editor and exit

Help Options

--help-rhai
Show Rhai scripting guide and exit
--help-functions [<KEYWORD>]
Show available Rhai functions and exit (optional KEYWORD filters by name/description; smartcase: lowercase matches any case, uppercase is exact)
--help-examples
Show practical Rhai examples and exit
--help-time
Show time format help and exit
--help-multiline
Show multiline strategy help and exit
--help-regex
Show regex format help and exit
--help-formats
Show format reference and exit
--completions <COMPLETIONS>

Generate shell completion script and exit

Values: bash zsh fish powershell elvish

Exit Codes

0    Success โ€” the run did its job. By default Kelora is resilient: malformed
     lines in an otherwise-valid stream, and best-effort --exec transform
     errors, are reported on stderr and counted but do NOT change the exit code
     (use --strict to make the first such error fatal instead).
1    Error. Any of:
       - a named input file could not be opened
       - an --assert condition failed
       - a gate stage saw input but never once succeeded (parsing failed on every
         line, or a --filter errored on every event)
       - a forbidden operation (e.g. mutating `conf` outside --begin)
       - with --strict, ANY single parse/filter/exec error (also aborts early)
2    Invalid command-line usage (unknown flag, bad value, conflicting options, or a
       malformed config file).
130  Interrupted (SIGINT / Ctrl-C).
134  Internal panic (SIGABRT) โ€” a bug; please report it.
141  Broken pipe (SIGPIPE).
143  Terminated (SIGTERM).