Release Notes
Version 1.2.0
📅 Release Date
August 23, 2026
📖 Overview
Observability, rebuilt so that a log line means something on its own.
Workers Logs stamps the script name, the outcome, the ray id, the colo, the country, and the CPU and wall time on every
invocation it ingests, and offers each as a column. Those columns exist inside Workers Logs. A line exported to a file,
shipped through Logpush, or read anywhere the invocation record did not travel keeps none of them — so every line
carries its own service, requestId, ip, colo, and country, and the overlap is deliberate.
The cost of a second copy is drift: a stale service name can reach production when it lives in a config/ file that a
rename never touches. The name therefore moved to SERVICE_NAME in wrangler.json, beside the Worker's own name,
where the two cannot part company unnoticed.
Added on top is what the platform cannot supply at all: a stable event name to filter and count on, and a request id that survives a Service Binding hop.
⚠️ Breaking Changes
- Log lines carry
eventwhere they carriedmessage. Anything querying onmessage— a saved Workers Logs view, a Logpush consumer, an alert — needs the field name changed. The values changed with it: prose has become dotted event names. - Every Worker needs
SERVICE_NAMEin itswrangler.jsonvars. All seven have it. A deployment without one still logs, and every line readsservice: "unknown"until it is added — visible in a query rather than silently missing. LoggingSettings.serviceis optional, and is now the fallback rather than the source. Code reading it readsstring | undefined.
🚀 Features
events— the cross-cutting event vocabulary, spelled once so it is not available to typos.request.completed,request.failed,request.unhandled,authentication.failed,external_api.error,kv.error, and the rest.import { events } from '@bayudwiyansatria/cloudflare' logger.error(events.EXTERNAL_API_ERROR, { provider, status })A Worker's own vocabulary stays in that Worker as string literals:
order.sentandinvoice.fetch.completedare meaningful in exactly one place each, and hoisting them here would centralise a list nothing shares.RequestCorrelation— the header that carries one request id across a hop the edge never sees.await env.NLP_WORKER.fetch(url, { headers: { 'content-type': 'application/json', ...RequestCorrelation.of(ctx.get('requestId')) } })Cloudflare gives every request from the internet a
CF-Ray, but a Service Binding subrequest gets no new one. Without a header of its own the trail stopped at each hop, and a failure three Workers deep could not be walked back to the request that caused it.It is correlation and only correlation. The value is attacker-controlled, it must never gate access or identify a caller, and nothing in this package reads it for anything but a log field.
CloudflareEnv.LOG_LEVELandCloudflareEnv.SERVICE_NAME— the two members of that interface that are not bindings. Everything insrc/that logs hands itsEnvstraight toLogger.fromEnv, and the kernel asks for exactly this shape; leaving each Worker to redeclare them made that call un-typeable from a genericE extends CloudflareEnv.
🔧 Enhancements
logToAnalyticsfiles requests underrequest.completedandrequest.failedinstead of the singlerequest. The level still separates a4xxfrom a2xx— both are a request that ended as the application meant it to — but a5xxgets its own name, which is what makes "how often is this Worker failing" a filter on one value rather than a range query.serviceis stamped fromSERVICE_NAME, andcoloandcountrystay on the request line, read once fromrequest.cf. All three overlap a Workers Logs column on purpose: the line has to identify itself and say where the request landed without the invocation record beside it. They also reach Analytics Engine, for the unrelated reason that an aggregate is joined to nothing and has to carry its own dimensions.CloudflareRequestMetadataprefers an inboundX-Request-IdoverCF-Ray, so the id an upstream Worker propagated is the one the whole fan-out shares.CF-Rayremains the fallback and a UUID the last resort, so correlation degrades to per-invocation rather than disappearing underwrangler dev.SecurityMiddlewaresays why it refused. A401now leaves anauthentication.failedline namingapi_key_mismatchorapi_key_absent, a rejected origin anauthorization.deniedline, and a Worker deployed without its key arequest.failedline naming which half is missing. Neither key appears in any of them: the value a caller presented is a credential even when it is the wrong one.AnalyticsBinding.writeSafeno longer writesconsole.error('[Analytics Engine Error]')on every request. An unbound dataset is a deployment decision, not a fault, and now logsanalytics.erroratdebug; a bound dataset that rejects a write logs it atwarn. Conflating the two is what made the old line unreadable in production, where the Workers that bind no dataset produced one per request forever.SecurityMiddlewarestopped logging the client IP atdebug. Therequest.completedline beside it already carries the address. It still goes on the context, where a handler can read it without re-deriving it from the wrong header.
🐛 Bug Fixes
- Secrets can no longer reach a log line through a payload.
Loggerruns every payload throughredact(new in@bayudwiyansatria/core@1.2.0) before serialising, replacing credential-named fields —apiKey,authorization,set-cookie,refresh_token,clientSecret, and the rest, in any casing or separator style — with[redacted], recursively and to a bounded depth. It is a last line of defence, not a licence to log request bodies.
🔐 Security
- None. See Bug Fixes above for the payload-redaction change.
🧪 Tests
test/middlewares/logger.spec.ts— new, against a real Hono app: the event and level per outcome, the request id from both headers, one line per request, and that the line carries its ownservice,colo, andcountry.test/core/observability/RequestCorrelation.spec.ts— new.test/core/services/CloudflareRequestMetadata.spec.ts— new, pinning the id precedence and thatX-Forwarded-Foris never believed.test/core/bindings/AnalyticsBinding.spec.ts— new, pinning that an absent binding stays below the default threshold.
📚 Documentation
- Observability — new. Event naming, the
service/eventsplit and where each comes from, request correlation across Service Bindings, what must never be logged, sampling rates with the reasoning behind them, source maps, and a five-step walk from a metrics spike to the line that explains it.
⬆️ Upgrading
npm install @bayudwiyansatria/cloudflare@1.2.0
Add SERVICE_NAME to every Worker's wrangler.json vars, and update anything querying the old message field — a
saved Workers Logs view, a Logpush consumer, an alert — to event instead. See Breaking Changes above.
🚨 Known Issues
- None
📦 Dependencies
@bayudwiyansatria/coremoves to^1.2.0, forredactand theeventfield.
👥 Contributors
- Bayu Dwiyan Satria
🙏 Acknowledgments
Special thanks to all contributors and the open-source community for their support.
For more information, visit the project's GitHub repository.