==> read the decisions
ADR-0018: Logs are structured, and the format is shared
Status: Accepted (2026-10-14). The fleet side is nixos2 ADR-0017. Implemented 2026-10-15.
Context
Section titled “Context”Every host's journal already reaches Loki on the hub, labelled by host, unit and syslog level. What
the platform and user services write into it is free text: console.log lines, a different shape in
each file, and nothing that ties the API's side of a request to the user service's side of it.
Decision
Section titled “Decision”@cs/login the shared library writes one JSON object per line to stdout, which systemd puts in the journal. Fields:ts,level,msg,service(cs-api,cs-web,cs-user-<user>),tenant,project,req; for a request alsomethod,route,status,msanddoor.- One request id, end to end. The API creates it, passes it to the user's service host in a
header, and returns it in the response (
x-cs-request). One id finds both halves of a request. - The platform logs every request once, from a single middleware; contexts log through the same library.
- User code logs through
ctx.log, already bound to tenant, project and request. A plainconsole.login user code is wrapped by the service host into the same format, so nothing escapes unstructured, and the host logs every forwarded request itself, so a request is visible even if the user's code logs nothing. - Never logged: tokens, cookies, passwords, invite links, request or response bodies.
Consequences
Section titled “Consequences”- Grafana can show request rate, errors and latency per tenant and project, and a request's whole path by its id.
- Which fields become Loki labels is the fleet's call (nixos2 ADR-0017): only fields with few values, never the request id.