Skip to content
runlot

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:

OutcomeMeaning
okThe handler returned. A handler that returns 500 is still ok; filter on $response.status for that
exceptionThe handler threw
exceededMemoryThe process was killed by the project's own memory limit while this request was in flight
exceededCpu, exceededWallTimeA runtime limit ended it
canceledThe client went away before an answer
internalErrorThe 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:

q.json
{
  "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, $country and more. Your own keys are bare: a field of an object you logged, such as userId, or order.total for 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, variance and percentiles from p001 to p999 (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 4bf92f3577b34da6a3ce929d0e0e4736
Request 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.

On this page