CtrlK
BlogDocsLog inGet started
Tessl Logo

kopai/root-cause-analysis

Investigate and root-cause production issues from live OpenTelemetry telemetry (traces, logs, metrics) with the Kopai SDK in TypeScript code mode. Use this skill to debug errors and error-rate spikes, investigate latency/slowness and timeouts, trace requests across services, find failing or cascading services, and correlate logs to a trace — including vague symptom reports like 'why is my API slow', 'getting 500 errors', 'service is down', 'requests are timing out', or 'something is failing in prod', even when the user never says 'traces' or 'observability'. This analyzes existing telemetry to find a cause. Do NOT use it to add instrumentation (use otel-instrumentation), to build dashboards or visualizations (use create-dashboard), or to fix non-telemetry problems like failing CI, lint errors, unit tests, or SQL query tuning.

77

Quality

97%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

log-filters.mdreferences/

Log query reference

Columns, filters, and measures for kq.logs.aggregate() / kq.logs.raw() (and the client.searchLogs(...) filter shape). Run a built query with client.query(q) — it is fully typed (const { data } = await client.query(q) for aggregate; { data, nextCursor } for raw).

How columns work

Same rules as traces: PascalCase = structural field, dotted = semantic-convention attribute (auto-resolved), { container, key } = explicit. Log containers: LogAttributes (record-specific), ResourceAttributes (process/host/service), ScopeAttributes (instrumentation scope).

Structural columns

ColumnNotes
BodyLog message content
SeverityNumberNumeric severity (use this for error detection) — see scale below
SeverityTextFree-form level string; varies by language (ERROR/error/Error/empty)
TraceId, SpanIdCorrelate to a trace/span
TimestampEvent time
EventName, TraceFlags, ScopeName, ScopeVersionRecord/scope metadata

No ServiceName structural column — use the dotted "service.name" attribute.

Severity model (canonical error detector)

Filter errors by SeverityNumber >= 17 rather than SeverityText. SeverityNumber is standardized by the OTel Log Data Model; SeverityText is free-form and inconsistent.

LevelNumber
TRACE1–4
DEBUG5–8
INFO9–12Some apps log real errors here — fall back to a Body search
WARN13–16
ERROR17–20f.gte("SeverityNumber", 17)
FATAL21–24
kq.logs
  .raw()
  .where((f) => f.gte("SeverityNumber", 17))
  .timeRelative("1h")
  .limit(50)
  .build();
kq.logs
  .raw()
  .where((f) => f.eq("TraceId", traceId))
  .timeRelative("1h")
  .build(); // correlate
kq.logs
  .raw()
  .where((f) => f.contains("Body", "connection refused"))
  .timeRelative("1h")
  .build();

Common semantic-convention attributes (dotted)

log.level, log.file.path, exception.type, exception.message, exception.stacktrace, error.type, code.function.name, code.file.path, http.request.method, http.response.status_code, http.route, url.path, client.address, plus the resource set (service.name, k8s.pod.name, …).

Filter & measure ops

Filters: same DSL as traces (eq/neq/contains/startsWith/in/gt/gte/lt/lte/isNull/and/or). Aggregate measures: count, countDistinct, sum/avg/min/max, rate*. (No errorRate/ throughput — those are trace-only.) Every query needs a time window + .summary()/.timeSeries().

// error volume by service + severity over time
kq.logs
  .aggregate()
  .measure((m) => m.count("n"))
  .dimension("service.name")
  .dimension("SeverityText")
  .where((f) => f.gte("SeverityNumber", 17))
  .timeRelative("3h")
  .timeSeries("5m")
  .orderByMeasure("n", "desc")
  .build();

CLI fallback (quick lookups)

FilterFlagExample
Service--service--service payment
Severity min/max--severity-min / --severity-max--severity-min 17
Body search--body--body "connection refused"
Trace correlation--trace-id--trace-id abc123
Log attribute--log-attr--log-attr "error.type=timeout"

client.searchLogs({ serviceName, severityNumberMin, bodyContains, traceId, logAttributes, limit }) mirrors these (field is bodyContains, not body). searchLogs is an async iterable — use for await (const log of client.searchLogs({…})) {…}, don't await it as an array; for one page use searchLogsPage{ data, nextCursor }.

references

log-filters.md

metric-filters.md

trace-filters.md

SKILL.md

tile.json