From the terminal
up0 tail's filters and output modes, and up0 metrics ls / query, with real output.
From the terminal
Two command groups cover the same telemetry Explore shows, from a shell:
up0 tail streams logs or traces, and up0 metrics lists and charts
metrics. Both require an organization to be selected: set org_id on the
active profile, or pass --org-id / set UP0_ORG_ID on each call.
A different, smaller query language
--query on both commands is sent to the server as q and compiled with
the backend's Lucene-subset grammar. It is not the select ... from ... where ... language the console's search bar accepts. See
Query language
for how the two relate.
up0 tail
up0 tail short-polls the Query API's tail endpoint and streams new logs or
traces to the terminal. It follows and stays open when stdout is a terminal,
and makes a single bounded pass when piped, redirected, or run with
--json.
Stream logs
up0 tail --type logs --service payments-api --status error --query 'timeout' --since 15mStream traces
up0 tail --type traces --service payments-api --status error --query 'checkout' --since 15m--status targets a different column per --type: log severity
(severity_text, case-insensitive) under --type logs, trace status
(status_code) under --type traces.
Real output, up0 tail --type traces --since 1h --no-follow against a
handful of seeded spans:
1647-gateway GET /checkout 40000000ns Ok 7b6a35f3749d47ffbc716ee6607e62ae
1647-orders POST /orders 30000000ns Ok 7b6a35f3749d47ffbc716ee6607e62ae
1647-gateway GET /checkout 40000000ns Ok 1c654aedae1645af9439509d40032ee7
1647-orders POST /orders 30000000ns Ok 1c654aedae1645af9439509d40032ee7
1647-lonely-worker cron.tick 5000000ns Ok 2a60ca6f82f3432abe92683ac8b6691b
... 3 more matching records this interval, showing latest 20 — narrow with --status/--service or increase your terminal's scrollbackEach trace line is service span_name duration status trace_id; each log
line is timestamp severity service body.
--query / -q is sent to the server as q. The server compiles it with a
Lucene-subset grammar (bare words, *substr*, "exact phrase",
key:value, key:>N comparisons, AND/OR/NOT), the same grammar the
Query API applies to its list, histogram, and tail endpoints alike. The CLI
does no parsing of its own; it passes the string through as-is.
Parenthesised grouping and a raw-SQL escape hatch are not implemented yet.
--view
--view <name> resolves a named DataView, a saved query with a display
payload set, created from the console (see
Saved queries and history), and applies its
query text and time range as defaults. Any of --query/--since you also
pass on the command line wins over the view's own value; the view only fills
in what you left unset. The view's display half (column set, widths, sort)
is looked at only to confirm the row is a DataView and is never applied.
The CLI streams records, it does not render columns:
up0 tail --view checkout-errorsA name that matches an ordinary saved search, not a DataView, is not resolved. Only a saved query with a presentation payload counts.
Options
--type:logsortraces(default:logs)--query/-q: free-text filter, sent to the server asq--view: resolve a named DataView and apply its query/range as defaults--since: initial lookback, e.g.5m,1h,24h,7d(default:5m)--service: filter byservice_name--status: filter byseverity_text(--type logs) orstatus_code(--type traces)--json: newline-delimited JSON, one record per line--follow/-f/--no-follow: keep polling after the first batch; on by default when writing to a terminal, off when piped, redirected, or--json--limit/-n: max records rendered per poll (default:20)--org-id: organization ID (or setUP0_ORG_ID)
tail warns on stderr rather than silently dropping records. If a poll hits
the result limit, narrowing with --service/--status lets it catch up. If
the follow loop fell behind the server's lookback window, records in that
range were skipped and cannot be recovered through tail. Narrowing helps
only when the cause is sustained volume, not when the poll itself was paused
by a sleeping machine or a network outage.
All warnings and empty-result notes go to stderr, so stdout carries only
record data and pipelines like up0 tail ... | grep ERROR see a clean
stream.
up0 logs and up0 traces are retired. Use up0 tail --type logs and up0 tail --type traces instead.
up0 metrics
up0 metrics has two subcommands: ls answers what metrics do I have, and
query returns one metric as a bucketed time series.
List what you have
Start here. Metric names come from your instrumentation, not from upzero, so nothing can guess them for you.
up0 metrics ls --since 1h┏━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ metric_name ┃ metric_type ┃ series_count ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ up0_ingest_test_6043b69e │ gauge │ 1 │
│ up0_ingest_test_62fd128a │ gauge │ 1 │
└──────────────────────────┴─────────────┴──────────────┘Only metrics with data in the range are listed, so a metric that stopped
reporting yesterday will not appear under --since 1h. Widen the range
before concluding it is gone.
series_count is the number of distinct label combinations, the number to
watch for cardinality: one metric carrying a request id or user id in a
label can reach thousands, and the query API will refuse to compute over it
rather than exhaust memory (see Cardinality limits).
Options:
--since: relative lookback, e.g.15m,1h,24h,7d(default: last1h)--start/--end: ISO 8601 range, mutually exclusive with--since--json: emit the API response as JSON--org-id: organization ID (or setUP0_ORG_ID)
Query one metric
up0 metrics query --metric-name example.queue.depth --since 1h --aggregation avgexample.queue.depth aggregation=avg type=gauge bucket=72s
2026-08-14T06:00:00 2026-08-14T06:01:12 76.5
2026-08-14T06:01:12 2026-08-14T06:02:24 77.5
...
scanned 1993 rows in 0.0122sBucket width is derived from the range, roughly 50 buckets across whatever you ask for, so you control the range and never the width.
Split into one series per label value with --group-by, repeatable up to 10
keys:
up0 metrics query --metric-name example.queue.depth --group-by route --since 1hThe aggregation has to match the encoding.
metric_type from ls decides which aggregations can return anything. The
full table is in Metrics.
The short version: sum/avg/min/max/count need a gauge or
counter; a histogram-shaped metric (histogram, exponential_histogram,
summary) returns null for all five and needs quantile instead:
up0 metrics query --metric-name example.http.duration \
--aggregation quantile --quantile 0.95 --since 1h--quantile is a fraction in [0, 1], not a percentile: 0.95, not
95. It is required with --aggregation quantile and rejected otherwise.
Counters are not rate-corrected. A counter's raw value is its cumulative
total, so charting one shows growth, not throughput. And an empty bucket is
null, not 0, except for sum/count, where zero is a real answer.
Options:
--metric-name: exact metric name, required, one metric per request--since: relative lookback (default: last1h)--start/--end: ISO 8601 range, mutually exclusive with--since--aggregation:sum,avg,min,max,count,quantile(default:avg)--quantile: fraction in[0, 1], required iff--aggregation quantile--group-by: label key to split into its own series, repeatable, up to 10--json: emit the API response as JSON--org-id: organization ID (or setUP0_ORG_ID)
Cardinality limits
A metric whose labels carry an unbounded value (a request id, a user id, a
pod name during a rolling deploy) can produce a practically unlimited
number of series. Rather than let one such metric exhaust memory for
everyone, the query API aborts and returns a 400 naming the problem:
INVALID_QUERY_PARAMS: this query has too many distinct label combinations to
compute exactly in the requested range. Narrow the range, or check whether a
label on this metric carries a request id, user id, or other
effectively-unbounded value.Narrowing the range is the quick fix. The real fix is at the emitting
instrumentation. up0 metrics ls sorts busiest-first, so the offender is
usually the top row.
The same data in Explore
The console's Explore surface answers the same questions from the same endpoints, so the terminal and the browser never disagree:
| Terminal | Explore equivalent |
|---|---|
up0 tail --type logs | Logs, with Live tail on |
up0 tail --type traces | Traces, with Live tail on |
up0 metrics ls | select * from metrics (the catalog question) |
up0 metrics query --aggregation avg | select avg('<metric>') from metrics |
See Query language for the console's full
grammar, and Saved queries and history for
--view's counterpart on the console side.