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

lang-python.mdreferences/

titleimpacttags
Python InstrumentationHIGHlang, python

Python Instrumentation

Set up OpenTelemetry SDK for Python applications with traces, logs, and metrics.

Install

pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http

Manual Setup (All Three Signals)

import os
import logging
from opentelemetry import trace, metrics
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter

# Configuration from environment
ENDPOINT = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4318")
SERVICE_NAME = os.getenv("OTEL_SERVICE_NAME", "my-service")

# Create resource
resource = Resource.create({"service.name": SERVICE_NAME})

# Traces
trace_provider = TracerProvider(resource=resource)
trace_provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint=f"{ENDPOINT}/v1/traces"))
)
trace.set_tracer_provider(trace_provider)

# Metrics
meter_provider = MeterProvider(
    resource=resource,
    metric_readers=[PeriodicExportingMetricReader(
        OTLPMetricExporter(endpoint=f"{ENDPOINT}/v1/metrics")
    )]
)
metrics.set_meter_provider(meter_provider)

# Logs
logger_provider = LoggerProvider(resource=resource)
logger_provider.add_log_record_processor(
    BatchLogRecordProcessor(OTLPLogExporter(endpoint=f"{ENDPOINT}/v1/logs"))
)
handler = LoggingHandler(logger_provider=logger_provider)
logging.getLogger().addHandler(handler)

Shutdown

Nothing flushes on its own. Without this, short scripts and SIGTERM'd servers lose their last batch — see validate-shutdown.md.

import atexit, signal, sys

def shutdown(*_):
    trace_provider.shutdown()      # flush order: traces, metrics, logs
    meter_provider.shutdown()
    logger_provider.shutdown()

atexit.register(shutdown)
signal.signal(signal.SIGTERM, lambda *_: (shutdown(), sys.exit(0)))

Auto-Instrumentation (Traces Only)

pip install opentelemetry-distro
opentelemetry-bootstrap -a install
opentelemetry-instrument python app.py

opentelemetry-bootstrap -a install inspects your installed packages and pulls the matching instrumentation libraries — Flask, Django, requests, psycopg, SQLAlchemy, redis and so on. Re-run it after adding dependencies, or their calls stay invisible.

Custom spans and attributes

from opentelemetry import trace

tracer = trace.get_tracer("checkout-service")

# attributes on the span you already have
span = trace.get_current_span()
span.set_attribute("user.id", user.id)
span.set_attribute("user.type", user.tier)

# a span of your own
with tracer.start_as_current_span("process-payment") as span:
    span.set_attribute("payment.amount", order.total)

Context across threads and processes

contextvars propagate through async/await and within a thread automatically. They do not cross a ThreadPoolExecutor boundary or a multiprocessing fork — spans created there become orphans:

from opentelemetry import context as otel_context

ctx = otel_context.get_current()

def run():
    token = otel_context.attach(ctx)
    try:
        process_item(item)
    finally:
        otel_context.detach(token)

executor.submit(run)

See context-propagation.md.

Reference

OpenTelemetry Python

Next

SDK setup is step 3 of six. It gets bytes flowing; it does not make the telemetry good.

  1. Decide what earns a span — instrument-spans.md
  2. Add the context that makes spans answerable — instrument-attributes.md
  3. Instrument the error paths — instrument-errors.md
  4. Drive traffic yourself — drive-traffic.md
  5. Assert on what arrived — validate-traces.md

Confirm before moving on: the SDK starts before any application code that could create a span, and shutdown flushes on SIGTERM (validate-shutdown.md). Both fail silently.

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