mcpField
K
kubesense-spl

kubesense-spl

github.com/kubesense-aiCloud & DevOps365 installsUpdated 3d ago
Repository

About kubesense-spl

kubesense-spl is a skill for querying KubeSense logs with SPL. It guides users through validating queries before execution and explains field naming, pipeline semantics, and access limitations.

Key features

  1. 01Validate SPL queries and inspect compiled SQL
  2. 02Run read-only log queries with a 1000-row cap
  3. 03Use pipeline commands for filtering and aggregation
  4. 04Query parsed JSON attributes with log_processed
  5. 05Understand field names and role-based limits

Use cases

  1. 01Count errors grouped by workload
  2. 02Find matching log messages in a time window
  3. 03Chart log counts over time by namespace

MCP support

Requires the KubeSense MCP server. It uses the server’s log-related tools to validate and execute SPL queries.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/kubesense-ai/kubesense-mcp-skills --skill kubesense-spl

After installing, type/kubesense-splin Claude Code to confirm it loaded.

Details

From the project README

KubeSense SPL

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

ToolUse whenReturns
get-trace-or-log-fields (signal: "logs")To find dynamic attribute keys — not column names, see belowField catalog for your window
validate-splBefore handing any query overvalid + the ClickHouse SQL it compiles to
execute-splTo answer a question yourselfRows (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 usesEvery other tool uses
leveltype
pod_nameinstance
clusterdomain
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_length or kuid. 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-fields returns 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 as log_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), never p95(x). p95 is 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:

MistakeParses?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 N or | 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

  1. Storage names, never catalog labels: level not type, pod_name not instance, cluster not domain.
  2. get-trace-or-log-fields is for attribute keys only — its column names are wrong for SPL.
  3. Call validate-spl before handing a query over or running it, and read the translated_sql it returns.
  4. perc95(x), never p95(x).
  5. Write no time or cluster filter — the server applies both.
  6. A bare quoted search string goes first in the pipeline.
  7. filter before stats is a WHERE; after stats it is a HAVING.
  8. Aggregate in SPL; do not ask for raw rows to count them. 1000-row cap.
  9. log_processed.<key> for parsed JSON fields — and a wrong key returns nulls, not an error.
  10. Logs only. Traces and metrics have no SPL.

Comments

Markdown supported