-
Notifications
You must be signed in to change notification settings - Fork 150
Fix ASP.NET Core Diagnostic Observer's incorrect Activity copying #8054
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
base: master
Are you sure you want to change the base?
Conversation
BenchmarksBenchmark execution time: 2026-01-15 12:00:02 Comparing candidate commit 39067b1 in PR branch Found 2 performance improvements and 10 performance regressions! Performance is the same for 160 metrics, 20 unstable metrics. scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net6.0
scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody netcoreapp3.1
scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net6.0
scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net6.0
scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net6.0
scenario:Benchmarks.Trace.DbCommandBenchmark.ExecuteNonQuery net6.0
scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net6.0
|
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8054) 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 (8054) - mean (69ms) : 67, 70
master - mean (68ms) : 67, 70
section Bailout
This PR (8054) - mean (72ms) : 71, 73
master - mean (72ms) : 71, 73
section CallTarget+Inlining+NGEN
This PR (8054) - mean (1,020ms) : 943, 1098
master - mean (1,008ms) : 937, 1078
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 (8054) - mean (106ms) : 103, 109
master - mean (105ms) : 103, 108
section Bailout
This PR (8054) - mean (107ms) : 106, 108
master - mean (106ms) : 105, 108
section CallTarget+Inlining+NGEN
This PR (8054) - mean (744ms) : 691, 798
master - mean (744ms) : 704, 784
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8054) - mean (94ms) : 92, 96
master - mean (93ms) : 91, 96
section Bailout
This PR (8054) - mean (95ms) : 94, 95
master - mean (94ms) : 93, 95
section CallTarget+Inlining+NGEN
This PR (8054) - mean (723ms) : 697, 750
master - mean (714ms) : 687, 742
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8054) - mean (93ms) : 90, 96
master - mean (92ms) : 89, 94
section Bailout
This PR (8054) - mean (93ms) : 92, 95
master - mean (93ms) : 92, 94
section CallTarget+Inlining+NGEN
This PR (8054) - mean (643ms) : 619, 667
master - mean (633ms) : 619, 647
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 (8054) - mean (193ms) : 189, 197
master - mean (194ms) : 189, 199
section Bailout
This PR (8054) - mean (197ms) : 193, 200
master - mean (198ms) : 194, 202
section CallTarget+Inlining+NGEN
This PR (8054) - mean (1,134ms) : 1053, 1216
master - mean (1,125ms) : 1067, 1183
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 (8054) - mean (277ms) : 272, 283
master - mean (277ms) : 271, 283
section Bailout
This PR (8054) - mean (277ms) : 273, 282
master - mean (277ms) : 273, 280
section CallTarget+Inlining+NGEN
This PR (8054) - mean (936ms) : 893, 978
master - mean (936ms) : 894, 979
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8054) - mean (270ms) : 264, 276
master - mean (271ms) : 262, 279
section Bailout
This PR (8054) - mean (269ms) : 266, 273
master - mean (270ms) : 266, 275
section CallTarget+Inlining+NGEN
This PR (8054) - mean (931ms) : 894, 968
master - mean (928ms) : 883, 973
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8054) - mean (270ms) : 266, 274
master - mean (269ms) : 263, 275
section Bailout
This PR (8054) - mean (271ms) : 264, 278
master - mean (270ms) : 265, 274
section CallTarget+Inlining+NGEN
This PR (8054) - mean (832ms) : 811, 852
master - mean (830ms) : 811, 849
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
f212b57 to
dcb2dce
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dcb2dce101
ℹ️ 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".
| var span = rootScope.Span; | ||
| CopyAspNetCoreActivityTagsIfRequired(span); | ||
| var isMissingHttpStatusCode = !span.HasHttpStatusCode(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure error flag still set when activity has status
With the activity listener enabled, ASP.NET Core’s hosting activity includes a numeric http.status_code tag. CopyAspNetCoreActivityTagsIfRequired uses OtlpHelpers.SetTagObject, which maps that to Tags.HttpStatusCode, so HasHttpStatusCode() becomes true and SetHttpStatusCode(...) is skipped. SetHttpStatusCode is the place that applies error classification (span.Error and error msg) for non-exceptional 5xx responses, so requests that set a 5xx/4xx status without throwing will no longer be marked as errors once activity tag copying runs here.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm, this is an interesting one 🤔 I'm not sure what we should do here. We could easily filter out some of the tags if we don't want to risk conflicts etc but I'm not sure what we should do here 🤔 cc / @bouwkast @zacharycmontoya
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we would have to exclude/filter out this tag or looking at the code could we just always call SetHttpStatusCode?
Looking at it, I think we can just call it?
| internal static void SetHttpStatusCode(this Span span, int statusCode, bool isServer, MutableSettings tracerSettings) |
I guess that could be a bit fragile / wasteful though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could override it by calling SetHttpStatusCode even if the tag is present, but I worry that the ASP.NET Core Activity could have the new attribute name http.response.status_code, in which case we could theoretically have two tags instead of one. So I propose that we instead just filter out tag names http.status_code and http.response.status_code when copying them over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, my concern is mainly about multiple tags and worse, conflicting tags, so filtering may make sense 👍 I wonder if there's anything else we should be filtering too, that may conflict, e.g. route tags😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an option to OtlpHelpers.SetTagObject() to allow excluding "known" otel values, because I don't think we want to (for example) set the operation name/resource name etc.
I also explicitly avoided setting tags that we set by default as part of the standard aspnetcore pipeline, as I could see weird things going on there if, for example, they're set with a different type of value to the ones we use.
This is all a little bit awkward and fragile tbh, but it's likely better than what we have today either way 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah looks good to me 👍
…ome issues: - Use the non-allocating tag enumeration - Only set the tags if the activity is the AspNetCore activity
…d of part way through Technically, this is a breaking change, but we consider it to primarily be a bug fix.
…(should make fast-path more easy to inline
43f2013 to
39067b1
Compare
bouwkast
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Summary of changes
Make our ASP.NET Core's
Diagnostic ObservercopyActivitytags to the root span instead of the MVC spanReason for change
This came up in a customer escalation recently, plus it just makes sense, and there's a bunch of issues
TagObjectsif availableImplementation details
"Microsoft.AspNetCore.Hosting.HttpRequestIn")OtlpHelpers(doing the allocation free enumeration etc)OnMvcAfterActionfrom single-span observerTest coverage
Added integration tests to confirm the tags are added to the root span as expected. Note that the .NET Core 2.1 sample doesn't reference a new-enough version of
System.Diagnostics.Activity, so we can't enable the listener in that scenario. Rather than change the sample dependencies, I omitted the testing, as it's low risk, and we don't technically support it now anywayOther details
Note that this is technically a breaking change, because we are no longer writing some tags that were written to the mvc span. However, given all the reasons above, we consider this to be a bug fix in practice.
Also, the copying behaviour is only enabled when you explicitly enable OTel integration, so the blast radius is reduced.