Logs
Every deployed room has a log. Your own room.log(...) calls go into it, and so do the lines the
runtime and the supervisor write about the room: it was created, it went to sleep, a handler threw,
it woke under a newer version. irtio logs reads that stream for a whole project, or for one room.
Writing to it
room.log(...) lives on the room object. Handlers that take the room directly call it as room.log(...); onJoin and onLeave take a join context, so there it is ctx.room.log(...):
// irtio/room.ts
import { defineRoom } from '@irtio/server';
import { schema } from './schema.js';
export default defineRoom(schema, {
mode: 'event',
onCreate(_state, room) {
room.log('quiz: room created, waiting in lobby');
},
onJoin(state, ctx) {
ctx.room.log(`quiz: ${ctx.room.clients.length} connected, phase ${state.match.phase}`);
},
}); Two things to know about it. Every room.log(...) line is recorded at info level; there is no room.warn or room.error, and warn and error lines in the stream come from the runtime and the
supervisor, not from your code. And arguments are converted to strings individually and joined with
a space, so an object arrives as [object Object]. Format values yourself:
room.log('quiz: scores', JSON.stringify(scores)); // good
room.log('quiz: scores', scores); // logs "[object Object]" room.log is the only logging surface. There is no console inside a room.
Reading it
npx irtio logs The project id comes from irtio.json, or from --project. Lines come back oldest first, so a
page reads top to bottom the way a file does:
$ npx irtio logs
2026-08-26 14:02:58Z INFO FV7A quiz: room created, waiting in lobby
2026-08-26 14:03:04Z INFO FV7A quiz: 3 connected, phase asking
2026-08-26 14:03:19Z INFO FV7A quiz: revealing round 1
2026-08-26 14:03:22Z WARN NNGQ irtio: onJoin threw TypeError: cannot read 'seat' of undefined
2026-08-26 14:04:12Z INFO FV7A quiz: sleeping (idle 10000ms) The columns are the timestamp, the level, the room id, and the message. A line with no room behind
it shows - in the room column.
Flags
| Flag | What it does |
|---|---|
--follow | Poll every 2 seconds and print what is new |
--since <cursor> | Start after this cursor instead of at the start of the retained window |
--room <id> | Only this room’s lines, filtered by the control plane |
--project <id> | Read this project instead of the one in irtio.json |
--url <control> | Talk to a specific control plane |
The cursor
--since takes a cursor, not a line count and not a duration. A cursor is the at timestamp of a
row you already have, and the server returns rows strictly after it. Because the stream is
oldest-first, the cursor you want is always the last line of the page you just read.
--follow does this for you: it prints a page, keeps the last row’s timestamp, waits two seconds,
and asks for everything since. Nothing you have already seen comes back.
npx irtio logs --follow One room at a time
npx irtio logs --room FV7A --follow The room filter is applied by the control plane, not by filtering the page after it arrives. That matters on a busy project: a page is 100 rows, and one chatty room can fill all 100 of them. Asking the server for one room’s stream gets you that room’s 100 rows instead of whatever survived the crowd. The room id is matched exactly.
Limits worth knowing
- A page is 100 rows. The CLI reads one page per request, and
--followreads a page every two seconds. A room producing more than 100 lines in that window will run behind. - Each room buffers its last 500 lines inside the tenant, and the host polls those buffers every few seconds. A room that writes thousands of lines between polls loses the oldest ones before they ever leave the box. Log events, not frames.
- The control plane keeps logs for 7 days. Older lines are pruned. If you need something permanently, send it somewhere permanent.
- Logs are not delivered instantly. They travel on a poll, so expect a few seconds of lag even
with
--follow. room.logis not free. It is a string built on the server, kept in memory, and shipped off the box. Logging every tick of a 20 Hz room is a bad trade.
The dashboard has the same stream with a room picker and a live tail, at irt.io/app/logs.