WCF cleanup and trace context extraction#8263
Conversation
…lready an active scope
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 84a0ae0a48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR applies follow-up fixes to WCF trace context handling (especially with OpenTelemetry WCF client instrumentation) and updates the integration tests/snapshots accordingly.
Changes:
- Stop extracting WCF propagation context when a scope is already active, and additionally search for W3C Trace Context SOAP header namespace.
- Remove disposed WCF server scopes from the weak-table to avoid referencing closed spans and reduce table size.
- Rework
Samples.Wcfintegration tests to optionally use OTel client instrumentation and add new snapshots for that mode.
Reviewed changes
Copilot reviewed 16 out of 58 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Wcf/WcfCommon.cs | Skips propagation extraction when a scope exists; adds W3C SOAP namespace fallback for header extraction |
| tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Wcf/BeforeSendReplyIntegration.cs | Removes request scope from the scopes table when the reply is sent and the scope is disposed |
| tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/WcfTests.cs | Converts test matrix to combinatorial/pairwise and adds optional OTel client instrumentation toggle |
| tracer/test/snapshots/WcfTests.__metadataSchemaVersion=v0_binding=NetTcpBinding_enableNewWcfInstrumentation=True_enableWcfObfuscation=True_useOtelClientInstrumentation=True.verified.txt | New snapshot covering NetTcpBinding + OTel client instrumentation (new WCF + obfuscation) |
| tracer/test/snapshots/WcfTests.__metadataSchemaVersion=v0_binding=NetTcpBinding_enableNewWcfInstrumentation=True_enableWcfObfuscation=False_useOtelClientInstrumentation=True.verified.txt | New snapshot covering NetTcpBinding + OTel client instrumentation (new WCF + no obfuscation) |
| tracer/test/snapshots/WcfTests.__metadataSchemaVersion=v0_binding=NetTcpBinding_enableNewWcfInstrumentation=False_enableWcfObfuscation=True_useOtelClientInstrumentation=True.verified.txt | New snapshot covering NetTcpBinding + OTel client instrumentation (old WCF + obfuscation) |
| tracer/test/snapshots/WcfTests.__metadataSchemaVersion=v0_binding=NetTcpBinding_enableNewWcfInstrumentation=False_enableWcfObfuscation=False_useOtelClientInstrumentation=True.verified.txt | New snapshot covering NetTcpBinding + OTel client instrumentation (old WCF + no obfuscation) |
| tracer/test/snapshots/WcfTests.__metadataSchemaVersion=v0_binding=Custom_enableNewWcfInstrumentation=True_enableWcfObfuscation=True_useOtelClientInstrumentation=True.verified.txt | New snapshot covering Custom binding + OTel client instrumentation |
| tracer/test/snapshots/WcfTests.__metadataSchemaVersion=v0_binding=BasicHttpBinding_enableNewWcfInstrumentation=True_enableWcfObfuscation=True_useOtelClientInstrumentation=True.verified.txt | New snapshot covering BasicHttpBinding + OTel client instrumentation |
Comments suppressed due to low confidence (3)
tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Wcf/WcfCommon.cs:75
- The
ActiveScopeguard now wraps the entire request-property parsing block, which means when a scope is already active you also skip extracting request metadata (e.g.,userAgent/httpMethod) that’s unrelated to propagation. Consider moving the guard so it only skips propagation context extraction (HTTP headers + WCF message headers), while still populating request metadata/tags regardless of an existing scope.
var requestProperties = requestMessage.Properties;
if (tracer.ActiveScope is { } activeScope)
{
Log.Warning("Skipped extracting headers due to existing scope: {ActiveScope}", activeScope.Span);
}
else
{
if (requestProperties is not null
&& requestProperties.TryGetValue("httpRequest", out var httpRequestProperty)
tracer/src/Datadog.Trace/ClrProfiler/AutoInstrumentation/Wcf/WcfCommon.cs:71
- Logging this as
Warningis likely to be noisy in normal scenarios where an upstream/manual/OTel scope exists (and skipping extraction is expected). Consider lowering toDebug(or using a rate-limited log) and/or logging a simpler identifier instead of the full span object to avoid excessive log volume and potentially expensive formatting.
if (tracer.ActiveScope is { } activeScope)
{
Log.Warning("Skipped extracting headers due to existing scope: {ActiveScope}", activeScope.Span);
}
tracer/test/Datadog.Trace.ClrProfiler.IntegrationTests/WcfTests.cs:78
- The environment variable is only set when
useOtelClientInstrumentationis true and is never explicitly unset/set to0when false. This can make tests order-dependent/flaky if the process environment leaks between test cases. Consider always setting it based on the boolean (or explicitly clearing it in thefalsebranch).
if (useOtelClientInstrumentation)
{
SetEnvironmentVariable("USE_OTEL_CLIENT_INSTRUMENTATION", "1");
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8263) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (70ms) : 68, 72
master - mean (70ms) : 68, 71
section Bailout
This PR (8263) - mean (74ms) : 72, 75
master - mean (73ms) : 72, 74
section CallTarget+Inlining+NGEN
This PR (8263) - mean (1,049ms) : 982, 1116
master - mean (1,045ms) : 999, 1090
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (107ms) : 105, 110
master - mean (108ms) : 105, 111
section Bailout
This PR (8263) - mean (108ms) : 107, 110
master - mean (109ms) : 107, 112
section CallTarget+Inlining+NGEN
This PR (8263) - mean (744ms) : 690, 797
master - mean (762ms) : 723, 801
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (95ms) : 92, 98
master - mean (95ms) : 93, 98
section Bailout
This PR (8263) - mean (96ms) : 94, 98
master - mean (97ms) : 95, 99
section CallTarget+Inlining+NGEN
This PR (8263) - mean (729ms) : 700, 757
master - mean (741ms) : 721, 761
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (94ms) : 91, 96
master - mean (95ms) : 92, 98
section Bailout
This PR (8263) - mean (95ms) : 93, 97
master - mean (96ms) : 94, 98
section CallTarget+Inlining+NGEN
This PR (8263) - mean (637ms) : 623, 651
master - mean (650ms) : 631, 669
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (195ms) : 191, 200
master - mean (196ms) : 192, 200
section Bailout
This PR (8263) - mean (199ms) : 196, 202
master - mean (199ms) : 196, 203
section CallTarget+Inlining+NGEN
This PR (8263) - mean (1,158ms) : 1101, 1216
master - mean (1,162ms) : 1090, 1234
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (280ms) : 275, 286
master - mean (283ms) : 275, 291
section Bailout
This PR (8263) - mean (281ms) : 277, 285
master - mean (282ms) : 277, 287
section CallTarget+Inlining+NGEN
This PR (8263) - mean (946ms) : 896, 996
master - mean (946ms) : 902, 989
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (273ms) : 268, 277
master - mean (274ms) : 268, 280
section Bailout
This PR (8263) - mean (273ms) : 268, 278
master - mean (274ms) : 269, 279
section CallTarget+Inlining+NGEN
This PR (8263) - mean (935ms) : 901, 969
master - mean (942ms) : 908, 975
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8263) - mean (272ms) : 268, 276
master - mean (273ms) : 265, 282
section Bailout
This PR (8263) - mean (273ms) : 265, 281
master - mean (274ms) : 270, 279
section CallTarget+Inlining+NGEN
This PR (8263) - mean (840ms) : 817, 864
master - mean (839ms) : 815, 863
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-03-03 18:20:06 Comparing candidate commit c8e4479 in PR branch Found 9 performance improvements and 7 performance regressions! Performance is the same for 163 metrics, 13 unstable metrics. scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild netcoreapp3.1
scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net472
scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net6.0
scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest netcoreapp3.1
scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1
scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice netcoreapp3.1
scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net6.0
scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net6.0
scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog netcoreapp3.1
scenario:Benchmarks.Trace.RedisBenchmark.SendReceive netcoreapp3.1
scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net6.0
scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore netcoreapp3.1
scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan netcoreapp3.1
scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin netcoreapp3.1
|
Summary of changes
Follow on fixes from WCF improvements made in #7842
Reason for change
@zacharycmontoya and I identified some things to fix while working on the above PR, but deferred fixing them till later. And now is that time! We flagged two main things:
While trying to create a repro for the second point (by using the Otel WCF instrumentation) 🤖 discovered a different issue, whereby the WCF server
wcf.requestspans were incorrectly parented under the manual span instead, of the OTel WCF client spandotnet_wcf.client.requestforNetTcpBinding(whenDD_TRACE_OTEL_ENABLED=trueand OpenTelemetry WCF client instrumentation was active).The latter issue was initially related to how we interact with the Otel instrumentation in the sample, but switching to "pure" OTel for propagation still revealed a parenting problem, because we were never extracting the OTel headers.
Implementation details
There are 3 fixes:
Test coverage
To increase coverage, reworked the
Samples.Wcfto allow optionally using the Otel instrumentation:TraceProvider, add theTelemetryEndpointBehavior, and stop manually injecting our headers into the messageuseOtelClientInstrumentation=FalsesuffixuseOtelClientInstrumentation=TrueOther details
Heavily used Claude Code on the testing and implementation. We knew about the first two issues, and it discovered the third when trying to follow our suggestion for a repro