Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
OpenTelemetry deliberately splits into an API and an SDK, and understanding why saves you from the most common beginner failure: instrumenting everything correctly and receiving no data. The API is a set of interfaces whose default implementation is a no-op — a library can call it freely, and if the application never installs an SDK, nothing happens and nothing costs anything. The SDK is the implementation the application installs: it decides sampling, batching, and where data goes. Exporters are the last hop, translating the SDK's in-memory objects into a wire format. This layering is what makes OTel safe for library authors to adopt, and it is why 'my spans do not appear' is usually 'I never registered a TracerProvider'.
The demo shows the same instrumented function producing nothing and then producing spans, with no change to the function itself. That gap is the whole point of the split. Get in the habit of proving the SDK is registered before you debug your instrumentation.
# ── The library's code. API only. Never installs an SDK. ──────────
from opentelemetry import trace
tracer = trace.get_tracer("mylib.payments", "1.4.0")
def charge(amount_cents: int) -> str:
with tracer.start_as_current_span("charge") as span:
span.set_attribute("payment.amount", amount_cents)
return "ok"
# ── Run it with no SDK registered ────────────────────────────────────
charge(500)
print(type(trace.get_tracer_provider()).__name__)
# -> ProxyTracerProvider, delegating to a NoOp implementation.
# The span object is a NonRecordingSpan: set_attribute() is a
# no-op, nothing is allocated, nothing is exported. Zero cost.
# ── Now the APPLICATION registers an SDK ─────────────────────────────
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
SimpleSpanProcessor, ConsoleSpanExporter,
)
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
charge(500) # identical call site -> a real span on stdout
# Debug checklist when nothing shows up, in this order:
# 1. is a real TracerProvider registered? (print the type, as above)
# 2. was it registered BEFORE the tracer was fetched at import time?
# 3. is there a span processor attached to it?
# 4. did the process exit before the batch processor flushed?python3 main.py