Skip to Content

Structured info and warn logs now reach Datadog with their fields

Shipped 2026-09-11

Twelve logger.info and logger.warn calls across the tunn3l connector and the push-notification service recorded structured fields that never arrived in Datadog. What was stored for passcode.sent, for example, was:

{ "message": "passcode.sent", "attributes": { "date": 1789145375574, "service": "tunnelflight-api" } }

No member_id, no channel, no masked recipient — every field the call was written to capture.

Why

Winston hoists a message key out of the object it is given. So for a call like logger.info({ message: 'passcode.sent', channel: 'email', member_id: 123 }), logFormat receives the label as a string and the remaining fields as separate metadata. The typeof message === 'object' branch never runs, and the metadata is appended as a second line:

passcode.sent {"channel":"email","member_id":123,...}

Datadog ingests line by line, so the fields end up on a different event from the label they belong to. Searching for them returns nothing at all.

logger.error calls were unaffected, but only by accident: they use an error: key rather than message:, so winston has nothing to hoist and the object survives intact and serialises as one line. That is the entire difference between email.send_failed arriving with every field and passcode.sent arriving with none.

The fix

Renaming the key to event: keeps the object whole, and the existing info branch serialises it as a single JSON line with every field attached. No shared logging code was touched.

One detail worth knowing for anyone repeating this: the rename is anchored on the logger call, not on the key. In the tunn3l connector the line immediately after a log call is frequently a genuine API response — return { valid: 0, message: 'Member not found' } — and a broader search-and-replace would have quietly changed what that connector returns to its callers.

The equivalent ten calls in the passcode service are fixed alongside the work in TUN-867.

Trade-off

With no message key, Datadog’s message column now shows the JSON rather than the label. Full-text search still finds the event name, and the fields are finally queryable. The alternative — repairing logFormat so the info and warn branches merge message and metadata the way the error branch already does — would fix every caller at once and keep the label, but it is shared code on every log line in the API. That remains available as a follow-up.

Last updated on