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
97%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
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).
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).
| Column | Notes |
|---|---|
Body | Log message content |
SeverityNumber | Numeric severity (use this for error detection) — see scale below |
SeverityText | Free-form level string; varies by language (ERROR/error/Error/empty) |
TraceId, SpanId | Correlate to a trace/span |
Timestamp | Event time |
EventName, TraceFlags, ScopeName, ScopeVersion | Record/scope metadata |
No
ServiceNamestructural column — use the dotted"service.name"attribute.
Filter errors by SeverityNumber >= 17 rather than SeverityText. SeverityNumber
is standardized by the OTel Log Data Model; SeverityText is free-form and inconsistent.
| Level | Number | |
|---|---|---|
| TRACE | 1–4 | |
| DEBUG | 5–8 | |
| INFO | 9–12 | Some apps log real errors here — fall back to a Body search |
| WARN | 13–16 | |
| ERROR | 17–20 | f.gte("SeverityNumber", 17) |
| FATAL | 21–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();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, …).
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();| Filter | Flag | Example |
|---|---|---|
| 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 }.