String vs map — fields and labels — structured logging and write-time JSON parsing

A log line is either a string parsed at read time or a typed map parsed at write time, and the two systems we are about to meet attach different names to the same idea — ELK calls indexed dimensions fields, Loki calls them labels.

Previously

The agent survived the backend outage by spilling to disk — but every line it spilled was still just a string. Before we ask the backend to index this stuff, we have to decide whether a log line is text or a typed record.

Scene 04

String vs map — fields and labels

  1. Watch
  2. Try it
  3. Predict
  4. Capture
UNSTRUCTUREDSTRUCTUREDraw line · regex parse at read time2026-05-09 12:00:01 ERROR [api] user_id=42 checkout fai…regex: /user_id=([^\s]+)/QUERY0uswrite fast · read slowJSON map · parse at write time{"ts": "2026-05-09T12:0…","level": "ERROR" ← label,"service": "api" ← label,"env": "prod" ← label,"user_id": "42","event": "checkout_failed"}Unstructured: the line is a STRING. The query 'find user 42' runs a regex over every character — the read clock ticks; the write …
What to watch for

One line, emitted twice. On the LEFT, it lands as an opaque string; the regex cursor scrubs across it looking for user_id=42, and the read-clock ticks — every character is work. On the RIGHT, the same line is a JSON map; user_id is a key, the hash lookup is instant, and the read-clock stays silent. Watch the asymmetry before we touch a slider.

Continue unlocks when the animation finishes.
Implementation

Highlighted lines are the ones running in the diagram right now.

App.emit_unstructured
the line is built as a STRING — work deferred to read time
def handle_request(uid, path, code):
# printf-style: format chars into one opaque blob
line = f"user_id={uid} path={path} status={code}"
log.info(line)
# later, every query pays the parse cost:
def query(term):
for line in scan_log_files():
if re.search(term, line): # regex over bytes
yield line
App.emit_structured
slog/zap-style: the line is a typed MAP — paid once at write
def handle_request(uid, path, code):
# named slots, typed values — JSON-marshalled at emit
log.info(
"checkout_failed",
"user_id", uid,
"path", path,
"status", code,
)
# later, every query is an O(1) hash lookup on the key:
def query(key, value):
return index[key].get(value, [])

Where this sits in Build a distributed logging stack (ELK / Loki)

Scene 04 of 12. A log line is either a string parsed at read-time or a typed map parsed at write-time, and the two systems we'll meet attach different names to the same idea — fields in ELK, labels in Loki.

Up next. Same line, two vocabularies — fields in ELK, labels in Loki. Now watch what each system actually writes to disk when that one line lands.

All 12 scenes in Build a distributed logging stack (ELK / Loki) · Every curriculum

Built with Arqly
Every scene in Build a distributed logging stack (ELK / Loki) builds on the one before it.All 12 Build a distributed logging stack (ELK / Loki) scenes