Skip to content

Commit 0e11757

Browse files
lucaspimentelclaude
andcommitted
Fix Azure Functions span parenting via HttpContext.Items
AsyncLocal context doesn't flow through Azure Functions middleware, causing worker's azure_functions.invoke span to be incorrectly parented. Use HttpContext.Items as an explicit bridge to pass the AspNetCore scope to the Azure Functions middleware. Changes: - Store AspNetCore scope in HttpContext.Items after creation - Add Items property to IFunctionContext duck type interface - Retrieve scope from HttpContext.Items when creating azure_functions.invoke span - Only use HttpContext.Items fallback when tracer.InternalActiveScope is null 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 9d50874 commit 0e11757

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Azure/Functions/AzureFunctionsCommon.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,13 +261,44 @@ _ when type.StartsWith("eventGrid", StringComparison.OrdinalIgnoreCase) => "Even
261261

262262
if (tracer.InternalActiveScope == null)
263263
{
264-
// This is the root scope
265-
tags.SetAnalyticsSampleRate(IntegrationId, tracer.CurrentTraceSettings.Settings, enabledWithGlobalSetting: false);
266-
scope = tracer.StartActiveInternal(OperationName, tags: tags, parent: extractedContext.SpanContext);
264+
// AsyncLocal context didn't flow - try to get parent scope from HttpContext.Items
265+
// This happens in Azure Functions isolated worker where middleware breaks AsyncLocal flow
266+
Scope? parentScope = null;
267+
try
268+
{
269+
// HttpContext is stored in FunctionContext.Items with key "__AspNetCoreHttpContext__"
270+
if (context.Items?.TryGetValue("__AspNetCoreHttpContext__", out var httpContextObj) == true
271+
&& httpContextObj is Microsoft.AspNetCore.Http.HttpContext httpContext)
272+
{
273+
// Retrieve the scope stored by AspNetCoreHttpRequestHandler
274+
if (httpContext.Items.TryGetValue("__Datadog.Trace.AspNetCore.ActiveScope", out var scopeObj)
275+
&& scopeObj is Scope aspNetCoreScope)
276+
{
277+
parentScope = aspNetCoreScope;
278+
Log.Debug("Retrieved AspNetCore scope from HttpContext.Items for Azure Functions span parenting");
279+
}
280+
}
281+
}
282+
catch (Exception ex)
283+
{
284+
Log.Debug(ex, "Error retrieving AspNetCore scope from HttpContext.Items");
285+
}
286+
287+
if (parentScope != null)
288+
{
289+
// Use the AspNetCore scope as parent
290+
scope = tracer.StartActiveInternal(OperationName, parent: parentScope.Span.Context, tags: tags);
291+
}
292+
else
293+
{
294+
// This is the root scope - use extracted context from headers
295+
tags.SetAnalyticsSampleRate(IntegrationId, tracer.CurrentTraceSettings.Settings, enabledWithGlobalSetting: false);
296+
scope = tracer.StartActiveInternal(OperationName, tags: tags, parent: extractedContext.SpanContext);
297+
}
267298
}
268299
else
269300
{
270-
// shouldn't be hit, but better safe than sorry
301+
// AsyncLocal is working - use it as parent
271302
scope = tracer.StartActiveInternal(OperationName);
272303
var rootSpan = scope.Root.Span;
273304
AzureFunctionsTags.SetRootSpanTags(

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Azure/Functions/Isolated/IFunctionContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ internal interface IFunctionContext
1818
FunctionDefinitionStruct FunctionDefinition { get; }
1919

2020
IEnumerable<KeyValuePair<Type, object?>>? Features { get; }
21+
22+
IDictionary<object, object>? Items { get; }
2123
}
2224

2325
#endif

tracer/src/Datadog.Trace/PlatformHelpers/AspNetCoreHttpRequestHandler.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ public Scope StartAspNetCorePipelineScope(Tracer tracer, Security security, Http
137137

138138
httpContext.Features.Set(requestTrackingFeature);
139139

140+
// Store scope in HttpContext.Items for Azure Functions middleware to retrieve
141+
// Use __ prefix to avoid conflicts with user code (same pattern as TracingHttpModule)
142+
httpContext.Items["__Datadog.Trace.AspNetCore.ActiveScope"] = scope;
143+
140144
if (tracer.Settings.IpHeaderEnabled || security.AppsecEnabled)
141145
{
142146
var peerIp = new Headers.Ip.IpInfo(httpContext.Connection.RemoteIpAddress?.ToString(), httpContext.Connection.RemotePort);

0 commit comments

Comments
 (0)