2026-08-23 — Consistent observability
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.
The premise was that Workers Logs, Workers Traces and Workers Metrics already observe a Worker, so the job was not to reimplement any of it — it was to add the things the platform's own view cannot supply and keep consumers from implementing them differently.
What the platform cannot supply
A stable event name. events spells the cross-cutting vocabulary once, so it is not available to typos:
request.completed, request.failed, request.unhandled, authentication.failed, external_api.error, kv.error
and the rest. A Worker's own vocabulary stays in that Worker as string literals — notification.sent means something in
exactly one place, and hoisting it here would centralise a list nothing shares.
A request id that survives a Service Binding hop. Cloudflare gives every request from the internet a CF-Ray, but a
Service Binding subrequest gets no new one, so the trail stopped at each hop and a failure three Workers deep could not
be walked back to its cause. RequestCorrelation is the header that carries one id across it.
It is correlation and only correlation. The value is attacker-controlled at the edge, and the note that it must never gate access or identify a caller is in the class comment rather than only here, because that is where someone about to misuse it will be looking.
Where the service name lives
A stale service name had previously reached production because it lived in a config/LoggingConfig.ts that a rename
never touched.
SERVICE_NAME in wrangler.json, beside the Worker's own name, where the two cannot part company unnoticed.
LoggingConfig.ts was removed from the sampled deployments.
CloudflareEnv gained LOG_LEVEL and SERVICE_NAME — the two members of that interface that are not bindings.
Everything in src/ that logs hands its Env straight to Logger.fromEnv, and the kernel asks for exactly that shape;
leaving each Worker to redeclare them made the call un-typeable from a generic E extends CloudflareEnv.
Two events, not one
logToAnalytics had filed everything under a single request. It now separates request.completed from
request.failed, with the level still distinguishing a 4xx from a 2xx — both are a request that ended as the
application meant it to — and a 5xx getting its own name. That is what makes "how often is this Worker failing" a
filter on one value rather than a range query.
Configuration across deployments
observability.logs.head_sampling_rate, observability.traces, and upload_source_maps: true in every
wrangler.json, with trace rates chosen per Worker rather than set to one number everywhere: 1.0 on the one whose
volume is low enough to trace whole, 0.2 on two, 0.1 on three, and 0.05 on the busiest.
What this release got wrong
Every line carried its own service, colo and country, unconditionally, on the argument that a line must mean
something outside the Workers Logs console. That argument is sound and the implementation was not: it made the trade for
every deployment instead of letting each make it, and inside the console — where these lines are actually read — half of
it duplicated $metadata.
Reading 330 production records the next day also turned up a synthetic client address on a third of traffic and a
Message column that had been blank since the message → event rename. See 2026-08-24.