Python custom OpenTelemetry instrumentation
Auto-instrumentation captures framework, database, and HTTP spans. Use the OpenTelemetry API to add spans and metrics for your own business logic — a batch job, a cache decision, a domain-specific counter. The API calls attach to the provider that opentelemetry-instrument or your programmatic setup already configured, so custom telemetry exports to Osuite through the same pipeline.
Create spans
Section titled “Create spans”Get a tracer once per module, then wrap work in a span with start_as_current_span. The context manager sets the span as current, so any auto-instrumented calls inside it — and any child spans you create — nest underneath.
from opentelemetry import trace
tracer = trace.get_tracer("myapp.orders")
def process_order(order): with tracer.start_as_current_span("process_order") as span: span.set_attribute("order.id", order.id) span.set_attribute("order.item_count", len(order.items)) charge(order)Set attributes to record the values you will filter and group by in Osuite. Use dotted, lowercase names.
Decorate a function
Section titled “Decorate a function”To trace an entire function, use the tracer as a decorator. The span name defaults to the wrapped function’s name.
@tracer.start_as_current_span("reconcile_ledger")def reconcile_ledger(): ...Record errors and status
Section titled “Record errors and status”Mark a span as failed and attach the exception so it appears in error tracking. record_exception stores the stack trace; set_status marks the span as an error.
from opentelemetry.trace import Status, StatusCode
with tracer.start_as_current_span("charge") as span: try: charge(order) except PaymentError as exc: span.record_exception(exc) span.set_status(Status(StatusCode.ERROR)) raiseAdd events
Section titled “Add events”Events are timestamped annotations on the current span — useful for marking a milestone without creating a child span.
span = trace.get_current_span()span.add_event("cache.miss", {"cache.key": key})Record metrics
Section titled “Record metrics”Get a meter once per module, then create instruments at module scope and record to them in your code. Osuite reads these in the Explore metrics view.
from opentelemetry import metrics
meter = metrics.get_meter("myapp.orders")
orders_processed = meter.create_counter( "orders.processed", unit="1", description="Number of orders processed",)
order_value = meter.create_histogram( "orders.value", unit="USD", description="Monetary value per order",)
def process_order(order): orders_processed.add(1, {"order.type": order.type}) order_value.record(order.total, {"order.type": order.type})- Counter — a value that only increases (requests handled, jobs completed). Use
create_up_down_counterwhen it can also decrease, such as an in-flight gauge you increment and decrement. - Histogram — a distribution of recorded values (durations, sizes, amounts). Osuite computes percentiles from it.
Observe a value on demand
Section titled “Observe a value on demand”For a value you read rather than accumulate — a queue depth, a pool size — register an observable gauge with a callback. The SDK invokes the callback each collection interval.
def queue_depth(options): yield metrics.Observation(get_queue_depth(), {"queue": "orders"})
meter.create_observable_gauge( "orders.queue.depth", callbacks=[queue_depth], unit="1", description="Pending orders in the queue",)Correlate logs with spans
Section titled “Correlate logs with spans”Logs written through the standard logging module inside an active span carry that span’s trace and span IDs automatically when the OpenTelemetry logging bridge is enabled (OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true, set in the overview). No extra code is needed — log as usual and the records line up with the trace in Osuite.
Verify in Osuite
Section titled “Verify in Osuite”What you should see
Exercise the instrumented code path. Your custom spans appear nested in the request or task trace in APM, with the attributes you set. Custom metrics appear in the Explore metrics view under the instrument names you chose within a minute of the first recording.
Troubleshooting
Section titled “Troubleshooting”No data in Osuite after a few minutes? Work through these checks.
- Endpoint — confirm the exporter targets exactly
ingest.<region>.osuite.io:443for your region, over TLS. - Token — confirm the ingest header carries a valid
<your-ingest-token>and has not been rotated. - Pipeline — confirm the signal you expect (traces, logs, or metrics) is wired into an active pipeline with the
otlp/osuiteexporter attached. - Export errors — check the application or Collector logs for OTLP export failures (auth, DNS, TLS, connection refused).
- Network — confirm the host has outbound access to the ingest endpoint on port
443. - Timing — allow up to a minute for the first data to appear before assuming a failure.
Still stuck? Ask the Investigation Agent or contact support.