Skip to content

Commit b9bec75

Browse files
committed
Reproduce and fix reparent issue with Ocelot
1 parent b3f8478 commit b9bec75

File tree

7 files changed

+55
-8
lines changed

7 files changed

+55
-8
lines changed

tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Http/HttpClient/SocketsHttpHandler/OcelotMessageInvokerPoolIntegration.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,15 @@ internal static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget
7373
return new CallTargetReturn<TReturn>(returnValue);
7474
}
7575

76-
// On net6.0+, SocketsHttpHandler injects the current Activity into request headers using the
77-
// ActivityHeadersPropagator. This overwrites propagation headers already set by our HttpClient
78-
// instrumentation. Disable this by setting a no-output propagator to preserve Datadog trace context.
76+
// On net6.0+, SocketsHttpHandler builds an internal handler chain that includes DiagnosticsHandler
77+
// when ActivityHeadersPropagator is non-null. DiagnosticsHandler creates Activities and fires
78+
// DiagnosticSource events, which allows OpenTelemetry SDK (if present) to inject its own trace
79+
// context headers, overwriting headers already set by our HttpClient instrumentation.
80+
// Setting the propagator to null prevents DiagnosticsHandler from being added to the chain
81+
// entirely, ensuring Datadog trace context is preserved.
7982
if (returnValue is System.Net.Http.SocketsHttpHandler handler)
8083
{
81-
handler.ActivityHeadersPropagator = DistributedContextPropagator.CreateNoOutputPropagator();
84+
handler.ActivityHeadersPropagator = null;
8285
}
8386

8487
return new CallTargetReturn<TReturn>(returnValue);

tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/OcelotDistributedTracingTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public OcelotDistributedTracingTests(ITestOutputHelper output)
2424
: base("Ocelot.DistributedTracing", output)
2525
{
2626
SetServiceVersion(ServiceVersion);
27+
SetEnvironmentVariable("DD_TRACE_OTEL_ENABLED", "true");
2728
}
2829

2930
public override Result ValidateIntegrationSpan(MockSpan span, string metadataSchemaVersion) => Result.DefaultSuccess;

tracer/test/snapshots/OcelotDistributedTracingTests.verified.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
Tags: {
1010
component: HttpMessageHandler,
1111
env: integration_tests,
12-
http-client-handler-type: System.Net.Http.HttpClientHandler,
12+
http-client-handler-type: System.Net.Http.SocketsHttpHandler,
1313
http.method: GET,
1414
http.status_code: 200,
1515
http.url: http://localhost:00000/proxy,
@@ -39,15 +39,21 @@
3939
env: integration_tests,
4040
http.method: GET,
4141
http.request.headers.host: localhost:00000,
42+
http.request.method: GET,
4243
http.status_code: 200,
4344
http.url: http://localhost:00000/proxy,
4445
language: dotnet,
46+
network.protocol.version: 1.1,
4547
runtime-id: Guid_1,
48+
server.address: 127.0.0.1,
4649
span.kind: server,
50+
url.path: /proxy,
51+
url.scheme: http,
4752
version: 1.0.0
4853
},
4954
Metrics: {
5055
process_id: 0,
56+
server.port: 55635.0,
5157
_dd.top_level: 1.0,
5258
_dd.tracer_kr: 1.0,
5359
_sampling_priority_v1: 1.0
@@ -93,16 +99,22 @@
9399
env: integration_tests,
94100
http.method: GET,
95101
http.request.headers.host: localhost:00000,
102+
http.request.method: GET,
96103
http.route: /,
97104
http.status_code: 200,
98105
http.url: http://localhost:00000/,
99106
language: dotnet,
107+
network.protocol.version: 1.1,
100108
runtime-id: Guid_1,
109+
server.address: 127.0.0.1,
101110
span.kind: server,
111+
url.path: /,
112+
url.scheme: http,
102113
version: 1.0.0
103114
},
104115
Metrics: {
105116
process_id: 0,
117+
server.port: 55635.0,
106118
_dd.top_level: 1.0,
107119
_dd.tracer_kr: 1.0,
108120
_sampling_priority_v1: 1.0

tracer/test/test-applications/integrations/Samples.Ocelot.DistributedTracing/Properties/launchSettings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
"CORECLR_PROFILER": "{846F5F1C-F9AE-4B07-969E-05C26BC060D8}",
1111
"CORECLR_PROFILER_PATH": "$(SolutionDir)shared\\bin\\monitoring-home\\win-x64\\Datadog.Trace.ClrProfiler.Native.dll",
1212

13-
"DD_DOTNET_TRACER_HOME": "$(SolutionDir)shared\\bin\\monitoring-home"
13+
"DD_DOTNET_TRACER_HOME": "$(SolutionDir)shared\\bin\\monitoring-home",
14+
"DD_TRACE_DEBUG": "true",
15+
"DD_TRACE_OTEL_ENABLED": "true"
1416
},
1517
"applicationUrl": "http://localhost:5472/",
1618
"nativeDebugging": true

