Advanced Tracing (OTEL)
Exploring Manual Context Propagation, Custom Decorators, and Sampling Techniques
This documentation provides advanced use cases and examples, such as manual context propagation, custom decorators, and sampling , filtering methods. These examples cater to real-world requirements like asynchronous execution, multi-service interactions, and specialized exporters or decorators for observability platforms like Future AGI.
1. Manual Context Propagation
In OpenTelemetry, context propagation ensures that the current tracing context (i.e., the active span and its metadata) is maintained across thread, task, or process switches. This is crucial when your code involves asynchronous tasks or spans multiple microservices.
Typically, OTEL instrumentation libraries manage context propagation automatically. However, manual intervention is sometimes necessary, especially in asynchronous workflows or custom instrumentation scenarios.
Handling Propagation in Async Functions
For Python async/await code, you might need to manually pass context if automated instrumentation doesn’t suffice or if custom logic is involved. The process involves:
- Extracting the current context (e.g., from an incoming HTTP request)
- Creating a new span as a child of that context
- Embedding the context into the async function for reuse
Propagation Between Microservices
When making HTTP or gRPC calls to another microservice, the current tracing context is usually propagated through HTTP headers. Built-in instrumentation (like opentelemetry-instrumentation-requests or opentelemetry-instrumentation-httpx) handles this automatically. For a custom approach, follow these steps:
- Inject the current span context into HTTP headers before sending the request
- Extract the context from incoming headers on the receiving microservice
Example: Service A sends a request to Service B.
Service A:
Service B:
Propagation with Concurrent Threads
When tasks are submitted to a ThreadPoolExecutor
or any concurrency mechanism, each task runs in a separate thread. The tracer’s current context (which stores the active span or baggage) doesn’t automatically follow tasks to worker threads. By capturing the context in the main thread and attaching it in each worker thread, you maintain the association between tasks and the original trace context.
Example: Below is a detailed, annotated example to show how you can:
-
Capture the current context before submitting tasks to the executor.
-
Attach that context within each worker thread (using
attach
). -
Run your task logic (e.g., processing questions).
-
Detach the context when the task is complete (using
detach
).
2. Creating Custom Decorators
Decorators offer a convenient way to instrument functions and methods across your codebase without repeatedly inserting tracing calls. A custom decorator can:
- Initiate a new span before the function call
- Add attributes/events with function arguments (inputs)
- Return the function’s result (outputs) and annotate or log it in the span
- Conclude the span
Example Decorator Implementation:
3. Selective Span Filtering Based on Attributes
In large-scale applications, recording every span may not be necessary. Instead, you might want to selectively sample:
- Spans from a specific service or component
- Spans meeting certain business criteria (e.g., user.id in a specific subset)
- Only error or slow spans
Creating a custom sampler allows you to dynamically control which spans are recorded/exported based on their attributes or names. This approach helps manage telemetry volume and cost while ensuring you capture the most relevant traces for debugging or analysis.
Basics of Custom Sampling
Sampler Interface
In OTEL Python, create a custom sampler by subclassing the Sampler
interface from opentelemetry.sdk.trace.sampling
. Implement:
should_sample(...)
- Determines whether the span is recorded (Sampled) or dropped (NotSampled)
- You can examine attributes, span name, span kind, parent context, etc.
Sampling Result
When implementing should_sample
, you must return a SamplingResult
, which indicates:
- Sampling Decision:
Decision.RECORD_AND_SAMPLE
,Decision.RECORD_ONLY
, orDecision.DROP
- Attributes: Optionally modify or add attributes in the returned
SamplingResult
(e.g., a reason for sampling)
Example:
You then pass your custom sampler into your tracer provider.