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 event where they carried message. Anything querying on message — 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_NAME in its wrangler.json vars. All seven have it. A deployment without one still logs, and every line reads service: "unknown" until it is added — visible in a query rather than silently missing.
  • LoggingSettings.service is optional, and is now the fallback rather than the source. Code reading it reads string | 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.sent and invoice.fetch.completed are 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_LEVEL and CloudflareEnv.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 this shape; leaving each Worker to redeclare them made that call un-typeable from a generic E extends CloudflareEnv.

🔧 Enhancements

  • logToAnalytics files requests under request.completed and request.failed instead of the single request. The level still separates a 4xx from a 2xx — both are a request that ended as the application meant it to — but a 5xx gets its own name, which is what makes "how often is this Worker failing" a filter on one value rather than a range query.
  • service is stamped from SERVICE_NAME, and colo and country stay on the request line, read once from request.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.
  • CloudflareRequestMetadata prefers an inbound X-Request-Id over CF-Ray, so the id an upstream Worker propagated is the one the whole fan-out shares. CF-Ray remains the fallback and a UUID the last resort, so correlation degrades to per-invocation rather than disappearing under wrangler dev.
  • SecurityMiddleware says why it refused. A 401 now leaves an authentication.failed line naming api_key_mismatch or api_key_absent, a rejected origin an authorization.denied line, and a Worker deployed without its key a request.failed line 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.writeSafe no longer writes console.error('[Analytics Engine Error]') on every request. An unbound dataset is a deployment decision, not a fault, and now logs analytics.error at debug; a bound dataset that rejects a write logs it at warn. Conflating the two is what made the old line unreadable in production, where the Workers that bind no dataset produced one per request forever.
  • SecurityMiddleware stopped logging the client IP at debug. The request.completed line 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. Logger runs every payload through redact (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 own service, colo, and country.
  • test/core/observability/RequestCorrelation.spec.ts — new.
  • test/core/services/CloudflareRequestMetadata.spec.ts — new, pinning the id precedence and that X-Forwarded-For is 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/event split 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/core moves to ^1.2.0, for redact and the event field.

👥 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.

results matching ""

    No results matching ""