Invocations and queries
One record per request, cron run, queue batch or email, a Query Builder over them, saved queries, log patterns, and the view of a single request.
Every invocation of your worker leaves a record: its request ID, time, handler, method, host, path, route, status, wall time, deployment version and outcome. The record is written by Runlot's front, not by your worker, so it is never sampled and it exists even when the process died mid-request. It is the same observation the usage charts count, so the two cannot disagree about how many requests there were.
When contents are collected, the record also carries that invocation's console lines, exceptions and spans.
Outcomes
The outcome names what happened to the handler, in the same vocabulary as Cloudflare Workers:
| Outcome | Meaning |
|---|---|
ok | The handler returned. A handler that returns 500 is still ok; filter on $response.status for that |
exception | The handler threw |
exceededMemory | The process was killed by the project's own memory limit while this request was in flight |
exceededCpu, exceededWallTime | A runtime limit ended it |
canceled | The client went away before an answer |
internalError | The process died on a signal; the record names it |
A process killed for memory takes every request in flight with it, and none of them can report anything. Runlot matches them to the process's exit record afterwards, so each one shows exceededMemory with the limit and the peak.
The Query Builder
Observability → Invocations opens the Query Builder. Pick a view — calculations, invocations, events or traces — then filters, a search string, calculations and group-by keys. The same query is JSON, and the CLI runs it:
{
"timeframe": { "last": "24h" },
"view": "calculations",
"filters": [{ "key": "$response.status", "op": "gte", "value": 500 }],
"calculations": [{ "op": "count" }, { "op": "p99", "key": "$wall_ms" }],
"groupBy": ["$route"],
"orderBy": { "calc": 0, "dir": "desc" },
"limit": 20
}runlot logs query --file q.json # a table
runlot logs query --file q.json --json # the raw result- Built-in keys start with
$:$request.path,$request.method,$response.status,$route,$wall_ms,$outcome,$event_type,$deployment.version,$level,$message,$source,$request_id,$span.name,$duration_ms,$region,$countryand more. Your own keys are bare: a field of an object you logged, such asuserId, ororder.totalfor a nested one. - Operators:
eq,neq,gt,gte,lt,lte,includes,not_includes,starts_with,regex(RE2),exists,not_exists. Filters AND together. - Calculations:
count,count_distinct,min,max,sum,avg,stddev,varianceand percentiles fromp001top999(two digits are percent, three are per mille). - Bounds: 20 filters, 10 calculations, 5 group-by keys, 1,000 rows, and 10 seconds per query. The timeframe is clamped to your plan's retention, and the answer says so when it was.
There is no SQL. The query is compiled into parameterised SQL with your project's filter added by Runlot, so a query can never read another project's data.
Saved queries
Save a query from the Query Builder to give it a URL. A saved query is visible to everyone in the project; one saved with the organization scope is visible from every project of the organization. Each person can star the ones they use. Free keeps 10 saved queries per project; Pro has no limit.
Log patterns
The Patterns tab clusters log lines by template — numbers, IDs and other variable tokens masked — and shows each pattern's count, a sample line, when it was first seen and its trend over the window. A pattern that is new or spiking stands out without a query.
One request
runlot logs request 4bf92f3577b34da6a3ce929d0e0e4736Request 4bf92f3577b34da6a3ce929d0e0e4736
POST my-app.me.runlot.app/checkout → 502 1,204 ms 2026-10-23T03:12:40.118Z
outcome exceededMemory (recorded as workerExited)
route /checkout · v42 · fetch
exit exceededMemory at 2026-10-23T03:12:41.004Z (code -1, SIGKILL, OOM-killed, peak 271 MiB of 256 MiB)
Logs (3)
…
Spans (4)
fetch /checkout |####################| 1,204 ms
db.query SELECT … | #### | 212 ms
…It shows the resolved outcome and the process exit that explains it, the request's logs with stacks, a text waterfall of its spans, the issues it raised in Runlot Errors, and the cron, queue or email history row it belongs to. --json prints the whole record.
The dashboard's view of the same request is at /o/<org>/p/<project>/observability/requests/<id>. The signed-in error page links straight to it.
Over the API
Everything above is the public API, under /v1/orgs/{org}/projects/{project}/observability/: query, keys and values (the facets the Query Builder suggests), patterns, requests/{requestId} and saved-queries. Reads need the viewer role or higher; saving a query needs member. See the API reference.