ProjectOperations CLI · 2026
CubeAPM CLI
Command-line investigation of distributed traces, PromQL metrics and LogsQL logs in CubeAPM. A single Go binary for incident work without leaving the terminal.
- Go
- CubeAPM API
- PromQL
- LogsQL
cubeapm traces search --service payments --status error4 traces, slowest 2.9s, op POST /v1/charges cubeapm metrics query 'sum by (service) (rate(errors[5m]))'payments 0.42 api-gateway 0.01 cubeapm logs query 'error' --service payments --last 30m17:02:11 upstream timeout after 2000msOperations CLI · Project
CubeAPM CLI queries traces, metrics and logs from one terminal session. CubeAPM speaks three compatible dialects: Jaeger for traces, Prometheus for metrics, VictoriaLogs for logs. This binary puts all three behind one command grammar. Before it, an incident meant three browser panels and a lot of copying timestamps between them.
The signals are the same incident seen three ways, but the tools treat them as three products. The cost is not the query, it is the alignment. You narrow a trace search to the window you just read off a chart, then ask the log store about that same window. Doing it by hand is slow, and a chart is not something an agent can read at all.
Three ports, one binary
CubeAPM does not serve everything on one port. Reads go to 3140, ingestion to 3130, and log deletion to the admin port 3199. A profile holds the server address and all three, and each is overridable by flag or environment variable. Authentication is Ory Kratos. cubeapm login drives the browser flow endpoints, submits the credentials, caches the session cookie, then verifies the connection against the metrics labels endpoint before saving anything. If a later request returns 401 and the profile still holds credentials, the client signs in again, retries once and persists the refreshed session. Profiles sit in ~/.config/cubeapm-cli/config.yaml at mode 0600.
The command groups
| Group | Commands |
|---|---|
| Traces | search, get, services, operations, dependencies, callers |
| Metrics | query, query-range, labels, label-values, series |
| Logs | query, hits, stats, streams, field-names, field-values, delete |
| Ingest and config | ingest metrics, ingest logs, config set, config profiles |
Output shape follows the upstream API rather than a house format. traces get renders a span waterfall in table mode and returns the full Jaeger trace under -o json. metrics query hands back the Prometheus response untouched, so jq '.data.result' behaves as it would against Prometheus itself. logs query streams instead of buffering, emitting one JSON object at a time.
Choices and their cost
- I did not put a query language on top of the existing ones. Metrics stay PromQL and logs stay LogsQL, so anything already written for either keeps working. The cost is that the two halves of an investigation still read differently, and nothing translates between them.
- Time is one grammar everywhere.
--last 30mworks on traces, metrics and logs, and--fromand--toaccept RFC3339, Unix seconds, Unix milliseconds or a relative value like-2h. A timestamp with no timezone is parsed as local time. Reading it as UTC used to shift narrow windows by the offset, and a window that misses everything looks exactly like an outage with no data. - Log deletion runs as a task, not a command that blocks.
logs delete runstarts it,listreports on it andstopends it. Deletion is irreversible, so the documented order is to preview the exact filter withlogs queryfirst. --read-onlyis honest about its holes. It blocks ingestion and starting or stopping deletion, butlogin, config writes, profile switching andupdatestill run. It guards the API surface, not the filesystem. Covering the local writes too is the change I would make next.
Walking an incident
cubeapm traces search --service payments --status error --min-duration 500ms --last 2h
cubeapm traces get 4f2a91c0e3 -o json | jq '.data[0].spans | length'
cubeapm metrics query-range 'sum by (service) (rate(errors_total[5m]))' --last 2h --step 1m
cubeapm logs query 'error' --service payments --last 30m -o json | jq -c '{t:._time, m:._msg}'
cubeapm logs field-names --last 30m
cubeapm traces dependencies --last 24h --dot > deps.dot
The window text is identical in every line, which is the entire point. Start from failing spans, open the slowest one, check whether the error rate moved at the same moment, then read the logs for that service and that half hour. field-names exists for a schema I have not seen before, since guessing field names is how a log query returns nothing and teaches nothing. The last line pipes into Graphviz when the question is which service calls which.
Where it stops
It reads what CubeAPM already stores. It draws no dashboards and replaces none. --verbose prints HTTP diagnostics to stdout rather than stderr, so it cannot be combined with a parser. A server address with no scheme is assumed to be HTTPS by the interactive login and HTTP by the general client, which is worth knowing before debugging a connection. A Kratos profile stores the password so it can renew an expired session, so environment variables are the documented alternative. It is MIT licensed, builds with Go 1.26 or later, ships one binary for macOS, Linux and Windows, and Reckon drives it during an RCA.
Next projectOperations CLI