Skip to content

Commit a6067d8

Browse files
committed
Add OpenTelemetry tracing support for Python SDK samples
1 parent 4521b23 commit a6067d8

File tree

12 files changed

+173
-91
lines changed

12 files changed

+173
-91
lines changed

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,33 +27,33 @@ This launches:
2727
### 2. Run the sample
2828

2929
```bash
30-
./gradlew runOpenTelemetryTracingPattern
30+
./gradlew run
3131
```
3232

3333
This starts the worker, schedules an `OrderProcessingOrchestration`, and waits for it to complete.
3434

3535
## Viewing Traces
3636

3737
1. Open the Jaeger UI at **http://localhost:16686**
38-
2. Select the **durable-worker** service from the dropdown
38+
2. Select the **DistributedTracingSample** service from the dropdown
3939
3. Click **Find Traces**
40-
4. Click on a trace to see the span tree — you'll see spans for each activity (`ValidateOrder`, `ProcessPayment`, `ShipOrder`, `SendNotification`)
40+
4. Click on the trace with **5 Spans** — you'll see the parent `create_orchestration:OrderProcessingOrchestration` with child activity spans nested underneath
4141

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

44-
Click on any trace to see the span details, including service name, duration, and OpenTelemetry tags:
44+
Click on the trace to see the full span tree with parent-child hierarchy and rich `durabletask.*` tags:
4545

46-
![Jaeger trace detail view with span metadata](images/jaeger-trace-detail.png)
46+
![Jaeger trace detail showing nested activity spans with durable task metadata](images/jaeger-trace-detail.png)
4747

4848
You can also view the orchestration status in the DTS Dashboard at **http://localhost:8082**.
4949

5050
## What You'll See
5151

52-
The Jaeger UI shows traces for each activity in the orchestration, including timing and any errors. This helps you:
52+
The Jaeger UI shows a single trace for the entire orchestration with nested child spans for each activity. Each span includes `durabletask.task.name`, `durabletask.type`, and `durabletask.task.task_id` tags. This helps you:
5353

54-
- Identify slow activities
54+
- Identify slow activities within an orchestration
5555
- See the sequential flow of function chaining
56-
- Correlate traces across distributed services
56+
- Correlate the full orchestration lifecycle in one trace
5757
- Debug failures with full context
5858

5959
## How It Works
@@ -68,7 +68,7 @@ OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
6868
.build();
6969

7070
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
71-
.setResource(Resource.builder().put("service.name", "durable-worker").build())
71+
.setResource(Resource.builder().put("service.name", "DistributedTracingSample").build())
7272
.addSpanProcessor(BatchSpanProcessor.builder(spanExporter).build())
7373
.build();
7474

samples/durable-task-sdks/java/opentelemetry-tracing/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ def grpcVersion = '1.78.0'
1414
def openTelemetryVersion = '1.58.0'
1515
base { archivesName = 'durabletask-samples' }
1616

