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 |
|---|---|---|
| Pattern: Log-Driven Investigation | HIGH | pattern, logs, investigation |
Impact: HIGH
Start from log entries when you don't have a trace ID. Detect errors by
SeverityNumber >= 17 (ERROR/FATAL) — SeverityText is free-form and varies by
language (ERROR/error/Error/empty), so never filter on it.
// log-driven.mts — run: npx tsx log-driven.mts
import { kq, KopaiQueryBuildError } from "@kopai/sdk";
import { clientFromConfig } from "@kopai/sdk/node";
const client = clientFromConfig();
try {
// 1. Rank error volume by service + severity (count errors server-side)
const volume = kq.logs
.aggregate()
.measure((m) => m.count("n"))
.dimension("SeverityText")
.dimension("service.name")
.where((f) => f.gte("SeverityNumber", 17))
.timeRelative("1h")
.summary()
.orderByMeasure("n", "desc")
.build();
console.log(JSON.stringify((await client.query(volume)).data, null, 2));
// 2. Drill into the actual error rows to read messages + grab a TraceId
const rows = kq.logs
.raw()
.where((f) => f.gte("SeverityNumber", 17))
.timeRelative("1h")
.limit(20)
.build();
const { data: logs } = await client.query(rows);
console.log(JSON.stringify(logs, null, 2));
// 3. Bridge to traces: TraceId is the join key from a log to its full trace
const traceId = logs.find((r) => r.TraceId)?.TraceId;
if (traceId) {
const spans = await client.getTrace(traceId);
console.log(JSON.stringify(spans, null, 2));
}
} catch (e) {
if (e instanceof KopaiQueryBuildError) console.error(e.issues);
else throw e;
}Fallback when errors hide at INFO/no severity — some apps log real failures below 17. Search the message body instead: kq.logs.raw().where(f => f.contains("Body", "error")).timeRelative("1h").limit(20).build().
// By service
kq.logs
.raw()
.where((f) => f.eq("service.name", "payment-api"))
.where((f) => f.gte("SeverityNumber", 17))
.timeRelative("1h")
.limit(20)
.build();
// By body content
kq.logs
.raw()
.where((f) => f.contains("Body", "connection refused"))
.timeRelative("1h")
.limit(20)
.build();
// By custom attribute (dotted = auto-resolved semconv)
kq.logs
.raw()
.where((f) => f.eq("error.type", "timeout"))
.timeRelative("1h")
.limit(20)
.build();| Field | Purpose |
|---|---|
| TraceId | Bridge to traces — client.getTrace(TraceId) for the full span chain |
| SpanId | Specific span context within the trace |
| SeverityText | Log level (display only — detect via SeverityNumber >= 17) |
| Body | Log message content (f.contains("Body", "…")) |
TraceId is the bridge. Once a log row yields a TraceId, hop to the trace
(client.getTrace) to get the full span chain, durations, and the call hierarchy
around the failure. No ServiceName column — use the dotted "service.name" attribute.
No aggregation in the CLI; use it for a single lookup.
npx @kopai/cli logs search --severity-min 17 --limit 20 --json
npx @kopai/cli logs search --body "connection refused" --json
npx @kopai/cli logs search --log-attr "error.type=timeout" --json
npx @kopai/cli traces get <traceId> --json