Skip to content

Commit 4de5186

Browse files
committed
Enhance OpenTelemetry tracing documentation and client implementation for Python SDK
- Updated README to clarify OpenTelemetry tracing setup and functionality. - Improved comments in client and worker scripts to reflect automatic span creation. - Removed manual span creation from activity functions, leveraging SDK's built-in capabilities. - Updated Jaeger UI references in documentation to match service name changes.
1 parent ff4b47e commit 4de5186

File tree

5 files changed

+55
-48
lines changed

5 files changed

+55
-48
lines changed

samples/durable-task-sdks/python/opentelemetry-tracing/README.md

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Python | Durable Task SDK
44

55
## Description
66

7-
This sample demonstrates how to add OpenTelemetry distributed tracing to a Durable Task SDK Python application. Traces are exported to Jaeger for visualization, allowing you to see the full flow of orchestrations and activities.
7+
This sample demonstrates OpenTelemetry distributed tracing with the Durable Task SDK for Python. The SDK **automatically** creates activity spans with `durabletask.*` tags and propagates W3C trace context from orchestrations to activities — no manual span creation needed in your activity code.
88

99
## Prerequisites
1010

@@ -33,28 +33,44 @@ This sample demonstrates how to add OpenTelemetry distributed tracing to a Durab
3333
```
3434

3535
4. View traces:
36-
- **Jaeger UI:** http://localhost:16686 (search for service `durable-worker`)
36+
- **Jaeger UI:** http://localhost:16686 (search for service `DistributedTracingSample`)
3737
- **DTS Dashboard:** http://localhost:8082
3838

3939
## Viewing Traces
4040

41-
Open the [Jaeger UI](http://localhost:16686), select the `durable-worker` service, and click **Find Traces**. You'll see one trace per activity — `validate_order`, `process_payment`, `ship_order`, and `send_notification`:
41+
Open the [Jaeger UI](http://localhost:16686), select the **DistributedTracingSample** service, and click **Find Traces**. You'll see a single trace with **5 Spans** — the parent `create_orchestration:OrderProcessingOrchestration` with 4 child activity spans:
4242

43-
![Jaeger search results showing 4 activity traces](images/jaeger-search-results.png)
43+
![Jaeger search results showing orchestration trace with 5 spans](images/jaeger-search-results.png)
4444

45-
Click on any trace to see span details including tags, duration, and OpenTelemetry metadata:
45+
Click on the trace to see the full span tree with parent-child hierarchy and rich `durabletask.*` tags (automatically added by the SDK):
4646

47-
![Jaeger trace detail showing span tags and process info](images/jaeger-trace-detail.png)
47+
![Jaeger trace detail showing nested activity spans with durable task metadata](images/jaeger-trace-detail.png)
48+
49+
## How It Works
50+
51+
The Durable Task Python SDK has **built-in OpenTelemetry support**:
52+
53+
1. **Client side**: When you schedule an orchestration with an active OpenTelemetry span, the SDK captures the W3C trace context (`traceparent`/`tracestate`) and sends it with the request.
54+
2. **Orchestrator side**: The SDK stores the parent trace context and passes it to all child activities and sub-orchestrations.
55+
3. **Activity side**: The SDK wraps each activity execution in an `activity:<name>` span as a child of the orchestration's trace context, with `durabletask.task.instance_id`, `durabletask.task.name`, and `durabletask.task.task_id` tags.
56+
57+
All you need is to configure a `TracerProvider` with an exporter — the SDK does the rest.
4858

4959
## What You'll See
5060

51-
These traces help you:
61+
The Jaeger UI shows a single trace for the entire orchestration with nested child spans for each activity. This helps you:
5262

53-
- Identify slow activities
63+
- Identify slow activities within an orchestration
5464
- See the sequential flow of function chaining
55-
- Correlate traces across distributed services
65+
- Correlate the full orchestration lifecycle in one trace
5666
- Debug failures with full context
5767

68+
## Clean Up
69+
70+
```bash
71+
docker compose down
72+
```
73+
5874
## Learn More
5975

6076
- [Observability Guide](../../../../docs/observability.md)

samples/durable-task-sdks/python/opentelemetry-tracing/client.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
"""Client to schedule and monitor orchestrations with OpenTelemetry tracing."""
1+
"""Client to schedule and monitor orchestrations with OpenTelemetry tracing.
2+
3+
The SDK automatically captures the current OpenTelemetry span context
4+
and propagates it as W3C trace context to the orchestration, which then
5+
forwards it to all activities and sub-orchestrations.
6+
"""
27
import os
38
import asyncio
49

@@ -18,7 +23,7 @@
1823
provider.add_span_processor(BatchSpanProcessor(exporter))
1924
trace.set_tracer_provider(provider)
2025

21-
tracer = trace.get_tracer("Microsoft.DurableTask")
26+
tracer = trace.get_tracer("durabletask")
2227

2328

2429
async def main():
@@ -32,7 +37,8 @@ async def main():
3237
token_credential=None,
3338
)
3439

35-
# Create a parent span for the orchestration, matching the .NET SDK pattern
40+
# Create a parent span — the SDK automatically captures this context
41+
# and propagates it to the orchestration and all child activities.
3642
with tracer.start_as_current_span(
3743
"create_orchestration:OrderProcessingOrchestration",
3844
attributes={
-34.1 KB
Loading
18.3 KB
Loading

samples/durable-task-sdks/python/opentelemetry-tracing/worker.py

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
"""Worker with OpenTelemetry tracing for Durable Task SDK."""
1+
"""Worker with OpenTelemetry tracing for Durable Task SDK.
2+
3+
The SDK automatically creates activity spans with durabletask.* tags
4+
and propagates W3C trace context from orchestrations to activities.
5+
All you need is to configure a TracerProvider with an exporter.
6+
"""
27
import asyncio
38
import os
49
import time
@@ -15,60 +20,40 @@
1520
logging.basicConfig(level=logging.INFO)
1621
logger = logging.getLogger(__name__)
1722

18-
# Configure OpenTelemetry with a service name that identifies this application
23+
# Configure OpenTelemetry — the SDK uses this tracer provider automatically
1924
resource = Resource.create({"service.name": "DistributedTracingSample"})
2025
provider = TracerProvider(resource=resource)
2126
otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
2227
exporter = OTLPSpanExporter(endpoint=otlp_endpoint, insecure=True)
2328
provider.add_span_processor(BatchSpanProcessor(exporter))
2429
trace.set_tracer_provider(provider)
2530

26-
tracer = trace.get_tracer("Microsoft.DurableTask")
27-
28-
29-
def _activity_span(activity_name: str, task_id: int = 0):
30-
"""Create an activity span with durable task metadata tags."""
31-
span = tracer.start_span(
32-
f"activity:{activity_name}",
33-
attributes={
34-
"durabletask.task.name": activity_name,
35-
"durabletask.type": "activity",
36-
"durabletask.task.task_id": task_id,
37-
},
38-
)
39-
return span
4031

32+
# Activities — no manual span creation needed. The SDK wraps each
33+
# activity execution in an "activity:<name>" span automatically.
4134

4235
def validate_order(ctx, order_id: str) -> str:
43-
span = _activity_span("ValidateOrder", task_id=1)
44-
with trace.use_span(span, end_on_exit=True):
45-
logger.info(f"Validating order: {order_id}")
46-
time.sleep(0.1)
47-
return f"Validated({order_id})"
36+
logger.info(f"Validating order: {order_id}")
37+
time.sleep(0.1)
38+
return f"Validated({order_id})"
4839

4940

5041
def process_payment(ctx, input: str) -> str:
51-
span = _activity_span("ProcessPayment", task_id=2)
52-
with trace.use_span(span, end_on_exit=True):
53-
logger.info(f"Processing payment for: {input}")
54-
time.sleep(0.2)
55-
return f"Paid({input})"
42+
logger.info(f"Processing payment for: {input}")
43+
time.sleep(0.2)
44+
return f"Paid({input})"
5645

5746

5847
def ship_order(ctx, input: str) -> str:
59-
span = _activity_span("ShipOrder", task_id=3)
60-
with trace.use_span(span, end_on_exit=True):
61-
logger.info(f"Shipping: {input}")
62-
time.sleep(0.15)
63-
return f"Shipped({input})"
48+
logger.info(f"Shipping: {input}")
49+
time.sleep(0.15)
50+
return f"Shipped({input})"
6451

6552

6653
def send_notification(ctx, input: str) -> str:
67-
span = _activity_span("SendNotification", task_id=4)
68-
with trace.use_span(span, end_on_exit=True):
69-
logger.info(f"Notifying customer: {input}")
70-
time.sleep(0.05)
71-
return f"Notified({input})"
54+
logger.info(f"Notifying customer: {input}")
55+
time.sleep(0.05)
56+
return f"Notified({input})"
7257

7358

7459
def order_processing_orchestration(ctx, order_id: str):

0 commit comments

Comments
 (0)