CtrlK
BlogDocsLog inGet started
Tessl Logo

kopai/otel-instrumentation

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).

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

validate-logs.mdreferences/

titleimpacttags
Validate LogsHIGHvalidate, logs, correlation

Validate Logs

A log that isn't correlated to a trace is a log you'll read in isolation at 3am. The point of routing logs through OTel is that each record carries the trace context of the work that produced it. These assertions prove that it does.

Run these only if the service emits logs through OTel. If it doesn't yet, that is a gap worth raising — see layered-telemetry.md for when logs earn their place.

L1 — Logs arrived for this run

npx @kopai/cli logs search --service "$OTEL_SERVICE_NAME" --json | jq 'length'

Pass: greater than zero. Fail: the log bridge isn't wired. The SDK log exporter is separate from the trace exporter in most languages — installing tracing does not give you logs. See references/lang-<language>.md.

L2 — Logs carry trace context

Take a trace ID from the run and ask for its logs:

TRACE_ID=$(npx @kopai/cli traces search --resource-attr "validation.run_id=$RUN_ID" \
  --limit 1 --json | jq -r '.[0].TraceId')

npx @kopai/cli logs search --trace-id "$TRACE_ID" --json | jq 'length'

Pass: greater than zero. Logs emitted during a request resolve back to that request. Fail: the log records were created outside an active span context, or the bridge isn't injecting context. Two usual causes:

  • The logger is called before the SDK initialises, or on a thread the context didn't reach — see context-propagation.md
  • The bridge is installed but the app still writes through the raw logger it bypassed

L3 — Severity survives the bridge

npx @kopai/cli logs search --service "$OTEL_SERVICE_NAME" --severity-text ERROR --json \
  | jq 'length'

Pass: matches the failures you drove in drive-traffic.md. Fail: severity is being flattened. Every record arriving as INFO means the bridge isn't mapping your library's levels onto OTel severity numbers, so you can never filter for real problems.

L4 — Failures actually say what failed

npx @kopai/cli logs search --service "$OTEL_SERVICE_NAME" --severity-text ERROR --json \
  | jq -r '.[] | .Body' | head -20

Pass: each body identifies the operation and the reason. Structured fields beat interpolated strings — {"op":"charge","err":"card_declined"} is queryable, "charge failed: card_declined" is a substring search. Fail: printf-style messages. Convert to structured logging; it is tedious but mechanical, and it is the difference between grep and GROUP BY.

L5 — Logs go to both places

Check the app's own stdout/stderr while it runs.

Pass: log lines appear locally and in Kopai. You want both: stderr for the tight feedback loop while developing, OTel for correlation. Fail: if the bridge replaced local output entirely, developers lose their debugging loop and will route around the bridge. Use a fan-out handler that writes to both.

Done when

L1–L5 pass for $OTEL_SERVICE_NAME on this run. Then validate-metrics.md.

Reference

Kopai CLI · Kopai CLI source

references

_sections.md

architectural-patterns.md

attributes.md

cli-reference.md

context-propagation.md

custom-instrumentation.md

drive-traffic.md

instrument-attributes.md

instrument-errors.md

instrument-spans.md

lang-cpp.md

lang-dotnet.md

lang-erlang.md

lang-fastify.md

lang-go.md

lang-java.md

lang-nextjs.md

lang-nodejs.md

lang-php.md

lang-python.md

lang-ruby.md

lang-rust.md

layered-telemetry.md

nextjs-examples.md

otel-docs.md

sampling.md

setup-backend.md

setup-environment.md

troubleshoot-missing-attrs.md

troubleshoot-missing-spans.md

troubleshoot-no-data.md

troubleshoot-wrong-port.md

validate-logs.md

validate-metrics.md

validate-shutdown.md

validate-traces.md

CHANGELOG.md

SKILL.md

tile.json