17+
application {
18+
mainClass = 'io.durabletask.samples.OpenTelemetryTracingPattern'
19+
}
20+
1721
repositories {
1822
mavenLocal()
1923
mavenCentral()
Binary file not shown.
8.14 KB
Loading
36.3 KB
Loading

samples/durable-task-sdks/java/opentelemetry-tracing/src/main/java/io/durabletask/samples/OpenTelemetryTracingPattern.java

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.opentelemetry.api.trace.Span;
99
import io.opentelemetry.api.trace.StatusCode;
1010
import io.opentelemetry.api.trace.Tracer;
11+
import io.opentelemetry.context.Context;
1112
import io.opentelemetry.context.Scope;
1213
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
1314
import io.opentelemetry.sdk.OpenTelemetrySdk;
@@ -24,10 +25,30 @@
2425
/**
2526
* Demonstrates OpenTelemetry distributed tracing with the Durable Task SDK.
2627
* Traces are exported to Jaeger via OTLP/gRPC for visualization.
28+
*
29+
* <p>The Java SDK's client automatically propagates W3C trace context
30+
* (traceparent/tracestate) when scheduling orchestrations. This sample
31+
* additionally shares the parent span context with activities running in
32+
* the same process so that all spans appear under a single trace in Jaeger.
2733
*/
2834
final class OpenTelemetryTracingPattern {
2935
private static final Logger logger = LoggerFactory.getLogger(OpenTelemetryTracingPattern.class);
3036

37+
// Shared parent context so activity spans become children of the orchestration span.
38+
// In a distributed setup the SDK would propagate this via W3C trace context headers.
39+
private static volatile Context orchestrationContext;
40+
41+
/** Create an activity span as a child of the orchestration span. */
42+
private static Span startActivitySpan(Tracer tracer, String activityName, int taskId) {
43+
Context parent = orchestrationContext != null ? orchestrationContext : Context.current();
44+
return tracer.spanBuilder("activity:" + activityName)
45+
.setParent(parent)
46+
.setAttribute("durabletask.task.name", activityName)
47+
.setAttribute("durabletask.type", "activity")
48+
.setAttribute("durabletask.task.task_id", taskId)
49+
.startSpan();
50+
}
51+
3152
public static void main(String[] args) throws IOException, InterruptedException, TimeoutException {
3253
// Configure OpenTelemetry
3354
String otlpEndpoint = System.getenv("OTEL_EXPORTER_OTLP_ENDPOINT");
@@ -40,7 +61,7 @@ public static void main(String[] args) throws IOException, InterruptedException,
4061
.build();
4162

4263
Resource resource = Resource.builder()
43-
.put("service.name", "durable-worker")
64+
.put("service.name", "DistributedTracingSample")
4465
.build();
4566

4667
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
@@ -52,7 +73,7 @@ public static void main(String[] args) throws IOException, InterruptedException,
5273
.setTracerProvider(tracerProvider)
5374
.buildAndRegisterGlobal();
5475

55-
Tracer tracer = openTelemetry.getTracer("durable-worker");
76+
Tracer tracer = openTelemetry.getTracer("Microsoft.DurableTask");
5677
logger.info("OpenTelemetry configured with OTLP exporter at {}", otlpEndpoint);
5778

5879
// Build connection string
@@ -81,36 +102,20 @@ public static void main(String[] args) throws IOException, InterruptedException,
81102
public TaskOrchestration create() {
82103
return ctx -> {
83104
String orderId = ctx.getInput(String.class);
84-
85-
// Step 1: Validate the order
86-
String validated = ctx.callActivity(
87-
"ValidateOrder", orderId, String.class).await();
88-
89-
// Step 2: Process payment
90-
String paid = ctx.callActivity(
91-
"ProcessPayment", validated, String.class).await();
92-
93-
// Step 3: Ship order
94-
String shipped = ctx.callActivity(
95-
"ShipOrder", paid, String.class).await();
96-
97-
// Step 4: Send notification
98-
String result = ctx.callActivity(
99-
"SendNotification", shipped, String.class).await();
100-
105+
String validated = ctx.callActivity("ValidateOrder", orderId, String.class).await();
106+
String paid = ctx.callActivity("ProcessPayment", validated, String.class).await();
107+
String shipped = ctx.callActivity("ShipOrder", paid, String.class).await();
108+
String result = ctx.callActivity("SendNotification", shipped, String.class).await();
101109
ctx.complete(result);
102110
};
103111
}
104112
})
105113
.addActivity(new TaskActivityFactory() {
106-
@Override
107-
public String getName() { return "ValidateOrder"; }
108-
109-
@Override
110-
public TaskActivity create() {
114+
@Override public String getName() { return "ValidateOrder"; }
115+
@Override public TaskActivity create() {
111116
return ctx -> {
112117
String orderId = ctx.getInput(String.class);
113-
Span span = tracer.spanBuilder("ValidateOrder").startSpan();
118+
Span span = startActivitySpan(tracer, "ValidateOrder", 1);
114119
try (Scope scope = span.makeCurrent()) {
115120
logger.info("[ValidateOrder] Validating order: {}", orderId);
116121
Thread.sleep(100);
@@ -126,14 +131,11 @@ public TaskActivity create() {
126131
}
127132
})
128133
.addActivity(new TaskActivityFactory() {
129-
@Override
130-
public String getName() { return "ProcessPayment"; }
131-
132-
@Override
133-
public TaskActivity create() {
134+
@Override public String getName() { return "ProcessPayment"; }
135+
@Override public TaskActivity create() {
134136
return ctx -> {
135137
String input = ctx.getInput(String.class);
136-
Span span = tracer.spanBuilder("ProcessPayment").startSpan();
138+
Span span = startActivitySpan(tracer, "ProcessPayment", 2);
137139
try (Scope scope = span.makeCurrent()) {
138140
logger.info("[ProcessPayment] Processing payment for: {}", input);
139141
Thread.sleep(200);
@@ -149,14 +151,11 @@ public TaskActivity create() {
149151
}
150152
})
151153
.addActivity(new TaskActivityFactory() {
152-
@Override
153-
public String getName() { return "ShipOrder"; }
154-
155-
@Override
156-
public TaskActivity create() {
154+
@Override public String getName() { return "ShipOrder"; }
155+
@Override public TaskActivity create() {
157156
return ctx -> {
158157
String input = ctx.getInput(String.class);
159-
Span span = tracer.spanBuilder("ShipOrder").startSpan();
158+
Span span = startActivitySpan(tracer, "ShipOrder", 3);
160159
try (Scope scope = span.makeCurrent()) {
161160
logger.info("[ShipOrder] Shipping: {}", input);
162161
Thread.sleep(150);
@@ -172,14 +171,11 @@ public TaskActivity create() {
172171
}
173172
})
174173
.addActivity(new TaskActivityFactory() {
175-
@Override
176-
public String getName() { return "SendNotification"; }
177-
178-
@Override
179-
public TaskActivity create() {
174+
@Override public String getName() { return "SendNotification"; }
175+
@Override public TaskActivity create() {
180176
return ctx -> {
181177
String input = ctx.getInput(String.class);
182-
Span span = tracer.spanBuilder("SendNotification").startSpan();
178+
Span span = startActivitySpan(tracer, "SendNotification", 4);
183179
try (Scope scope = span.makeCurrent()) {
184180
logger.info("[SendNotification] Notifying customer: {}", input);
185181
Thread.sleep(50);
@@ -205,15 +201,24 @@ public TaskActivity create() {
205201
DurableTaskClient client = DurableTaskSchedulerClientExtensions
206202
.createClientBuilder(connectionString).build();
207203

208-
// Create a parent span for the orchestration - the SDK automatically propagates
209-
// W3C trace context (traceparent/tracestate) when scheduling orchestrations
210-
Span orchestrationSpan = tracer.spanBuilder("OrderProcessingOrchestration").startSpan();
204+
// Create a parent span for the orchestration. The SDK automatically propagates
205+
// W3C trace context (traceparent/tracestate) when scheduling orchestrations.
206+
Span orchestrationSpan = tracer.spanBuilder("create_orchestration:OrderProcessingOrchestration")
207+
.setAttribute("durabletask.task.name", "OrderProcessingOrchestration")
208+
.setAttribute("durabletask.type", "orchestration")
209+
.startSpan();
210+
211+
// Share the orchestration context with the worker thread so activity spans
212+
// become children of this orchestration span in the trace.
213+
orchestrationContext = Context.current().with(orchestrationSpan);
214+
211215
String instanceId;
212216
try (Scope scope = orchestrationSpan.makeCurrent()) {
213217
logger.info("Scheduling order processing orchestration...");
214218
instanceId = client.scheduleNewOrchestrationInstance(
215219
"OrderProcessingOrchestration",
216220
new NewOrchestrationInstanceOptions().setInput("Order-12345"));
221+
orchestrationSpan.setAttribute("durabletask.task.instance_id", instanceId);
217222
logger.info("Started orchestration: {}", instanceId);
218223
}
219224

@@ -225,11 +230,20 @@ public TaskActivity create() {
225230

226231
logger.info("Status: {}", result.getRuntimeStatus());
227232
logger.info("Result: {}", result.readOutputAs(String.class));
233+
if (result.getFailureDetails() != null
234+
&& result.getFailureDetails().getErrorMessage() != null
235+
&& !result.getFailureDetails().getErrorMessage().isEmpty()) {
236+
logger.error("Failure: {} - {}", result.getFailureDetails().getErrorType(),
237+
result.getFailureDetails().getErrorMessage());
238+
}
228239
logger.info("");
229240
logger.info("View traces in Jaeger UI: http://localhost:16686");
241+
logger.info(" Search for service: DistributedTracingSample");
230242
logger.info("View orchestration in DTS Dashboard: http://localhost:8082");
231243

232-
// Shutdown
244+
// Flush traces and shut down
245+
tracerProvider.forceFlush();
246+
Thread.sleep(2000);
233247
tracerProvider.shutdown();
234248
worker.stop();
235249
System.exit(0);

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,19 @@ This sample demonstrates how to add OpenTelemetry distributed tracing to a Durab
3636
- **Jaeger UI:** http://localhost:16686 (search for service `durable-worker`)
3737
- **DTS Dashboard:** http://localhost:8082
3838

39+
## Viewing Traces
40+
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`:
42+
43+
![Jaeger search results showing 4 activity traces](images/jaeger-search-results.png)
44+
45+
Click on any trace to see span details including tags, duration, and OpenTelemetry metadata:
46+
47+
![Jaeger trace detail showing span tags and process info](images/jaeger-trace-detail.png)
48+
3949
## What You'll See
4050

41-
The Jaeger UI shows the complete trace for each orchestration — the parent orchestration span with child spans for each activity, including timing and any errors. This helps you:
51+
These traces help you:
4252

4353
- Identify slow activities
4454
- See the sequential flow of function chaining

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

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,67 @@
1-
"""Client to schedule and monitor orchestrations."""
1+
"""Client to schedule and monitor orchestrations with OpenTelemetry tracing."""
22
import os
33
import asyncio
4-
import durabletask.client as client
4+
5+
from opentelemetry import trace
6+
from opentelemetry.sdk.trace import TracerProvider
7+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
8+
from opentelemetry.sdk.resources import Resource
9+
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
10+
11+
from durabletask.azuremanaged.client import DurableTaskSchedulerClient
12+
13+
# Configure OpenTelemetry (same service name as worker for unified view)
14+
resource = Resource.create({"service.name": "DistributedTracingSample"})
15+
provider = TracerProvider(resource=resource)
16+
otlp_endpoint = os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317")
17+
exporter = OTLPSpanExporter(endpoint=otlp_endpoint, insecure=True)
18+
provider.add_span_processor(BatchSpanProcessor(exporter))
19+
trace.set_tracer_provider(provider)
20+
21+
tracer = trace.get_tracer("Microsoft.DurableTask")
522

623

724
async def main():
825
endpoint = os.environ.get("ENDPOINT", "http://localhost:8080")
926
taskhub = os.environ.get("TASKHUB", "default")
1027

11-
async with client.DurableTaskClient(
28+
c = DurableTaskSchedulerClient(
1229
host_address=endpoint,
13-
secure_channel=not endpoint.startswith("http://localhost"),
30+
secure_channel=endpoint != "http://localhost:8080",
1431
taskhub=taskhub,
15-
) as c:
32+
token_credential=None,
33+
)
34+
35+
# Create a parent span for the orchestration, matching the .NET SDK pattern
36+
with tracer.start_as_current_span(
37+
"create_orchestration:OrderProcessingOrchestration",
38+
attributes={
39+
"durabletask.task.name": "OrderProcessingOrchestration",
40+
"durabletask.type": "orchestration",
41+
},
42+
) as span:
1643
print("Scheduling order processing orchestration...")
17-
instance_id = await c.schedule_new_orchestration(
44+
instance_id = c.schedule_new_orchestration(
1845
"order_processing_orchestration",
1946
input="Order-12345",
2047
)
48+
span.set_attribute("durabletask.task.instance_id", instance_id)
2149
print(f"Started orchestration: {instance_id}")
2250
print("Waiting for completion...")
2351

24-
result = await c.wait_for_orchestration_completion(
52+
result = c.wait_for_orchestration_completion(
2553
instance_id, timeout=60
2654
)
2755
print(f"Status: {result.runtime_status.name}")
2856
print(f"Result: {result.serialized_output}")
29-
print()
30-
print("View traces in Jaeger UI: http://localhost:16686")
31-
print("View orchestration in DTS Dashboard: http://localhost:8082")
57+
58+
# Flush remaining spans
59+
provider.force_flush()
60+
61+
print()
62+
print("View traces in Jaeger UI: http://localhost:16686")
63+
print(" Search for service: DistributedTracingSample")
64+
print("View orchestration in DTS Dashboard: http://localhost:8082")
3265

3366

3467
if __name__ == "__main__":
106 KB
Loading
85.2 KB
Loading

0 commit comments

Comments
 (0)