{
  "markdown": "<div align=\"center\">\n\n# scribe\n\n**The one who writes down what happened.**\n\nA structured log with a fixed shape, a transaction identifier, and a cost you can measure in nanoseconds.\n\n`11 ns` suppressed · `24 ns` written · zero dependencies · Bun & Node\n\n</div>\n\n```\n2026-08-21T14:03:11.482Z|INFO |checkout |01a02443eaf5c9580d12|ORDER_OK   |org=7 items=6 total=249.90 ms=7\n└──────── when ────────┘ └lvl┘ └─ who ─┘ └─ which request ──┘ └─ what ──┘ └───────── detail ───────────┘\n```\n\nSix fields. One line. Every service.\n\n---\n\n## Why it looks like that\n\nNobody reads one service.\nThey read a failure that crossed four of them at 03:00, and the only thing that makes that possible\nis every service writing the same shape with the same identifier in the same column.\n\nSo the format is not a preference. It is the whole point.\n\n- **Fixed columns** — `cut -d'|' -f5` works. So does reading it down the page.\n- **One record, one line** — no value can break a record apart, whatever a caller puts in it.\n- **A transaction identifier in every record**, minted at the edge, propagated inwards. `grep 01a02443eaf5c9580d12 *.log` is the whole investigation.\n- **Time-ordered identifiers** — `sort` is chronological. No extra key, no random B-tree writes.\n\n## Install\n\n```bash\nbun add @concurrent-systems/scribe\n```\n\n## Use\n\n```ts\n// lib/log.ts — your vocabulary, once\nimport { SCRIBE_EVENTS, type Emitter, log as base } from \"@concurrent-systems/scribe\";\n\nexport const EVENTS = {\n  ...SCRIBE_EVENTS,\n  ORDER_OK:   \"ORDER_OK\",\n  ORDER_FAIL: \"ORDER_FAIL\",\n  PAY_DECLINE:\"PAY_DECLINE\",\n} as const;\n\nexport type Event = (typeof EVENTS)[keyof typeof EVENTS];\nexport const log = base as Emitter<Event>;\n```\n\n```ts\n// app.ts\nimport { initLog, initPayloads, newTxnId, runWithTxn } from \"@concurrent-systems/scribe\";\nimport { EVENTS, log } from \"./lib/log.ts\";\n\ninitLog({ component: \"checkout\" });\ninitPayloads();\n\napp.use((c, next) =>\n  runWithTxn({ txn: newTxnId(), startedAt: performance.now(), body: await c.req.text() }, next),\n);\n\nlog.info(EVENTS.ORDER_OK, { org, items, total, ms });\n```\n\nEvery record inside that request now carries the same identifier, with no argument threaded anywhere.\n\n## Naming your events\n\nAn event code is an **identity, not a message**.\nIt is what a runbook cites, what a saved search matches, and what an alert fires on —\nso it has to survive any rewording of the human explanation beside it.\nYou can change the sentence. You cannot change the code.\n\nFive rules, and the fifth is the one people skip:\n\n1. **`SUBJECT_VERB`** — the thing, then what happened to it.\n2. **Ten characters at most**, so the column stays fixed.\n3. **Group by subject, not by service.** If a payment is observed in two services, both write `PAY_TAKEN`.\n   Which service it was is already in the component column.\n4. **One code per *outcome*, not per branch.** A wrong password and an unknown address are one\n   `AUTH_FAIL` with the reason in a field — the response does not distinguish them, so neither should\n   the record a support engineer pastes into a ticket.\n5. **The key and the value are the same string.** `ORDER_OK: \"ORDER_OK\"`.\n   Then grepping the source for a code you found in a log file lands you on the line that wrote it.\n\n```ts\nexport const EVENTS = {\n  ...SCRIBE_EVENTS,\n\n  // ── Service lifecycle ──────────────────────────────────────────────\n  /** Listener is up and accepting traffic. Carries port and version. */\n  SRV_START:   \"SRV_START\",\n  /** Shutdown signal received; draining has begun. */\n  SRV_STOP:    \"SRV_STOP\",\n\n  // ── The order, wherever it is observed ─────────────────────────────\n  /** An order arrived and was given its identifier. */\n  ORDER_RECV:  \"ORDER_RECV\",\n  /** The canonical record for one order: items, total, elapsed. */\n  ORDER_OK:    \"ORDER_OK\",\n  /** The order failed. The caller received an error. */\n  ORDER_FAIL:  \"ORDER_FAIL\",\n  /**\n   * Nothing was in the basket, so nothing happened.\n   *\n   * Worth its own code: an empty basket is otherwise a 200 with nothing\n   * written anywhere, and it is the most common \"checkout does nothing\" report.\n   */\n  ORDER_EMPTY: \"ORDER_EMPTY\",\n\n  // ── Payment ────────────────────────────────────────────────────────\n  /** Funds captured. */\n  PAY_TAKEN:   \"PAY_TAKEN\",\n  /** The issuer declined. Reason in a field, never in the code. */\n  PAY_DECLINE: \"PAY_DECLINE\",\n  /** The gateway could not be reached; the order is unresolved. */\n  PAY_NOGW:    \"PAY_NOGW\",\n} as const;\n```\n\nTwo of those deserve the attention:\n\n**`ORDER_EMPTY` earns a code because \"nothing happened\" is a result.**\nThe single most common support report on any system is *it did nothing* — and the one path that\nwrites no record at all is the one nobody can investigate. Give the quiet outcome a name.\n\n**`PAY_NOGW` is not `PAY_DECLINE`.**\nOne means the answer was no; the other means there is no answer. Collapsing them looks tidy and\nloses the distinction between a customer who was refused and money that may or may not have moved.\n\n## The parts you will care about at 03:00\n\n### Change the level without a restart\n\n```ts\nsetLevel(\"debug\");   // → \"debug\"\n```\n\nA restart destroys the thing you were investigating: the process that was misbehaving is gone,\nand the next one behaves. Turning `debug` on for ninety seconds on the node that is *actually wrong*\nis how the interesting record gets written at all.\n\nscribe does not decide how you reach this — a route behind an internal secret, a signal, an admin socket.\nYour service knows what surface it can safely expose; a log library guessing that for you would be\nthe library making a security decision on your behalf.\n\n### Payloads, captured for everything, written for failures\n\nThe request body is the highest-value artefact there is — with it the whole transaction can be reconstructed —\nand the worst possible thing to put in a log stream. It is unbounded, it is where the user's data is,\nand it dwarfs everything else by more than an order of magnitude.\n\nSo scribe **holds** the body (one pointer, no copy, no serialisation) and **writes** it only if the\ntransaction fails, to its own file, with its own retention and its own permissions.\n\n100% capture for exactly the transactions anyone will ever investigate. Nothing on the request path for the rest.\n\nA payload record carries the transaction identifier, the tenant and the outcome. Anything else an\noperator would search by goes in `tags`, and is written verbatim:\n\n```ts\nrunWithTxn({ txn: newTxnId(), startedAt: performance.now(), body, tags: { sku, channel } }, next);\n```\n\nA tag that collides with a key scribe writes itself is ignored, so no caller can choose its own identity.\n\n### Secrets do not reach the file\n\nA field whose *name* looks like a credential is redacted, in both formats, before anything is written.\n\n```ts\nlog.info(EVENTS.ORDER_OK, { password: \"hunter2\" });\n// …|ORDER_OK   |password=[REDACTED]\n```\n\nA log file is copied, shipped to a collector, read by support and kept far longer than anyone intended.\nA password that reaches one has been published, and no later fix un-publishes it.\n\n## Settings\n\nRead once at `initLog`, from the environment, so an operator can change them without a deploy.\n**An unrecognised value falls back rather than throwing** — a typo in a log setting must never stop a\nservice from starting, least of all mid-deploy.\n\n| Variable | Default | |\n|---|---|---|\n| `LOG_LEVEL` | `info` | `fatal` `error` `warn` `info` `debug` `trace` |\n| `LOG_FORMAT` | `text` | `text` or `json` |\n| `LOG_BUFFER` | `4096` | Records held before the newest are dropped |\n| `LOG_FLUSH_MS` | `50` | How often the buffer is written |\n| `LOG_PAYLOAD` | `error` | `off` · `error` · `all` |\n\n`initLog({ level, format, fd })` overrides all of it — which is how a test pins it.\n\n## Rotation\n\nDon't. scribe writes to a descriptor and never opens a log file itself, so rotation belongs to\nwhatever is already supervising the process: **journald** (`SystemMaxUse=`) under systemd, or the\ncontainer runtime's log driver (`max-size`, `max-file`) under Docker.\n\n`logrotate` against a file a process holds open is the classic footgun — renaming the file does not\nmove the process's file descriptor, so it keeps writing to the renamed inode forever.\n\n## What it costs\n\n```\nlog emission: disabled 11 ns · enabled 24 ns · in context 25 ns\n```\n\nFour properties, in the order they matter:\n\n1. **The gate is the first statement.** A suppressed record costs one integer comparison and a return — no object, no clock, no context lookup.\n2. **Nothing is serialised at call time.** Formatting a timestamp, padding a column, building `key=value` — all of it happens later, in a timer, on a batch.\n3. **Fields are scalars, by type.** Not `unknown`. That deletes the recursive sanitiser, the depth cap, the cycle guard and the unbounded record — none of which need to exist if a value cannot nest.\n4. **One write per batch.** A flush concatenates and issues a single `writeSync`.\n\n> **One rule for callers:** the `fields` object is read at flush time, not at call time. Pass a literal.\n> A long-lived object mutated afterwards logs the later value.\n\n## Design notes\n\nThis repository is an [OKF](https://github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md)\nbundle — markdown with frontmatter, an index per level, navigable by relative links.\n\n| | |\n|---|---|\n| [`requirements/`](requirements/index.md) | What the log must do, and the three things it must never do |\n| [`design/decisions/`](design/decisions/index.md) | Why it is built this way |\n| [`plan.md`](plan.md) · [`log.md`](log.md) | What is next, and what happened |\n\nTwo worth reading:\n[scribe owns the mechanism, not the vocabulary](design/decisions/mechanism-not-vocabulary.md) —\nwhy your event codes are yours, and\n[the registry is the package](design/decisions/published-to-npm.md).\n\n## Contributing\n\n`bun install && bun test`. 87 tests, strict lint, no dependencies — and the last of those is a\nconstraint, not an achievement: every service loads this, so anything scribe depends on, they all do.\n\nMIT.\n",
  "bytes": 10055,
  "sha": "429107a676f4a3c48d3ba6cbdf2f6d1fe7899e7d3ee5f2dc53831787aa80a2cc",
  "repo_slug": "concurrent-systems/scribe",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_concurrent_systems_scribe_index_md_558fa7e5/readme"
}