Release Notes

Version 1.2.1

📅 Release Date

August 24, 2026

📖 Overview

1.2.0 gave every line an event and made it say which service wrote it. Production then showed what that cost: 330 log records from five services, in which nearly half the fields on a line were being paid for twice, and the one column a person actually reads was blank.

Three things came out of reading them.

The data wrapper was making every useful field a path. data.status, data.ip, data.colo — a dashboard column named for a nesting level rather than for what it holds, and a query prefix on every filter.

The line had no message. 1.2.0 renamed it to event, which is right for machines and wrong for the one column most log viewers show by default. Both belong on the line: event for event = request.failed, message for the person scanning a page of them at two in the morning.

And nothing could be turned off. Which fields a line carries is a property of where it is going — a runtime that stamps the service name on every record does not need a second copy, and one that stamps nothing does. That is a deployment's decision, and until now it took a code change in nine repositories.

🚀 Features

  • LogFields — decides which context fields reach the line, from LOG_FIELDS in the environment, logging.fields on the configuration surface, or the kernel default.

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

    * admits every field. An empty declaration admits none, which is deliberately not the same as leaving it unset: LOG_FIELDS="" is a deployment saying the level, the event and the message are the whole line, and it must not fall through to the surface.

    The kernel default is *. Narrowing means knowing what the runtime already records, which is an adapter's knowledge, not the kernel's — @bayudwiyansatria/cloudflare supplies its own list, and a kernel-only consumer sees no field disappear.

  • An optional sentence on every log call. debug, info, warn and error take a third argument:

    log.info('notification.sent', { provider }, `Notification sent via ${provider}`)
    

    Omit it and message falls back to the event name, so the column is never empty. Existing call sites are unaffected.

⚠️ Breaking Changes

  • The payload is flattened onto the line. logger.info('article.created', { id: 7 }) emits { …, "id": 7 }, not { …, "data": { "id": 7 } }. Anything reading data.x — a saved query, a dashboard column, a Logpush consumer — reads x now. No call site changes; the shape of what they produce does.
  • Every line carries a message, defaulting to the event name. event is unchanged and still the field to filter on.
  • Context fields can be filtered out. Under the kernel's own default nothing is, but a logging.fields on the surface or a LOG_FIELDS in the environment will drop what it does not name — including service and time, which were previously unconditional.

🐞 Fixes

  • redact no longer masks booleans. Found in production: every field on an authentication.failed line came back [redacted], including the four booleans that said why it failed.

    { "tokenPresented": "[redacted]", "apiKeyPresented": "[redacted]" }
    

    The line recorded a failure and withheld its cause — a security feature defeating a security log. A boolean cannot carry a credential: there are two of them and neither is a secret, so they pass through. Names that routinely hold numeric secrets — cardNumber, cvv, cvc, pinCode, otp, iban — are exempt from that exemption and mask whatever type they arrive as, because digits in a log stream are as damaging unquoted as quoted.

⬆️ Upgrading

Nothing stops compiling. Two things to check:

  1. Anything reading data. — saved queries, dashboard columns, Logpush consumers, alert rules — drops the prefix.
  2. time and service are now listed fields. On the kernel's default they are still emitted; if you set logging.fields, name them or lose them.

results matching ""

    No results matching ""