Summarize¶
How many, how often, how slow, what kinds? Kelora answers these in one pass
without printing the events. The examples use api_latency_incident.jsonl:
800 API requests across three endpoints, some of them slow.
Count, describe, estimate: one field¶
Three options take a field name and need no script:
endpoint (3 values):
value count
/api/users 283
/api/posts 261
/api/search 256
status (2 values):
value count
200 782
503 18
kelora examples/api_latency_incident.jsonl --filter 'e.endpoint == "/api/posts"' --describe response_time_ms
response_time_ms_avg = 146.614
response_time_ms_count = 261
response_time_ms_max = 1000
response_time_ms_min = 38.804
response_time_ms_p50 = 99.9901
response_time_ms_p95 = 392.112
response_time_ms_p99 = 880.159
response_time_ms_sum = 38266.3
| Option | Gives you |
|---|---|
--freq FIELD |
events per value, most frequent first |
--describe FIELD |
count, min, max, average, sum, p50/p95/p99 of a number |
--card FIELD |
an estimate of the number of distinct values (about 1 % error, constant memory) |
They count only events that pass your filters, accept dotted paths for nested
fields (--freq user.id), and take several fields, repeated or as a comma list
(--card user_id,trace_id). Look at the average and p99
above: the average looks fine, the tail does not.
Output: table, TSV, JSON¶
The format depends on where the output goes. Shown on screen, you get the
tables above. Piped into another command (| head) or redirected to a file
(> counts.tsv), the same command writes
one tab-separated row per value — metric name, value, count — sorted by count,
so head gives the top N and tail the rarest (statistics like --describe
print as name_stat, empty column, value):
| Option | Output |
|---|---|
--metrics=full / --metrics=tsv |
force table / TSV regardless of where output goes |
--metrics=json |
one JSON object on stdout |
--metrics-file FILE |
also write JSON to a file |
--metrics=short |
only the first 5 values of each metric |
Count anything: track_*() in a script¶
--freq takes a field name. To count something you compute — a status class,
an hour, the first path segment — call the tracking function in --exec and
add -m (--metrics) to print the results instead of events:
kelora examples/api_latency_incident.jsonl -m \
-e 'track_freq("status_class", e.status / 100 * 100)' \
-e 'if e.response_time_ms > 500 { track_top("slow_endpoint", e.endpoint, 3) }'
slow_endpoint (3 items):
item count
/api/search 7
/api/posts 6
/api/users 4
status_class (2 values):
value count
200 782
500 18
Each call names its metric, so one run can track many things.
| Function | Tracks |
|---|---|
track_freq(name, value) |
count per distinct value (what --freq uses) |
track_inc(name) |
a counter |
track_sum(name, n), track_avg, track_min, track_max |
running numbers |
track_stats(name, n) |
min, max, avg, sum, count, p50/p95/p99 (what --describe uses) |
track_percentiles(name, n, [0.5, 0.999]) |
chosen percentiles |
track_top(name, value, k) / track_bottom |
the k most / least frequent values (bounded memory) |
track_top_by(name, value, score, k) / track_bottom_by |
the k values with the highest / lowest score — slowest endpoints, biggest responses |
track_unique(name, value) |
the distinct values themselves |
track_cardinality(name, value) |
an estimate of the distinct count (what --card uses) |
() values are skipped, so track_freq("user", e.get("user")) ignores
events without a user. Percentiles and cardinality are approximate (t-digest,
HyperLogLog) and use constant memory, so they work on files of any size.
Where you put the tracking stage matters: a track_* call before a
--filter counts every event, one after it counts only events that passed.
To format the results yourself, read the metrics map in an --end script —
see Cross-Event Logic.
Per minute, per hour, per session¶
--freq and track_* total the whole run. For one row per time window, add
--span and --span-summary:
2025-01-20T14:00:00Z events=400 level.INFO=379 level.WARN=21
2025-01-20T14:20:00Z events=400 level.ERROR=17 level.INFO=286 level.WARN=97
Group into Spans covers count-based windows, sessions, and per-window scripts.
Message templates¶
Messages that differ only in IDs, hostnames, numbers, or durations are the same
event type. --drain finds those templates in the field you name with -k:
templates (4 items):
438: Connection timeout to database host <fqdn> after <duration>
187: Upstream <fqdn> returned <num> for request <uuid>
94: Failed to acquire lock on resource <path> after <duration>
23: Payment gateway <fqdn> rejected transaction <uuid>: insufficient_funds
--drain=full adds the line range and a sample line for each template;
--drain=json and --drain=id are for scripts. Filters run first, so
-l error --drain -k msg summarizes only errors.
What changed between two logs?¶
--drain-diff compares template frequencies: before and after a deploy, a good
day and a bad day.
--- examples/deploy_before.jsonl 110 events 2025-01-20T13:30:00Z .. 2025-01-20T13:56:10Z
+++ examples/deploy_after.jsonl 120 events 2025-01-20T14:00:00Z .. 2025-01-20T14:24:20Z
+ 3 config reloaded with <num> stale keys
+ 2 worker <num> restarted after heartbeat timeout <duration>
- 8 connection pool recycled for <fqdn>
* 14× more upstream <fqdn> returned <num> for request <uuid>
2 templates unchanged in frequency | field: msg
2 of them changed a little, but 110 and 120 events are too few to tell that from random variation
+ is a template only the second log has, - one that disappeared, and * a
template whose rate changed by more than random variation would explain. With a
single file, split it at a time (--cut-at 2025-01-20T14:00Z) or at the first
event matching an expression (--cut-before 'e.msg.contains("deploy")').
See it over time¶
Map formats print one character per event, one line per stretch of time:
2025-01-20T14:00:00.000Z _______________________________________________________
2025-01-20T14:02:42.000Z _______________________________________________________
2025-01-20T14:05:27.000Z _______________________________________________________
2025-01-20T14:08:12.000Z __________________________________________1____________
2025-01-20T14:10:57.000Z _____________________________1_________________________
2025-01-20T14:13:42.000Z _______________________________________________________
2025-01-20T14:16:27.000Z _________________________1________2______1______2______
2025-01-20T14:19:12.000Z __________________21_322_11_12_1132__111_3_3112_31_2__3
2025-01-20T14:21:57.000Z _22_23_1_11__2_1112_1121__1211__32211_22232222_122_1111
2025-01-20T14:24:42.000Z 21_2______________2___1_____2__________2_______________
2025-01-20T14:27:27.000Z ____________1______________________1___________________
2025-01-20T14:30:12.000Z _______________________________________________________
2025-01-20T14:32:57.000Z _______________________________________________________
2025-01-20T14:35:42.000Z _______________________________________________________
2025-01-20T14:38:27.000Z ______________________________
🔹 response_time_ms: 800 events, range 38.4 to 1000.0, p90=315.0, p95=389.7, p99=870.3
_ = below p90 | 1 = p90-p95 | 2 = p95-p99 | 3 = above p99 | . = missing
| Format | Each character shows |
|---|---|
-F levelmap |
the event's level (E, W, I, …) |
-F keymap -k FIELD |
the first character of FIELD |
-F tailmap -k FIELD |
where a number falls: _ below p90, 1 p90–p95, 2 p95–p99, 3 above p99 |
Processing statistics¶
--stats (-s) reports what Kelora did: lines read, events output and
filtered, parse and script errors, the time span, and the levels and fields it
saw. --stats=json is for scripts; --with-stats prints the stats after the
events instead of replacing them.
Detected format: json
Lines processed: 800 total, 0 filtered (0.0%), 0 errors (0.0%)
Events created: 800 total, 17 output, 783 filtered (97.9%)
Throughput: 239730 lines/s in 3ms
Timestamp: timestamp (auto-detected) - 800/800 parsed (100.0%) — access via meta.parsed_ts.
Input time span (before filtering): 2025-01-20T14:00:00+00:00 to 2025-01-20T14:39:57+00:00 (39m 57s)
Output time span (after filtering): 2025-01-20T14:20:18+00:00 to 2025-01-20T14:24:45+00:00 (4m 27s)
Levels seen: ERROR,INFO,WARN
Levels output: ERROR
Keys seen: endpoint,level,response_time_ms,status,timestamp