Skip to content
runlot
ObservabilityLogs

Logs

Follow your worker's output in real time, filter it by level, stream or request, and search what it printed over the last few days.

runlot logs

Press Ctrl-C to exit. When you connect, you first receive the most recent 200 lines, and after that new logs stream in live.

runlot logs --tail 1000        # how many lines to receive up front (max 2000)
runlot logs --no-follow        # print up to the most recent line and exit

Filtering the live tail

Filters are applied on the node before a line is sent, so a busy worker does not flood your terminal.

runlot logs --level warn                    # warn and error only
runlot logs --source stderr                 # one stream: stdout, stderr or runlot
runlot logs --request 4bf92f3577b34da6a3ce929d0e0e4736
runlot logs --tail 500 --grep timeout       # --grep filters the live tail when --tail or --no-follow is given

Levels are ordered debug < log = info < warn < error, and --level keeps that level and everything above it. A line printed as JSON with a level field counts as that level; otherwise stderr is error and stdout is log. The runlot stream is Runlot's own lines about your project — started, stopped, out of memory.

--format json prints one object per line, in the same shape the stored events have:

runlot logs --format json | jq 'select(.level == "error")'

Past logs

runlot logs follows the worker from this moment on. To look at what it printed earlier, give a time range:

runlot logs --since 2h                  # the last two hours
runlot logs --since 24h --grep ERROR    # only lines containing ERROR
runlot logs --since 2026-09-20T10:00 --until 2026-09-20T11:00
runlot logs --since 6h --level error --source exception

--since and --until take either an age (30m, 2h, 3d) or a time (2026-09-20T10:00, or a date on its own such as 2026-09-20). A time is UTC, the same zone the printed lines are stamped in, so a timestamp copied out of the output can be pasted straight back in. Write +09:00 or -04:30 after it to name your own zone instead. --grep on its own searches stored logs; it is a plain substring, not a regular expression.

Stored logs have two more sources than the live tail: console (the lines of a request whose contents were collected) and exception. --level, --request and those two sources need the event store; a self-hosted installation without ClickHouse refuses them by name instead of ignoring them.

Past logs print oldest first, and a range wider than one page ends with a line telling you how to fetch the next one:

-- more available: rerun with --cursor obs.1790244251882.3

--limit sets the lines per page (200 by default, up to 1,000). The dashboard has the same thing in Observability → Logs, with a range picker and a search box.

How long logs are kept

Every line your worker prints is kept for 3 days on Free and 7 days on Pro. After that it is deleted and there is no way to get it back. To keep logs longer, export them to your own bucket or collector.

Each project can store 200,000 log events a day on Free and 2,000,000 on Pro, counted on the UTC day. Past that, the live tail keeps working exactly as before, and for the rest of the day one event in a hundred is kept, chosen by request so a request's lines stay together. Counts in the Query Builder are weighted, so they still estimate the real volume. On Pro you can raise or remove the cap; see Caps and cost.

A self-hosted installation without ClickHouse keeps stored lines in PostgreSQL under the same daily figures, and stops storing for the day at the cap with one line saying so:

runlot: daily log cap reached (200000 lines); further lines today are dropped
The live tail and the stored history are two separate things. The tail reads a ring buffer in the node's memory — 2,000 lines or 1 MiB per project, whichever comes first, cleared whenever the node agent restarts. The history lives in the control plane, so it survives restarts, redeploys, and a project moving between nodes.

Dropped-line notices

If the terminal running runlot logs cannot consume the output fast enough, that stream skips some log lines. This keeps a slow log reader from slowing down the project itself.

The number of skipped lines is reported as a notice event in the stream.

-- dropped 128 lines — this log stream could not keep up with the worker

This notice exists so you can tell the difference between "there were no logs" and "logs were skipped in transit".

Using it in a pipe

Stream termination notices and paging hints are written to stderr.

runlot logs --no-follow | grep ERROR

Only the log lines your worker printed are passed to grep through the pipe.

Log entry size limit

Each log entry is at most 64 KiB in the live tail. A collected console.log is cut at 256 KiB and marked truncated.

Logging objects

Log a plain object as the first argument and its fields become queryable keys:

console.log({ event: "checkout", userId: 42, cents: 1999 });

In the Query Builder, userId and cents are filterable and can be grouped or summed like any built-in field.

On this page