Observability¶
Sluice writes logs, serves Prometheus metrics and sends traces. The record of every query is the audit log.
Example¶
Goal: find out why a query failed.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_analyst.supersecret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT CAST(name AS INTEGER) FROM shop.main.customers WHERE id = 1"}'
{
"code": "ERR_QUERY_FAILED",
"message": "the database rejected the query",
"query_id": "<query-id>"
}
Search the log for this error:
time=<time> level=WARN msg="query rejected by duckdb" query_id=<id> error="executor: query execution failed: executor: statement rejected by duckdb: Conversion Error: Could not convert string 'Alice' to INT32 when casting from source column name\n\nLINE 1: SELECT CAST(name AS INTEGER) FROM shop.main.customers WHERE id = 1\n ^"
What happened: the client gets an error code and a query_id. The log line with this query_id holds the cause from DuckDB.
Logs¶
Sluice writes one line per event to stderr.
logging:
level: "info"
# The default is "json". The text format is easier to read in a terminal.
format: "text"
logging.levelisdebug,info,warnorerror.logging.formatisjsonortext.- Sluice accepts an unknown value. It then uses
infoorjson. ERR_QUERY_FAILEDlogs aWARNline.ERR_INTERNALlogs anERRORline with the messageinternal error.- These lines are not redacted. They can hold SQL and cell values, like
'Alice'above. logging.maxErrorBytescuts a longerrortext. The default is 4096, and0turns the cut off.
Metrics¶
The admin listener serves GET /metrics. It needs admin.enabled: true and the admin token.
curl -s http://localhost:9091/metrics \
-H "Authorization: Bearer demo-admin-token-change-me" | grep "^sluice_audit"
sluice_audit_enqueued_total{event_type="query"} 2
sluice_audit_enqueued_total{event_type="query-result"} 1
sluice_audit_queue_depth{sink="file"} 0
Metrics lists every metric. Two alerts are useful:
sluice_audit_dropped_totalabove 0: Sluice lost audit records. Thereasonlabel isqueue_full,context,buffer_fullorwrite_error.sluice_audit_queue_depthclose to 10000: the audit queue is almost full.
Tracing¶
Add a tracing block to the server config. Sluice then sends spans to an OpenTelemetry (OTLP) collector. A span is one timed step of a request.
protocolisgrpcorhttp.insecure: truesends without TLS.- Sluice continues the trace of a W3C
traceparentrequest header. - Every REST request and every MCP request over HTTP gets a span.
- A query adds the span
querywith the childrenquery.parse,query.policy,query.rewriteandquery.execute. A post-query column mask addsquery.mask. - The
queryspan holds the query id, origin, decision, SQL fingerprint, catalogs and error code, never the SQL text. It ends before Sluice sends the rows.