Skip to content

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:

kelora examples/api_latency_incident.jsonl --freq endpoint --freq status
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):

kelora examples/web_access_large.log.gz --freq status | head -3
status  501 73
status  301 71
status  500 67
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:

kelora examples/api_latency_incident.jsonl --span 20m --freq level --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:

kelora examples/syslog_errors.log --drain -k msg
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.

kelora --drain-diff examples/deploy_before.jsonl examples/deploy_after.jsonl -k msg
--- 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:

kelora examples/api_latency_incident.jsonl -F tailmap -k response_time_ms
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.

kelora examples/api_latency_incident.jsonl -l error -s
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