2026-08-23 — Logger stops restating what the runtime knows

Written up after the fact: this session shipped 1.2.0 and left no entry here at the time. CHANGELOG.md and docs/release-notes/1.2.0.md are the record it was reconstructed from.

Three changes to Logger, all following from one observation: the runtimes this kernel is deployed on already know things it was restating, and the things it was not saying were the ones nothing else could supply.

An event name where the sentence was

Logger emitted prose under a message key. A collector that indexes JSON fields turns a key into a column, and a column whose value is a different sentence on every line can be neither filtered nor counted — 'Failed to send notification for user 7' groups with nothing.

So the first argument became a dotted event name, emitted as event. event = notification.failed is a filter, and the count of one event over time is a series.

The method signatures did not change, so nothing stopped compiling. What changed was the field a query runs against, which is the kind of break a type checker cannot catch and a release note has to.

A service name that comes from the deployment

logging.service on the configuration surface meant the name lived in a config/ file. A rename does not touch that file, which is how a deployed service came to spend production emitting service: 'cloudflare-boilerplate' — the boilerplate's default, inherited and never changed.

SERVICE_NAME in the environment now wins, with logging.service as the fallback for runtimes that have no environment to read. The value sits beside the deployment's own name — in wrangler.json, two lines below "name" — where a rename that misses one is visible in the same diff.

LoggingSettings.service became optional and systemDefaults stopped supplying one, so a default is no longer something to inherit and forget.

With neither set the line reads service: "unknown" rather than omitting the key. An omitted field makes a misconfiguration invisible: the lines look ordinary, the service looks healthy, and nothing surfaces until someone needs to tell two services apart in one stream and cannot. service = unknown is a query that finds every one of them.

Redaction before serialisation

Logger handed whatever payload it was given straight to JSON.stringify. redact now runs first, masking fields whose names look like credentials — separator- and case-insensitively, so one entry covers apiKey, API_KEY, x-api-key and providerApiKey.

It walks arrays and nested objects to a bounded depth, which is also what makes a cyclic structure terminate rather than exhaust the stack, and returns Error values untouched so the expansion pass afterwards can still recognise them. That ordering is load-bearing in one direction only and is written down where it matters.

It lives in security/ rather than utils/, on the same intent test as timingSafeEqual: it exists because the obvious alternative is unsafe, not because it happens to be useful.

LogContext.component

Call sites had been passing a class name as service, so the field meant "which deployment" on some lines and "which class" on others. component answers the second question and leaves service to the first.

Known at the time, fixed later

The data wrapper, the missing message, and redact masking booleans were all shipped in this release and found the next day by reading production output. See 2026-08-24.

results matching ""

    No results matching ""