Release Notes

Version 1.2.1

📅 Release Date

August 24, 2026

📖 Overview

An anonymized sample of 330 production log records from several Workers, read line by line. Most of what follows is a consequence of something in that file rather than a design idea.

Half the line was already in $metadata. Cloudflare wraps every stored record in an envelope carrying the script name as service, the severity as level, the message, and trigger — the method and path joined. Two of those it lifts from the top of the object we log; the rest it knows on its own. A Worker restating them pays to ingest a column the dashboard already renders.

The Message column was blank. $metadata.message matched source.message on 171 of 171 rows and source.event on 0 of 136 — the promotion reads the key message and nothing else. 1.2.0 renamed it, so nothing fed the column.

46% of rows carried none of our fields. 152 of 330 were invocation logs, which duplicate what request.completed already says while leaving every application column empty.

And 15 of 28 distinct client addresses were fake. More on that below.

⚠️ Breaking Changes

  • The payload is flattened onto the line by the kernel, so data.status is status. Saved queries, dashboard columns and Logpush consumers reading data. drop the prefix
  • service, colo, country and durationMs are off the line by default. They were unconditional; they are now one word in LOG_FIELDS away. $metadata.service covers the first inside Workers Logs
  • invocation_logs: false is recommended in every Worker's wrangler.json. It removes 46% of rows. The cost is real: outcome, cpuTimeMs and wallTimeMs exist only on those rows and leave Workers Logs with them — request.completed / request.failed covers the outcome, and traces cover the timing

🚀 Features

  • LOG_FIELDS — which ambient fields a line carries, declared per deployment in wrangler.json:

    {
      "vars": { "LOG_FIELDS": "requestId,method,path,ip,status" }
    }
    

    cloudflareDefaults.logging.fields supplies that same list as the default, so a Worker that sets nothing carries the correlation id, the method, the path, the client address and the status — and leaves service, colo, country, component, durationMs and time to be asked for.

    That default is right only while the lines stay inside Workers Logs. A deployment shipping through Logpush, exporting OTel, or writing to a file should add service: a record that only means something inside one vendor's console is not a record. * keeps everything; "" keeps nothing.

    The mechanism is the kernel's and vendor-neutral. What is here is the judgement about this platform — which is the only thing that would be wrong to carry to another one.

  • A readable Message column. logToAnalytics composes a sentence:

    Level  Message
    info   Request completed: POST / (200)
    warn   Request completed: POST /api/v1/search (400)
    error  Request failed: POST /api/v1/resource (500)
    

    event is untouched and still what a query filters on. The sentence restates the method, path and status that are already fields on purpose: you read the message, you filter on the fields.

🔧 Enhancements

  • None. Everything this release does is a fix or a field-level default change — see Breaking Changes and Bug Fixes.

🐛 Bug Fixes

  • The client address was synthetic for a third of traffic. 15 of 28 distinct IPs we recorded fell in 240.0.0.0/4, and on 50 of 150 invocation rows cf-connecting-ip was byte-identical to cf-pseudo-ipv4:

    cf-connecting-ip    251.130.12.42                  ← what we logged
    cf-pseudo-ipv4      251.130.12.42                  ← identical: it is synthetic
    cf-connecting-ipv6  2a09:bac1:7680:1258::272:48    ← the real client
    

    A zone with Pseudo-IPv4 set to overwrite headers replaces CF-Connecting-IP for every IPv6 client. The replacement routes nowhere, geolocates to nothing, and need not be stable between requests — worse than logging no address at all, because it reads as a real answer and an investigation follows it to nobody. CloudflareRequestMetadata now reads cf-connecting-ipv6 first, so the true address wins whatever the zone is set to.

  • authentication.failed logged at info in the shared middleware's sibling — four production failures that never reached a severity filter. Now warn.

🔐 Security

  • None. See Bug Fixes above for the client-address correction.

🧪 Tests

📚 Documentation

  • None.

⬆️ Upgrading

{
  "vars": {
    "SERVICE_NAME": "my-worker",
    "LOG_LEVEL": "info",
    "LOG_FIELDS": "requestId,method,path,ip,status"
  },
  "observability": {
    "enabled": true,
    "logs": { "head_sampling_rate": 1, "invocation_logs": false },
    "traces": { "enabled": true, "head_sampling_rate": 0.1 }
  },
  "upload_source_maps": true
}

Then point the dashboard at $metadata.service, $metadata.level, $metadata.message, and the plain method, path, ip and status columns. Nothing needs a $workers. prefix any more.

🚨 Known Issues

  • None

📦 Dependencies

  • @bayudwiyansatria/core moves to ^1.2.1.

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