Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
A surprising amount of instrumentation work gets blocked on access — someone needs a vendor account, an API key, a budget approval — and in the meantime nothing gets instrumented. You do not need any of it. The console exporter prints spans as JSON to stdout, which is enough to verify parent-child relationships, attribute names and status codes; and a single Jaeger container gives you a real waterfall UI on localhost. Working locally is also strictly better for learning, because you see the raw data instead of a vendor's interpretation of it, and you can iterate in seconds rather than waiting on ingestion delay. Every technique in this course can be verified this way.
Two setups. The first prints spans so you can assert on them in a test — this is how you keep instrumentation from silently rotting. The second is a one-command local backend with a real waterfall view.
# ── A. Assert on spans in a unit test. No backend, no network. ────
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
InMemorySpanExporter,
)
exporter = InMemorySpanExporter()
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
trace.set_tracer_provider(provider)
def test_checkout_emits_a_tenant_attribute():
checkout(tenant="acme") # your code under test
spans = exporter.get_finished_spans()
root = next(s for s in spans if s.parent is None)
assert root.name == "POST /checkout" # low cardinality name
assert root.attributes["tenant.id"] == "acme"
assert root.kind.name == "SERVER"
# The assertion that catches the most real regressions: children
# must actually be children, not accidental roots.
assert all(
s.parent.span_id == root.context.span_id
for s in spans if s is not root
), "a child span lost its parent context"
# ── B. A real waterfall UI, one command, no account ───────────────────
# docker run --rm -p 16686:16686 -p 4317:4317 -p 4318:4318 \
# jaegertracing/all-in-one:latest
#
# OTEL_SERVICE_NAME=checkout \
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
# opentelemetry-instrument python app.py
#
# open http://localhost:16686python3 main.py