spike: Activity-based tracing — SentrySpanProcessor as a raw ActivityListener in core (experiment, do not merge)#5382
Draft
jamescrosswell wants to merge 4 commits into
Draft
spike: Activity-based tracing — SentrySpanProcessor as a raw ActivityListener in core (experiment, do not merge)#5382jamescrosswell wants to merge 4 commits into
jamescrosswell wants to merge 4 commits into
Conversation
Experiment for the Activity-as-single-source-of-truth discussion (see #3238, #5350, #4859): proves that the Activity->Sentry conversion logic in Sentry.OpenTelemetry's SentrySpanProcessor can live in the core Sentry package, driven by a System.Diagnostics.ActivityListener, with zero dependency on the OpenTelemetry SDK. - Sentry.Internal.Tracing.SentryActivityProcessor: near-verbatim port of SentrySpanProcessor's OnStart/OnEnd state machine. Only two changes: no BaseProcessor<Activity> base class, and OTel Resource detection replaced by an injectable resourceAttributeResolver (the only two OTel SDK touch points in the original). - Sentry.Internal.Tracing.SentryActivityListener: replaces the OTel TracerProvider registration glue with a raw ActivityListener (ShouldListenTo / Sample / ActivityStarted / ActivityStopped). - HAS_ACTIVITY_LISTENER constant gates everything to modern TFMs; netstandard2.0/2.1/net462 compile the new files out, so no new package dependency is introduced anywhere. - Tests: all 33 SentrySpanProcessorTests ported 1:1 and passing against the core port, plus 4 new end-to-end SentryActivityListener tests (no OTel SDK involved). Full Sentry.Tests suite green (2439 passed). #skip-changelog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… support Validates the packaging model for supporting Activity-based tracing on every TFM without adding a dependency to the core Sentry package: - The tracing sources move to src/Sentry.DiagnosticSource/Internal/Tracing/, so the existing compile-include in Sentry.csproj ships them inside core for modern TFMs (where ActivityListener is part of the shared framework), while legacy TFMs (netstandard2.0/2.1/net462) get them by referencing the Sentry.DiagnosticSource package - the same split the EF/SqlClient DiagnosticSource integration uses today. - Sentry.DiagnosticSource bumps System.Diagnostics.DiagnosticSource from 4.5.0 to 8.0.1. ActivityListener needs 5.0 and ActivityStatusCode needs 6.0, but the netstandard2.0/net462 assets of 6.0 lag the modern API surface (no Activity.HasRemoteParent), making 8.0.1 the effective minimum. - ActivityIdExtensions regains the non-NET8 hex-string conversion branches and the processor avoids KeyValuePair deconstruction, so the sources compile on the legacy TFMs. - Tests move to Sentry.DiagnosticSource.Tests, where modern TFMs exercise the core-compiled path and net48 (Windows CI) exercises the standalone package path: one suite covers both packagings. Verified locally (macOS): Sentry builds on all TFMs; Sentry.DiagnosticSource builds on netstandard2.0/2.1/net462; Sentry.DiagnosticSource.Tests 119/119 on net10.0; Sentry.Tests 2402 passed; OTel packages build unchanged. #skip-changelog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jamescrosswell
commented
Jul 13, 2026
Addresses review feedback on the PR: now that the tracing sources live in Sentry.DiagnosticSource, ActivityListener is available in every compilation context that sees them - the standalone package always references System.Diagnostics.DiagnosticSource 8.0.1, and the compile-include into core Sentry only fires on modern TFMs where the APIs ship in the framework. This also matches the existing convention: the EF/SqlClient DiagnosticSource sources carry no availability guards either; HAS_DIAGNOSTIC_INTEGRATION is only used by core files (e.g. SentryOptions.cs) that are compiled on all TFMs and reference those types. If/when core gains wiring that references the Activity tracing types (e.g. SDK init), it will need an equivalent constant - reintroduce it then, on the referencing side. #skip-changelog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #5382 +/- ##
==========================================
- Coverage 74.15% 73.98% -0.18%
==========================================
Files 508 513 +5
Lines 18353 18710 +357
Branches 3586 3673 +87
==========================================
+ Hits 13610 13842 +232
- Misses 3870 3970 +100
- Partials 873 898 +25 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
On net9.0/net10.0-android, the Android implicit usings bring Android.App into scope, making the bare `Activity` identifier ambiguous with System.Diagnostics.Activity (which the repo-wide global usings import). Alias it explicitly in the three tracing files that use the bare type name. First time core has compiled System.Diagnostics.Activity usage on mobile TFMs, so no precedent existed. Verified locally: src/Sentry builds for net9.0-android35.0 (the TFM CI failed on). #skip-changelog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is part (a) of a two-part spike exploring whether
System.Diagnostics.Activitycan become the single source of truth for tracing in the .NET SDK (context: #3238, #3288, #5350, #4859, and the internal discussion about the Sentry-vs-OTel instrumentation ecosystems bifurcating). It exists to inform that discussion with working code; it will be closed, not merged.Question this spike answers
Answer: yes. The port was near-verbatim. Exactly two things needed to change, both predicted in advance:
BaseProcessor<Activity>base class —ActivityListener.ActivityStarted/ActivityStoppedare drop-in replacements forOnStart/OnEnd.Resourcedetection (ParentProvider.GetResource()) has noSystem.Diagnosticsequivalent — replaced with an injectableresourceAttributeResolver(defaults to empty).Everything else — the shadow span map, transaction-vs-child parent inference (including mixed Sentry/OTel parenting), DSC-from-baggage, DSN self-request filtering, scope restoration across async, pruning/leak guards, error generation from exception events, op/description/status mapping — is pure
System.Diagnosticscode and ported as-is.Packaging model: all TFMs, no new core dependency
The sources live in
src/Sentry.DiagnosticSource/Internal/Tracing/and ship through the same split the EF/SqlClient DiagnosticSource integration already uses:Sentrypackage via the existing<Compile Include>inSentry.csproj—ActivityListeneris part of the shared framework there, so core takes no new dependency.netstandard2.0/2.1,net462): the code ships in theSentry.DiagnosticSourcepackage, which carries theSystem.Diagnostics.DiagnosticSourcepackage reference. Legacy apps opt in by adding that package — exactly as they do today for EF/SQL instrumentation.📌 Version finding:
ActivityListenerneeds DiagnosticSource 5.0 andActivityStatusCodeneeds 6.0, but thenetstandard2.0/net462assets of the 6.0 package lag the modern API surface (noActivity.HasRemoteParent), so 8.0.1 is the effective minimum (bumped from 4.5.0).A
HAS_ACTIVITY_LISTENERconstant (defined in both csproj files) gates compilation; only two small source concessions were needed for the legacy TFMs (hex-string id conversion branches, noKeyValuePairdeconstruction).What's here
src/Sentry.DiagnosticSource/Internal/Tracing/SentryActivityProcessor.csSentrySpanProcessor(kept diffable against the original)src/Sentry.DiagnosticSource/Internal/Tracing/SentryActivityListener.csTracerProviderregistration glue with a rawActivityListenersrc/Sentry.DiagnosticSource/Internal/Tracing/ActivityIdExtensions.cs,ActivityAttributeExtensions.cs,ISentryActivityEnricher.csInternalsVisibleTocollisions with the copies in the OTel packages)test/Sentry.DiagnosticSource.Tests/Tracing/SentryActivityProcessorTests.csSentrySpanProcessorTestsported 1:1 — bodies kept diffable — passing against the core porttest/Sentry.DiagnosticSource.Tests/Tracing/SentryActivityListenerTests.csStartActivityreturnsnull(the zero-costHasListenersproperty). No OTel SDK anywhere in the pipeline.The tests live in
Sentry.DiagnosticSource.Testsdeliberately: modern TFMs exercise the compiled-into-core path, whilenet48(Windows CI) exercises the standalone-package path — one suite covers both packagings.Test evidence (local, macOS)
SentrySpanProcessorTests33/33 ✅Sentry.DiagnosticSource.Tests119/119 on net10.0)Sentry.Tests(net10.0): 2402 passed, 0 failed ✅src/Sentrybuilds clean on all TFMs ✅;Sentry.DiagnosticSourcebuilds clean onnetstandard2.0/2.1/net462with DiagnosticSource 8.0.1 ✅net48test execution happens in Windows CI (not runnable locally on macOS)What this spike deliberately does NOT address
Samplecallback returnsAllDataAndRecordedfor parity with today's POTEL behaviour (record everything, Sentry samples at conversion). Moving Sentry's sampling into theSamplecallback is future work and needs answers for theTracesSamplercustomSamplingContextgap and multi-listener composition (the runtime takes the most permissive result).Instrumenter.OpenTelemetryconstructor guard is retained as-is for test parity; in a real implementation it inverts (Activity-based becomes the default, not the opt-in).#skip-changelog
🤖 Generated with Claude Code