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.