Repository navigation
Add Azure Durable Functions tracing support #12618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gh-worker-dd-mergequeue-cf854d
merged 21 commits into
master
from
storms/azure-durable-fx-support
Oct 12, 2026
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
1ecaa96
Add Azure Durable Functions worker instrumentation
jcstorms1 f6cb9cf
Add Azure Functions worker code owner
jcstorms1 376c24d
Convert Azure Functions worker tests to JUnit
jcstorms1 12dec94
Fix Durable Functions replay edge cases
jcstorms1 8cd74a3
Fix Azure Functions replay error handling
jcstorms1 e159a3d
Address Azure Functions review feedback
jcstorms1 ab2c863
Sort Azure Functions helper classes
jcstorms1 868f14c
Scope Azure sampling workaround to durable functions
jcstorms1 675b1ca
Align Azure worker test fixture with SPI
jcstorms1 96637fd
Harden durable function replay tracing
jcstorms1 39331a5
Merge remote-tracking branch 'origin/master' into storms/azure-durabl…
jcstorms1 c56b3ed
Remove Azure Functions sampling override
jcstorms1 9b6fc61
Avoid rescanning Azure Durable orchestration history
jcstorms1 b706092
Run Azure worker v0 tests with latest dependencies
jcstorms1 05516ca
Avoid activating scope for Azure replay error span
jcstorms1 06e2965
Scope Azure durable orchestration tracing to worker invocations
jcstorms1 5bd0b58
Share Azure durable orchestration state across classloaders
jcstorms1 374e866
Harden Azure Durable Functions tracing and classloader isolation
jcstorms1 fb59635
Simplify Azure Durable Functions muzzle and error span finish
jcstorms1 7fc25cc
Merge branch 'master' into storms/azure-durable-fx-support
jcstorms1 8186859
Merge branch 'master' into storms/azure-durable-fx-support
jcstorms1 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
...rc/main/java/datadog/trace/bootstrap/instrumentation/azure/DurableOrchestrationState.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package datadog.trace.bootstrap.instrumentation.azure; | ||
|
|
||
| import datadog.context.Context; | ||
| import datadog.context.ContextKey; | ||
| import datadog.context.ContextScope; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
||
| /** Shares an Azure Durable orchestration invocation and its span across worker classloaders. */ | ||
| public final class DurableOrchestrationState { | ||
| private static final ContextKey<DurableOrchestrationState> KEY = | ||
| ContextKey.named("azure-durable-orchestration"); | ||
|
|
||
| private final AgentSpan parentSpan; | ||
| private final String functionName; | ||
| private final AtomicReference<AgentSpan> span = new AtomicReference<>(); | ||
| private final AtomicBoolean errorRecorded = new AtomicBoolean(); | ||
|
jcstorms1 marked this conversation as resolved.
|
||
|
|
||
| private DurableOrchestrationState(AgentSpan parentSpan, String functionName) { | ||
| this.parentSpan = parentSpan; | ||
| this.functionName = functionName; | ||
| } | ||
|
|
||
| public static ContextScope activate(AgentSpan remoteSpan, String functionName) { | ||
| Context context = Context.current(); | ||
| final AgentSpan parentSpan = remoteSpan != null && remoteSpan.isValid() ? remoteSpan : null; | ||
| if (parentSpan != null) { | ||
| context = context.with(parentSpan); | ||
| } | ||
| return context.with(KEY, new DurableOrchestrationState(parentSpan, functionName)).attach(); | ||
|
mcculls marked this conversation as resolved.
|
||
| } | ||
|
|
||
| public static DurableOrchestrationState current() { | ||
| return Context.current().get(KEY); | ||
| } | ||
|
|
||
| public AgentSpan span() { | ||
| return span.get(); | ||
| } | ||
|
|
||
| public AgentSpan parentSpan() { | ||
| return parentSpan; | ||
| } | ||
|
|
||
| public String functionName() { | ||
| return functionName; | ||
| } | ||
|
|
||
| public boolean errorRecorded() { | ||
| return errorRecorded.get(); | ||
| } | ||
|
|
||
| public void markErrorRecorded() { | ||
| errorRecorded.set(true); | ||
| } | ||
|
|
||
| public AgentSpan setSpan(AgentSpan span) { | ||
| return this.span.getAndSet(span); | ||
| } | ||
| } | ||
61 changes: 61 additions & 0 deletions
61
...est/java/datadog/trace/bootstrap/instrumentation/azure/DurableOrchestrationStateTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| package datadog.trace.bootstrap.instrumentation.azure; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertFalse; | ||
| import static org.junit.jupiter.api.Assertions.assertNull; | ||
| import static org.junit.jupiter.api.Assertions.assertSame; | ||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
| import static org.mockito.Mockito.mock; | ||
| import static org.mockito.Mockito.when; | ||
|
|
||
| import datadog.context.Context; | ||
| import datadog.context.ContextScope; | ||
| import datadog.trace.api.DDTraceId; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpan; | ||
| import datadog.trace.bootstrap.instrumentation.api.AgentSpanContext; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| class DurableOrchestrationStateTest { | ||
|
|
||
| @Test | ||
| void activatesRemoteParentAndRestoresPreviousContext() { | ||
| AgentSpanContext spanContext = mock(AgentSpanContext.class); | ||
| when(spanContext.getTraceId()).thenReturn(DDTraceId.from(42)); | ||
| when(spanContext.getSpanId()).thenReturn(43L); | ||
| AgentSpan parent = AgentSpan.fromSpanContext(spanContext); | ||
| AgentSpan orchestration = mock(AgentSpan.class); | ||
|
|
||
| assertNull(DurableOrchestrationState.current()); | ||
| try (ContextScope scope = DurableOrchestrationState.activate(parent, "Orchestrator")) { | ||
| DurableOrchestrationState state = DurableOrchestrationState.current(); | ||
| assertSame(parent, state.parentSpan()); | ||
| assertSame("Orchestrator", state.functionName()); | ||
| assertSame(parent, AgentSpan.fromContext(Context.current())); | ||
| assertFalse(state.errorRecorded()); | ||
| state.markErrorRecorded(); | ||
| assertTrue(state.errorRecorded()); | ||
| assertNull(state.span()); | ||
| assertNull(state.setSpan(orchestration)); | ||
| assertSame(orchestration, state.span()); | ||
| assertSame(orchestration, state.setSpan(null)); | ||
| assertNull(state.span()); | ||
| } | ||
| assertNull(DurableOrchestrationState.current()); | ||
| } | ||
|
|
||
| @Test | ||
| void ignoresMissingAndInvalidRemoteParents() { | ||
| AgentSpanContext invalidContext = mock(AgentSpanContext.class); | ||
| when(invalidContext.getTraceId()).thenReturn(DDTraceId.ZERO); | ||
| when(invalidContext.getSpanId()).thenReturn(0L); | ||
| AgentSpan invalidParent = AgentSpan.fromSpanContext(invalidContext); | ||
| assertFalse(invalidParent.isValid()); | ||
| try (ContextScope scope = DurableOrchestrationState.activate(null, null)) { | ||
| assertNull(DurableOrchestrationState.current().parentSpan()); | ||
| } | ||
| try (ContextScope scope = DurableOrchestrationState.activate(invalidParent, null)) { | ||
| assertNull(DurableOrchestrationState.current().parentSpan()); | ||
| assertNull(AgentSpan.fromContext(Context.current())); | ||
| } | ||
| assertNull(DurableOrchestrationState.current()); | ||
| } | ||
| } |
20 changes: 20 additions & 0 deletions
20
dd-java-agent/instrumentation/azure-functions-worker-2.7/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Azure Durable Functions Java tracing | ||
|
|
||
| For correlated host and Java worker traces, enable OpenTelemetry in the Function App's | ||
| `host.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "version": "2.0", | ||
| "telemetryMode": "OpenTelemetry" | ||
| } | ||
| ``` | ||
|
|
||
| The instrumentation honors the incoming W3C `traceparent` sampled flag and Datadog | ||
| `tracestate` sampling decision. It does not force-keep Durable invocations. If the host or an | ||
| upstream service sends a drop decision, the corresponding Durable spans may not be ingested. | ||
| Keep the application's sampling configuration consistent across the HTTP starter and Durable | ||
| worker invocations; enabling OpenTelemetry does not override an intentional upstream drop. | ||
|
|
||
| See the [Azure Functions OpenTelemetry setup guide](https://learn.microsoft.com/en-us/azure/azure-functions/opentelemetry-howto) | ||
| for host configuration details. |
69 changes: 69 additions & 0 deletions
69
dd-java-agent/instrumentation/azure-functions-worker-2.7/build.gradle.kts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import groovy.lang.Closure | ||
|
|
||
| plugins { | ||
| id("dd-trace-java.module.instrumentation") | ||
| } | ||
|
|
||
| muzzle { | ||
| // FunctionExecutionMiddleware lives in the unpublished Azure Functions Java worker. | ||
| // The SPI pass validates its public middleware contract, but cannot check the worker's | ||
| // internal class name or invoke signature. The test stub exercises the advice behavior; | ||
| // worker compatibility also requires validation against a running Function App. | ||
| pass { | ||
| group = "com.microsoft.azure.functions" | ||
| module = "azure-functions-java-spi" | ||
|
jcstorms1 marked this conversation as resolved.
|
||
| versions = "[1.0.0,)" | ||
|
jcstorms1 marked this conversation as resolved.
|
||
| extraDependency("com.microsoft.azure.functions:azure-functions-java-core-library:1.2.0") | ||
| extraDependency("com.microsoft:durabletask-azure-functions:1.0.1") | ||
| extraDependency("com.microsoft:durabletask-client:1.0.0") | ||
| } | ||
| pass { | ||
| group = "com.microsoft" | ||
| module = "durabletask-client" | ||
| versions = "[1.0.0,)" | ||
| extraDependency("com.microsoft.azure.functions:azure-functions-java-core-library:1.2.0") | ||
| extraDependency("com.microsoft.azure.functions:azure-functions-java-spi:1.0.0") | ||
| extraDependency("com.microsoft:durabletask-azure-functions:1.0.1") | ||
| } | ||
| pass { | ||
| group = "com.microsoft" | ||
| module = "durabletask-azure-functions" | ||
| versions = "[1.0.1,)" | ||
| extraDependency("com.microsoft.azure.functions:azure-functions-java-core-library:1.2.0") | ||
| extraDependency("com.microsoft.azure.functions:azure-functions-java-spi:1.0.0") | ||
| extraDependency("com.microsoft:durabletask-client:1.0.0") | ||
| } | ||
| } | ||
|
|
||
| fun addTestSuiteForDir(name: String, directory: String) { | ||
| (project.extra["addTestSuiteForDir"] as Closure<*>).call(name, directory) | ||
| } | ||
|
|
||
| addTestSuiteForDir("latestDepTest", "test") | ||
|
jcstorms1 marked this conversation as resolved.
|
||
| (project.extra["addForkedTestTask"] as Closure<*>).call("latestDepTest") | ||
|
|
||
| dependencies { | ||
| compileOnly("com.google.protobuf:protobuf-java:3.19.2") | ||
| compileOnly("com.microsoft:durabletask-client:1.0.0") | ||
| compileOnly("com.microsoft.azure.functions:azure-functions-java-core-library:1.2.0") | ||
| compileOnly("com.microsoft.azure.functions:azure-functions-java-spi:1.0.0") | ||
|
|
||
| testImplementation("com.microsoft:durabletask-azure-functions:1.0.1") | ||
| testImplementation("com.microsoft:durabletask-client:1.0.0") | ||
| testImplementation("com.google.protobuf:protobuf-java:3.19.2") | ||
| testImplementation("com.microsoft.azure.functions:azure-functions-java-core-library:1.2.0") | ||
| testImplementation("com.microsoft.azure.functions:azure-functions-java-spi:1.0.0") | ||
| testImplementation(libs.bundles.mockito) | ||
|
|
||
| add("latestDepTestImplementation", "com.microsoft:durabletask-azure-functions:+") | ||
| add("latestDepTestImplementation", "com.microsoft:durabletask-client:+") | ||
| add("latestDepTestImplementation", "com.google.protobuf:protobuf-java:+") | ||
| add( | ||
| "latestDepTestImplementation", | ||
| "com.microsoft.azure.functions:azure-functions-java-core-library:+" | ||
| ) | ||
| add( | ||
| "latestDepTestImplementation", | ||
| "com.microsoft.azure.functions:azure-functions-java-spi:+" | ||
| ) | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.