Requires the KubeSense MCP server; see kubesense-mcp for connection
and auth. Tools here resolve RBAC against the logs module.
SPL is logs only. There is no SPL for traces or metrics — use kubesense-traces and kubesense-metrics.
Tools
| Tool | Use when | Returns |
|---|---|---|
get-trace-or-log-fields (signal: "logs") |
To find dynamic attribute keys — not column names, see below | Field catalog for your window |
validate-spl |
Before handing any query over | valid + the ClickHouse SQL it compiles to |
execute-spl |
To answer a question yourself | Rows (TSV), capped at 1000 |
Validate before you execute. SPL's parser accepts an unknown field and an unknown function alike,
so a wrong name compiles and fails at run time — after the scan has already been paid for.
validate-spl costs no scan, so the pair is cheap in that order and wasteful in the other.
execute-spl needs clusters and a time window; validate-spl needs neither. Prefer search-logs
or analyze-logs when they can express the question — they enforce namespace- and workload-level
access rules the SPL endpoints cannot, which is also why both SPL tools are refused outright for a
scope-restricted role.
Field Names Are the Opposite of Everywhere Else
Every other KubeSense surface — search-logs, analyze-logs, the SQL endpoints, the field catalog —
takes catalog labels and rejects storage column names. SPL does the reverse.
| SPL uses | Every other tool uses |
|---|---|
level |
type |
pod_name |
instance |
cluster |
domain |
container_name (or container) |
container |
node_name (or node) |
node |
host |
— a different column from node_name; both exist |
The complete set SPL knows: timestamp (or @timestamp) · body (or @message) · level ·
cluster · namespace · workload · pod_name (or @logstream) · container_name (or
container) · node_name (or node) · host · service · source · format · env_type ·
raw · @loggroup (= workload).
[!WARNING] SPL has no
region,app_version,instance,type,domain,body_lengthorkuid. A name SPL does not know is not an error — it is passed through to ClickHouse as a bare column name, so the query parses, compiles, and fails only when someone runs it.
[!WARNING]
get-trace-or-log-fieldsreturns the wrong names for SPL. It reports catalog labels —type,instance,domain— which SPL does not accept. Use it only to discover dynamic attribute keys, and address those aslog_processed.<key>. For columns, use the table above.
Dynamic attributes
Parsed JSON fields are reached with the log_processed. prefix — SPL's equivalent of the SQL
endpoints' @attr:
filter log_processed.status = "500"
| stats count(*) as errors by log_processed.url
Resolution is string attributes → float attributes → JSON-parsed body.
The Pipeline
Commands separated by |, read left to right.
"connection refused"
| filter level = "ERROR" AND namespace = "production"
| stats count(*) as errors by workload
| sort errors desc
| head 20
Commands: fields · filter (aliases where, search) · parse · rex · eval · stats ·
timechart · dedup · head · tail · sort · limit · rename · table · top · rare.
A bare quoted string is a full-text search on the message body and must come first. # starts a
comment.
Order is semantic, not cosmetic. A filter before stats becomes a WHERE; the same filter
after stats becomes a HAVING over the aggregate:
filter level = "ERROR" | stats count(*) as n by workload | filter n > 100
Aggregations for stats / timechart: count(*) · sum · avg · min · max · dc
(distinct count) · median · mode · range · stdev · stdevp · var · sumsq · latest ·
earliest · values · list · perc<N>.
[!WARNING] Percentiles are
perc95(x), neverp95(x).p95is not a function. Like an unknown field it compiles and fails at run time — and KubeSense's own SPL manual taught the wrong form for a while, so it appears in older examples.
timechart takes span=<N><s|m|h|d> and names its bucket column _time:
timechart span=5m count(*) as errors by workload
The Server Owns the Time Window
The time range and cluster filter are applied by the server, from the request — not from the
query. Write no timestamp condition. This is the inverse of the SQL endpoints, where
$__timeFilter(timestamp) is mandatory.
Always Validate
validate-spl is not a formality here, because SPL's parser accepts more than ClickHouse will run:
| Mistake | Parses? | Compiles? | Caught by |
|---|---|---|---|
filter level = (syntax) |
✗ | — | parse |
filter region = "x" (unknown field) |
✓ | ✓ | only validate-spl |
stats p95(x) (unknown function) |
✓ | ✓ | only validate-spl |
log_processed.typo |
✓ | ✓ | nothing — returns nulls |
validate-spl resolves every identifier and function against the real log table without scanning a
row, and returns the compiled SQL under ## translated_sql. Read it — it is how you confirm
pod_name resolved to the column you meant.
Attribute keys are the one thing it cannot check: a wrong log_processed.<key> returns nulls
silently, exactly as a wrong @attr does on the SQL side. Confirm keys with
get-trace-or-log-fields first.
Limits
- 1000 rows unless the pipeline ends with
| head N,| tail Nor| limit N. - Read-only; filesystem, network and system functions are rejected.
- Unavailable to roles scoped to specific clusters, namespaces or workloads — the SPL endpoints apply
a cluster filter only and cannot enforce finer rules. Use
search-logs/analyze-logs, which do.
Worked Examples
Error count per workload, worst first
filter level = "ERROR"
| stats count(*) as errors by workload
| sort errors desc
Find the timeouts and read them
"timeout"
| filter workload = "checkout"
| fields timestamp, pod_name, body
| head 50
Error rate over time, split by namespace
filter level = "ERROR"
| timechart span=5m count(*) as errors by namespace
Latency percentiles from a JSON field
filter log_processed.duration_ms > 0
| stats perc95(log_processed.duration_ms) as p95, avg(log_processed.duration_ms) as mean by log_processed.route
| sort p95 desc
Extract fields from unstructured lines
"APICallLogs:"
| parse 'Path:* Method:* StatusCode:*' as path, method, code
| filter code != "200"
| stats count(*) as errors by path, code
Rules
- Storage names, never catalog labels:
levelnottype,pod_namenotinstance,clusternotdomain. get-trace-or-log-fieldsis for attribute keys only — its column names are wrong for SPL.- Call
validate-splbefore handing a query over or running it, and read thetranslated_sqlit returns. perc95(x), neverp95(x).- Write no time or cluster filter — the server applies both.
- A bare quoted search string goes first in the pipeline.
filterbeforestatsis a WHERE; afterstatsit is a HAVING.- Aggregate in SPL; do not ask for raw rows to count them. 1000-row cap.
log_processed.<key>for parsed JSON fields — and a wrong key returns nulls, not an error.- Logs only. Traces and metrics have no SPL.