# Logs What the app logs, where it goes, and how to read it back. The record format below is a public contract: `v: 1` keeps its fields and their meaning, and a change to either comes with a new version. ## Reading Logs ```bash Terminal elements logs # everything, oldest first elements logs -f # then keep going as records are written elements logs -service app -since 10m # the app's last ten minutes elements logs -since 2026-10-13T15:00:00Z -until 2026-10-13T15:05:00Z elements logs -json # the records as stored, one per line ``` - `-service `: one service's records. `app` is the program `elements start` or `elements run` runs; `job` is the job server and its workers; `project` is the project server itself (the `elementsd` process). - `-f` (`-follow`): print what is there, then new records as they arrive. - `-since` / `-until`: a duration back from now (`30s`, `10m`, `2h`, `1d`) or a time. A time is RFC 3339 (`2026-10-13T15:04:05Z`) or a local `2026-10-13 15:04`. - `-json`: the stored lines, for `jq` and other tools. Without `-service` every service is merged in time order. `elements logs` reads the files in `.elements/logs` and needs no running server, so it works while the app is down, from any terminal or subagent. It reads the current file of each service, not the rotated backups. ## Reading a Deploy Machine's Logs ```bash Terminal elements logs -remote # every production machine, merged elements logs -remote=staging#web2 -f # one machine, then keep going elements logs -remote -service app -since 1h -json ``` `-remote[=[#]]` reads the systemd journal on the deploy machines over ssh, the connection `elements db -remote` uses. The environment defaults to `production`. The journal holds `elements-app` (service `app`), `elementsd` (`project`), `elements-job` (`job`) and one unit per custom service, named after it. `-service`, `-since`, `-until`, `-f` and `-json` work as they do locally. On a deploy machine `elements start` writes each of the app's records to the journal as its JSON line, stamped with `service`, `pid`, `run` and `stream`, and every other line as the app printed it. A record keeps its own `ts`; any other line is a raw line at the time the journal received it. Lines are drawn as they are locally. With no `#` every machine of the environment is read and the lines are merged by `ts`. When there is more than one machine, each line starts with its machine's name, and `-json` sets `machine` on each record. A damaged line never stops a read. A line that is not a record (a write cut off by a full disk or a crash, or bytes that are not text) is shown as a raw line, unreadable bytes replaced, and `-json` prints it wrapped as a raw record, so every line it prints is a record. A record with no usable `ts` is placed beside the line before it. A line over 1 MB is cut there. With `-f`, a file that is rotated or deleted and made again is read from its start. ## Where Logs Go `elements start` reads everything the app writes to stdout and stderr, one line at a time. Each line is shown in the terminal and appended to `.elements/logs/.jsonl`, `app.jsonl` for the app. `elements run` logs the same way, to `app.jsonl`. The project server, `elementsd`, does the same for the processes it runs: job workers write `job.jsonl`, beside the job server's own records. Its own records go to `project.jsonl`, which holds records and nothing else; a panic it recovers from is an `error` record there. A crash nothing recovers from (a runtime fatal error, or a `SIGQUIT` dump) is printed by the Go runtime as plain text to stderr, which is appended to `.elements/logs/crash.log`. That file is kept as written: nothing reads, moves or clears it, and `elements logs` does not show it. If the server can't open `project.jsonl`, it logs to stderr, so to `crash.log`. Under systemd on a deploy machine both go to the journal instead. Several processes can append to one file at once, as `elements start` and `elements run` both do to `app.jsonl`; each line lands whole. A file rotates at 100 MB to `-