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
| title | impact | tags |
|---|---|---|
| Step 3: Correlate Logs | CRITICAL | workflow, logs, correlation, step3 |
Impact: CRITICAL
Pull the logs tied to a trace, then find the earliest error message + stack trace — usually closest to root cause. Lead in code mode (correlate.mts, run npx tsx correlate.mts).
const q = kq.logs
.raw()
.where((f) => f.eq("TraceId", traceId))
.timeRelative("1h")
.build();
const { data } = await client.query(q);
console.log(JSON.stringify(data, null, 2));Or the client helper (an async iterable — iterate it, never await it as an array):
for await (const log of client.searchLogs({ traceId })) { console.log(log); }. For a single page use client.searchLogsPage({ traceId }) → { data, nextCursor }.
Sort by Timestamp ascending — the first record is where the failure surfaced.
Errors are detected by SeverityNumber >= 17 (covers ERROR/FATAL regardless of SeverityText casing — ERROR/error/Error/empty all vary by language). Filters are AND-combined, so add a second .where():
const q = kq.logs
.raw()
.where((f) => f.eq("TraceId", traceId))
.where((f) => f.gte("SeverityNumber", 17))
.timeRelative("1h")
.build();Some apps log real errors at INFO (9–12) — if nothing comes back at >= 17, drop the severity filter and search the Body instead.
const q = kq.logs
.raw()
.where((f) => f.eq("TraceId", traceId))
.where((f) => f.contains("Body", "exception"))
.timeRelative("1h")
.build();f.contains is a case-sensitive substring match. Try "exception", "timeout", "connection refused", or the dotted exception.stacktrace attribute for the stack.
Each .where() adds an AND clause — chain them to narrow the trace's error logs:
const q = kq.logs
.raw()
.where((f) => f.eq("TraceId", traceId))
.where((f) => f.gte("SeverityNumber", 17))
.where((f) => f.contains("Body", "timeout"))
.timeRelative("1h")
.build();Equivalent via the client helper (field is bodyContains, not body). searchLogs is an async iterable — consume it with for await, don't await it as an array:
for await (const log of client.searchLogs({
traceId,
severityNumberMin: 17,
bodyContains: "timeout",
})) {
console.log(log);
}
// single page instead: const { data } = await client.searchLogsPage({ traceId, severityNumberMin: 17, bodyContains: "timeout" });See references/log-filters.md for log columns, the severity scale, and filter ops.
For a single lookup. Note severity uses --severity-min 17 (numeric), not --severity-text ERROR:
npx @kopai/cli logs search --trace-id <traceId> --json
npx @kopai/cli logs search --trace-id <traceId> --severity-min 17 --json
npx @kopai/cli logs search --trace-id <traceId> --body "exception" --json
npx @kopai/cli logs search --trace-id <traceId> --severity-min 17 --body "timeout" --json