Emit machine-readable key-value events instead of prose sentences, with a stable schema and controlled field cardinality. Use when logs need to be queried and aggregated, not just read one line at a time by a person.
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Amey-Thakur/AI-SKILLS --skill structured-logging --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Structured Logging?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/amey-thakur-structured-logging)More formats (shields.io, HTML) on the badges page.
---
name: structured-logging
description: Emit machine-readable key-value events instead of prose sentences, with a stable schema and controlled field cardinality. Use when logs need to be queried and aggregated, not just read one line at a time by a person.
---
# Structured logging
`log.info("user " + id + " failed to pay $" + amt)` reads fine to a human and
tells a machine nothing. You cannot filter it, group it, or count it without a
regex that shatters the next time the sentence changes. Structured logging
emits events as fields so a query engine can answer questions the author never
thought to ask.
## Method
1. **Emit an event with fields, not an interpolated string.** Write
`log.info("payment_failed", user_id=id, amount=amt, currency="usd")` and let
the logger render JSON. Now `amount>100 and currency="usd"` is a query, not
a grep: the message becomes a stable name and the variables become
searchable fields.
2. **Keep the event name constant and move nouns to fields.** The first argument
is a fixed token like `order_placed` or `db_timeout`, identical on every
emit. Everything that varies, ids, counts, durations, lands in fields.
Constant names let you count occurrences without matching free text.
3. **Pin a schema and reuse names across services.** Agree that the user is
always `user_id`, latency is always `duration_ms`, the request key is always
`request_id`. When every service spells them alike, one query joins them all;
`uid`, `userId`, and `user` fragment the same data into three.
4. **Control cardinality: bounded values are fields, unbounded ones get
sampled.** A `status` with a dozen values is a fine group-by. A raw SQL
string or full stack trace as a field explodes index size and cost. Keep
high-cardinality blobs in an unindexed message field, or sample them.
5. **Bind context once at the entry point.** Attach `request_id` and `user_id`
with `logger.bind(request_id=rid)` so every line in that request carries them
without repeating arguments. Correlation turns automatic instead of a field
you forget on the one line that mattered.
6. **Log durations and counts as numbers.** Emit `duration_ms=214` as an
integer, never `"took 214ms"`. Numeric fields let the backend compute
averages, percentiles, and thresholds; a number wrapped in a sentence has to
be parsed back out before it is usable.
## Checks
- Can you answer a new question with a field filter, touching no logging code?
- Do the same concepts carry the same field name in every service?
- Is any field's value unbounded, and if so is it unindexed or sampled?
## Boundaries
Structure fixes the shape of a line, not its urgency: which level it fires at is
log-levels. Chasing the id across services once it is logged is
distributed-tracing. Match the field names the project already uses over a
tidier scheme of your own, because a shared schema is the entire point.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!