tracer/test/test-applications/integrations/Samples.Ocelot.DistributedTracing/Samples.Ocelot.DistributedTracing.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
<ItemGroup>
1616
<PackageReference Include="Ocelot" Version="$(ApiVersion)" />
17+
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.2" />
18+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.2" />
19+
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.11.1" />
20+
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.11.1" />
1721
</ItemGroup>
1822

1923
</Project>

tracer/test/test-applications/integrations/Samples.Ocelot.DistributedTracing/Startup.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using Microsoft.Extensions.Hosting;
77
using Ocelot.DependencyInjection;
88
using Ocelot.Middleware;
9+
using OpenTelemetry.Resources;
10+
using OpenTelemetry.Trace;
911
using System.Collections.Generic;
1012

1113
namespace Samples.Ocelot.DistributedTracing
@@ -14,6 +16,24 @@ public class Startup
1416
{
1517
public void ConfigureServices(IServiceCollection services)
1618
{
19+
// Simulate a customer environment where the OpenTelemetry SDK is configured.
20+
// The OTel SDK replaces DistributedContextPropagator.Current with its own propagator,
21+
// which causes SocketsHttpHandler's internal DiagnosticsHandler to overwrite Datadog
22+
// trace context headers on forwarded requests.
23+
services.AddOpenTelemetry()
24+
.ConfigureResource(r => r.AddService("ocelot-sample"))
25+
.WithTracing(builder =>
26+
{
27+
builder
28+
.AddAspNetCoreInstrumentation()
29+
.AddHttpClientInstrumentation()
30+
.AddOtlpExporter(o =>
31+
{
32+
// Use a non-routable endpoint so exports silently fail
33+
o.Endpoint = new System.Uri("http://192.0.2.1:4317");
34+
});
35+
});
36+
1737
// Create a minimal in-memory configuration for Ocelot startup
1838
// The actual routes will be configured at runtime by the Worker
1939
var ocelotConfig = new Dictionary<string, string>

tracer/test/test-applications/integrations/Samples.Ocelot.DistributedTracing/Worker.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
6060
// Update Ocelot's internal configuration to point the downstream route back to this application
6161
await UpdateOcelotInternalConfig(serviceScope.ServiceProvider, address);
6262

63-
// Send a request through the proxy
64-
using var client = new HttpClient();
63+
// Send a request through the proxy.
64+
// Disable ActivityHeadersPropagator on the Worker's HttpClient to prevent the OTel SDK
65+
// from overwriting Datadog's trace context on the initial request. In a real scenario,
66+
// the initial request comes from an external caller (e.g. RUM, Nginx) that isn't in
67+
// this process, so this propagation conflict wouldn't occur.
68+
var handler = new SocketsHttpHandler { ActivityHeadersPropagator = null };
69+
using var client = new HttpClient(handler);
6570

6671
_logger.LogInformation("Sending request to self via Ocelot proxy");
6772
var response = await client.GetAsync($"{address}/proxy", stoppingToken);

0 commit comments

Comments
 (0)