feat(otel): link Azure Durable Functions spans via W3C trace context - #9683
feat(otel): link Azure Durable Functions spans via W3C trace context#9683chemystery09 wants to merge 4 commits into
Conversation
Add OTel-only auto-instrumentation for @azure/functions and durable-functions when DD_TRACE_OTEL_ENABLED is on and the native azure-durable-functions plugin is disabled, so HTTP/orchestration/activity/entity spans share one trace ID. Co-authored-by: Cursor <cursoragent@cursor.com>
Overall package sizeSelf size: 7.84 MB Dependency sizes| name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.3 | 125.43 kB | 441.68 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |🤖 This report was automatically generated by heaviest-objects-in-the-universe |
|
Fix eslint violations, consolidate unit tests under test/helpers so verify-exercised-tests picks them up via test:instrumentations:misc. Co-authored-by: Cursor <cursoragent@cursor.com>
Load native azure instrumentations before OTel hooks so plugin-structure.spec.js detects the expected plugin mapping, and stub all HTTP registration methods in unit tests. Co-authored-by: Cursor <cursoragent@cursor.com>
BenchmarksBenchmark execution time: 2026-08-04 18:34:49 Comparing candidate commit 568150b in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 2324 metrics, 34 unstable metrics.
|
The esbuild plugin invokes every hooks.js entry at build time. Loading OTel Azure modules from a second require in hooks.js pulled @opentelemetry/api into unrelated bundles and caused hono esbuild integration flakes. Apply OTel patches from the existing azure-functions and durable-functions addHook callbacks instead, and restore single-require hook entries. Co-authored-by: Cursor <cursoragent@cursor.com>
Summary
Adds OTel-only auto-instrumentation for Azure Functions and Durable Functions so HTTP, orchestration, activity, and entity spans link into a single distributed trace when:
DD_TRACE_OTEL_ENABLED=true(OTel API spans export through dd-trace)DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED=false(native durable plugin disabled — OTel-only path)host.json)This is the OTel-sourced span equivalent of the trace-linking work in #9394 / #9594 for the native
azure-durable-functionsplugin.Scope: this PR adds trace linking via W3C context extraction. A related sampling visibility issue affects all OTel + Azure Durable V2 deployments (see below) and is documented here with a recommended app workaround until a library-side fix lands.
Motivation
Azure Durable Functions run as separate invocations connected via queues. Without extracting W3C
traceparent/tracestatefromInvocationContext.traceContextbefore creating spans, each invocation becomes an independent root trace in Datadog APM.For apps using
@opentelemetry/apiwith dd-trace as an export bridge (plugins: false), there was no built-in instrumentation to propagate durable trace context into OTel spans.Changes
helpers/azure-trace-context.jsInvocationContext.traceContexthelpers/otel-azure-enabled.jshelpers/otel-azure-span.jsotel-azure-functions.jsapp.genericorchestration triggersotel-azure-durable-functions.jsapp.orchestration,app.activity,app.entityhelpers/hooks.jsRequired app configuration
host.json(mandatory){ "extensions": { "durableTask": { "tracing": { "distributedTracingEnabled": true, "version": "V2" } } } }Without this,
InvocationContext.traceContextis empty on orchestration/activity invocations.Environment variables
DD_TRACE_OTEL_ENABLEDtrueDD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLEDfalseDD_TRACE_SAMPLE_RATE1_DD_APM_TRACING_AGENTLESS_ENABLEDtrueTypical OTel-only setup also uses
ddtrace.init({ plugins: false })so native dd-trace plugins do not duplicate spans.Architecture
Behavior notes
context.df.isReplayingis true (replay-safe).app.genericorchestration triggers (registered via@azure/functions) are also instrumented.azure-durable-functionsplugin remains unchanged; OTel path activates only when that plugin is explicitly disabled.Azure trace context sampling (known limitation)
This applies to any OTel-based Azure Durable Functions app using distributed tracing V2 — not specific to one sample app or local Azurite.
Azure's host forwards W3C
traceparentwith the sampled flag unset (flags=00). dd-trace honors that on extract, so spans created from propagated context are exported with_sampling_priority_v1: 0and may be absent from APM Trace Explorer even when intake accepts them (HTTP 202). SettingDD_TRACE_SAMPLE_RATE=1does not override priority already set from propagated context.Recommended app workaround until library fix:
Register an OTel span processor that upgrades unsampled Azure-propagated spans to
USER_KEEPviaddSpan._prioritySampler.setPriority(ddSpan, USER_KEEP).Follow-up for dd-trace-js: re-apply tracer sampling inside
helpers/azure-trace-context.jsorhelpers/otel-azure-span.jswhen starting spans from Azure-propagated context, so customers do not need app-level workarounds.This does not affect the native
azure-durable-functionsdd-trace plugin path (#9394 / #9594).CI / OTel-only design note
Disabling the native
azure-durable-functionsplugin (DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED=false) is an application/runtime configuration for OTel-only customers. It does not turn off dd-trace-js library tests or make tracing CI red by design.What this PR does instead:
azure-functions/azure-durable-functionsinstrumentation registered (required forplugin-structure.spec.jsand existing plugin coverage).DD_TRACE_OTEL_ENABLED=trueand the native durable plugin is explicitly disabled.Initial tracing/instrumentations-misc failures on this branch were fixable implementation issues (hook
requireorder trippingplugin-structure.spec.js, incomplete unit-test mocks), not an inherent consequence of the OTel-only path.Test plan
node node_modules/mocha/bin/mocha.js packages/datadog-instrumentations/test/helpers/azure-trace-context.spec.js packages/datadog-instrumentations/test/helpers/otel-azure.spec.jshost.jsontracing V2 + OTel flags above@_dd.integration:otel_sampling_priority_v1 >= 1, not rejected)DD_TRACE_AZURE_DURABLE_FUNCTIONS_ENABLED=falseandplugins: false