Skip to content
ccrawl

Run journal

The JSON Lines event log a long run writes, the metrics it exposes, and the queries to ask of both.

A publish run takes hours or days. When one of them finishes short, or stalls, or gets killed by a supervisor, the question afterwards is always the same: which shards failed, and when did it slow down. The run journal answers both without a regex over stderr.

Every long running command writes one JSON object per line to a journal file, narrates itself on stderr in whichever shape you asked for, and can serve Prometheus metrics for the duration of the run. Three global flags control all of it.

Flag Meaning Default
--progress text, json, or none text on a terminal, json otherwise
--journal Where the JSON Lines go run.jsonl beside the ledger
--metrics-addr Address to serve /metrics on off

The default for --progress is the useful one: run it in a terminal and you get human lines, redirect it into a log file or run it under a supervisor and you get JSON, without anybody having to remember a flag.

Which commands report

markdown export and markdown refetch report per shard, since a shard is the unit of work they resume on. download, export, host enrich and crawl seed report per item or per phase. The event shape is the same for all of them, so one query works everywhere.

markdown export and markdown refetch default their journal to run.jsonl beside the ledger, so a resumed run's events land next to the record of what it resumed from. The rest write a journal only when --journal asks for one.

Event schema

Four fields are on every line: ts, event, run, and pipeline. The rest depend on the event.

Field Meaning
ts UTC timestamp, millisecond resolution
event start, shard, item, phase, tick, or end
run This attempt, timestamp plus pid
pipeline markdown export, markdown refetch, download, export, host enrich, crawl seed
crawl Crawl ID, where the command has one
phase The stage a multi-phase command is in
name The file or URL an item event is about
shard Shard index, on shard events
status ok, failed, or skipped
error Why it failed
rows, bytes Rows written and bytes moved
warc_bytes, html_bytes, md_bytes, parquet_bytes Bytes by kind, on the markdown pipelines
extract_s, fetch_s, convert_s, export_s, publish_s Seconds in each phase of one shard
done, total, committed, skipped, failed Progress counts
inflight Units of work in flight at that moment
fetch_failed URLs a refetch shard could not fetch
rate_per_hour, eta_s, elapsed_s Live rates
free_disk_bytes, rss_bytes Resources at that moment

fetch_failed is separate from failed on purpose. A refetch shard that hit a thousand dead hosts still succeeded, and folding the two together would make a healthy run look like a broken one.

A tick lands every thirty seconds. That is often enough to see a stall on a three day run and rare enough that the journal stays small.

Queries worth keeping

Which shards failed, and why:

jq -r 'select(.event=="shard" and .status=="failed") | "\(.shard) \(.error)"' run.jsonl

When the rate dropped:

jq -r 'select(.event=="tick") | [.ts, .rate_per_hour, .inflight] | @tsv' run.jsonl

What the last attempt did, when the journal holds several:

jq -r --arg run "$(jq -r .run run.jsonl | tail -1)" 'select(.run==$run)' run.jsonl

Bytes committed per shard, largest first:

jq -r 'select(.event=="shard" and .status=="ok") | [.parquet_bytes, .shard] | @tsv' run.jsonl | sort -rn | head

Whether the run ran out of disk before it ran out of work:

jq -r 'select(.event=="tick") | [.ts, .free_disk_bytes, .rss_bytes] | @tsv' run.jsonl

Metrics

--metrics-addr :9090 serves the same numbers at /metrics in the Prometheus text format, for the runs you want on a dashboard rather than in a shell. The address is bound before the command starts working, so a busy port fails immediately instead of an hour in.

Metric Type Labels
ccrawl_shards_total counter pipeline, status
ccrawl_items_total counter pipeline, status
ccrawl_rows_total counter pipeline
ccrawl_bytes_total counter pipeline, kind
ccrawl_phase_duration_seconds histogram pipeline, phase
ccrawl_inflight gauge pipeline
ccrawl_done gauge pipeline
ccrawl_total gauge pipeline
ccrawl_rate_per_hour gauge pipeline
ccrawl_run_elapsed_seconds gauge pipeline
ccrawl_free_disk_bytes gauge pipeline
ccrawl_rss_bytes gauge

The endpoint lives for the length of the run and goes away with it, so scrape it with a short interval and treat a gap as the run having ended.

Resident set size

rss_bytes is read from /proc/self/status on Linux and from ps on macOS, and is 0 on anything else. It is sampled on the tick, not on a hot path.