Instrument applications with the OpenTelemetry SDK and prove the telemetry is good by validating it against a local Kopai backend. Use when setting up observability, adding tracing/logging/metrics, deciding what to instrument or which attributes to add, retrofitting OTel into an existing codebase, threading context through call chains, configuring sampling, or when traces/logs/metrics aren't appearing after setup. Also use when users say things like "my traces aren't showing up", "I don't see any data", or "how do I add observability to my app". Do NOT use to investigate existing telemetry for a root cause (use root-cause-analysis), to build dashboards (use create-dashboard), or to instrument LLM and agent calls (use otel-genai-instrumentation).
—
—
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
| title | impact | tags |
|---|---|---|
| Missing Spans | HIGH | troubleshoot, spans, orphan |
Some telemetry arrives, but not the spans you expected. Diagnose by symptom.
If nothing arrives, start at troubleshoot-no-data.md instead.
Context isn't propagating. This is the most common failure in the whole skill, and the code runs perfectly the entire time it's happening.
npx @kopai/cli traces search --resource-attr "validation.run_id=$RUN_ID" --json \
| jq -r '.[] | select((.ParentSpanId // "") == "") | .SpanName' | sort | uniq -c | sort -rnEvery name here that isn't an entry point is a break. Go to context-propagation.md —
specifically the framework accessor table, since using the wrong context object compiles
cleanly and silently detaches everything downstream.
Auto-instrumentation for that library isn't installed or isn't hooked.
npm ls @opentelemetry/auto-instrumentations-node # Node.js
pip list | grep opentelemetry-instrumentation # Python
go list -m all | grep otelsql # Go — per-library wrappersGo is the usual culprit: it has no blanket auto-instrumentation, so each library needs
its own wrapper (otelhttp, otelsql, otelgorm) applied explicitly. Nothing warns you
when one is missing.
Also confirm the instrumented client is the one the code actually calls. Wrapping a
*sql.DB you then never use is easy to do and invisible.
Middleware ordering. OTel middleware registered after the router — or on a sub-router that some paths bypass — never wraps those handlers.
Register it first and outermost. Then re-drive that specific route and re-check assertion A5.
unknown_service or genericThe resource isn't configured. echo $OTEL_SERVICE_NAME in the shell that launched the
app; if it's set there but not in the telemetry, the SDK is being configured
programmatically with a resource that overrides the environment.
Nothing auto-instruments your own functions — that's the point of instrument-spans.md.
Decide which operations are interesting and aggregable, then add spans there.
Sampling, or a queue overflow. Check OTEL_TRACES_SAMPLER (sampling.md), then raise
OTEL_BSP_MAX_QUEUE_SIZE — the batch processor drops spans silently when its queue is
full, which looks exactly like sampling.
Initialisation ordering. The SDK must start before the libraries it patches are
imported. In Node.js this means --import/--require, not a require() at the top of
server.mjs — by then the modules are already loaded and unpatchable.
Two more causes with the identical symptom — patching that no-ops without an error:
"type": "module" doesn't disable patching wholesale:
CommonJS dependencies still load through the require hook and patch fine. A
dependency published as native ESM bypasses that hook and needs OTel's loader hook
registered before the app loads:
--experimental-loader=@opentelemetry/instrumentation/hook.mjs. Diagnose per
dependency, by its packaging — and if the framework ships a native plugin, prefer it
over loader mechanics entirely (Fastify → lang-fastify.md).npm ls <pkg> (pnpm list <pkg> / yarn why <pkg>) — then
npm view <pkg>@<version> deprecated; a bare npm view <pkg> deprecated reads the
latest release, which may differ. npm view works in any project regardless of its
package manager — it is a registry read, and npm ships with Node. The message
is free text: it usually names the replacement; when it doesn't, check the package's
migration notes. Either way, spend the time migrating (after a compatibility check),
not debugging the abandoned package. Known case:
@opentelemetry/instrumentation-fastify → @fastify/otel.Re-drive (drive-traffic.md) with a fresh $RUN_ID and re-run all of
validate-traces.md. Fixing propagation frequently reveals a second break behind the first.
references