From 5286daace5aaa14f8aff9b2d23e5a633ce84f4c3 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 17 Mar 2026 16:31:25 +1300 Subject: [PATCH 01/11] feat: Add option to exclude certain HTTP statuses from tracing resolves: #4739 - #4739 --- AGENTS.md | 13 ++ src/Sentry/BindableSentryOptions.cs | 2 + ...aceIgnoreStatusCodeTransactionProcessor.cs | 31 +++++ src/Sentry/SentryOptions.cs | 9 +- ...iApprovalTests.Run.DotNet10_0.verified.txt | 1 + ...piApprovalTests.Run.DotNet8_0.verified.txt | 1 + ...piApprovalTests.Run.DotNet9_0.verified.txt | 1 + ...noreStatusCodeTransactionProcessorTests.cs | 126 ++++++++++++++++++ 8 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs create mode 100644 test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs diff --git a/AGENTS.md b/AGENTS.md index ff986efde2..100a221a9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -139,6 +139,19 @@ gh pr view --json number -q '.number' - Maintain **backwards compatibility** — avoid breaking public API without strong justification - Platform-specific code lives in `src/Sentry/Platforms/` and is conditionally compiled +## Adding New Options (AOT Compatibility) + +`SentryOptions` is **not** bound directly from configuration. Instead, a parallel `BindableSentryOptions` class (`src/Sentry/BindableSentryOptions.cs`) exists for AOT-safe configuration binding. + +When adding a configurable property to any of the classes descending from `SentryOptions`: + +1. Add the property to `SentryOptions` as normal. +2. Add a matching **nullable** property to `BindableSentryOptions`. Use only simple/primitive types the source generator can handle. For complex types (e.g., `IReadOnlyList`), use a simpler surrogate (e.g., `List?`) and convert in `ApplyTo`. +3. Add a line in `BindableSentryOptions.ApplyTo`: `options.MyProp = MyProp ?? options.MyProp;` +4. Run the relevant bindable options test (e.g., `BindableSentryOptionsTests`) — the `BindableProperties_MatchOptionsProperties` test will fail if any bindable property is missing from the bindable class. + +The same pattern applies to `BindableSentryAspNetCoreOptions`, `BindableSentryMauiOptions`, `BindableSentryLoggingOptions`, and the platform-specific partial classes under `src/Sentry/Platforms/`. + ## Commit Attribution AI commits MUST include: diff --git a/src/Sentry/BindableSentryOptions.cs b/src/Sentry/BindableSentryOptions.cs index ebfba5c55a..e1210878d4 100644 --- a/src/Sentry/BindableSentryOptions.cs +++ b/src/Sentry/BindableSentryOptions.cs @@ -38,6 +38,7 @@ internal partial class BindableSentryOptions public string? CacheDirectoryPath { get; set; } public bool? CaptureFailedRequests { get; set; } public List? FailedRequestTargets { get; set; } + public List? TraceIgnoreStatusCodes { get; set; } public bool? DisableFileWrite { get; set; } public TimeSpan? InitCacheFlushTimeout { get; set; } public Dictionary? DefaultTags { get; set; } @@ -90,6 +91,7 @@ public void ApplyTo(SentryOptions options) options.CacheDirectoryPath = CacheDirectoryPath ?? options.CacheDirectoryPath; options.CaptureFailedRequests = CaptureFailedRequests ?? options.CaptureFailedRequests; options.FailedRequestTargets = FailedRequestTargets?.Select(s => new StringOrRegex(s)).ToList() ?? options.FailedRequestTargets; + options.TraceIgnoreStatusCodes = TraceIgnoreStatusCodes?.Select(code => new HttpStatusCodeRange(code)).ToList() ?? options.TraceIgnoreStatusCodes; options.DisableFileWrite = DisableFileWrite ?? options.DisableFileWrite; options.InitCacheFlushTimeout = InitCacheFlushTimeout ?? options.InitCacheFlushTimeout; options.DefaultTags = DefaultTags ?? options.DefaultTags; diff --git a/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs b/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs new file mode 100644 index 0000000000..819a816495 --- /dev/null +++ b/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs @@ -0,0 +1,31 @@ +using Sentry.Extensibility; +using Sentry.Internal.OpenTelemetry; + +namespace Sentry.Internal; + +internal class TraceIgnoreStatusCodeTransactionProcessor : ISentryTransactionProcessor +{ + private readonly SentryOptions _options; + + public TraceIgnoreStatusCodeTransactionProcessor(SentryOptions options) + { + _options = options; + } + + public SentryTransaction? Process(SentryTransaction transaction) + { + if (_options.TraceIgnoreStatusCodes.Count == 0) + { + return transaction; + } + + if (transaction.Data.TryGetValue(OtelSemanticConventions.AttributeHttpResponseStatusCode, out var statusCodeObj) + && statusCodeObj is int statusCode + && _options.TraceIgnoreStatusCodes.ContainsStatusCode(statusCode)) + { + return null; + } + + return transaction; + } +} diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index e8bd13d457..e5028f4778 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -857,6 +857,12 @@ public IDiagnosticLogger? DiagnosticLogger (500, 599) }; + /// + /// Transactions will be dropped if the HTTP Response status code matches any of the configured ranges. + /// Defaults to an empty collection (all transactions are captured regardless of status code). + /// + public IList TraceIgnoreStatusCodes { get; set; } = []; + // The default failed request target list will match anything, but adding to the list should clear that. private Lazy> _failedRequestTargets = new(() => new AutoClearingList( @@ -1318,7 +1324,8 @@ public SentryOptions() _lazyInstallationId = new(() => new InstallationIdHelper(this).TryGetInstallationId()); TransactionProcessorsProviders = new() { - () => TransactionProcessors ?? Enumerable.Empty() + () => TransactionProcessors ?? Enumerable.Empty(), + () => new[] { new TraceIgnoreStatusCodeTransactionProcessor(this) } }; _clientReportRecorder = new Lazy(() => new ClientReportRecorder(this)); diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index d614075852..94c19b4fc1 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -838,6 +838,7 @@ namespace Sentry public string SpotlightUrl { get; set; } public Sentry.StackTraceMode StackTraceMode { get; set; } public System.Collections.Generic.IList TagFilters { get; set; } + public System.Collections.Generic.IList TraceIgnoreStatusCodes { get; set; } public System.Collections.Generic.IList TracePropagationTargets { get; set; } public double? TracesSampleRate { get; set; } public System.Func? TracesSampler { get; set; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index d614075852..94c19b4fc1 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -838,6 +838,7 @@ namespace Sentry public string SpotlightUrl { get; set; } public Sentry.StackTraceMode StackTraceMode { get; set; } public System.Collections.Generic.IList TagFilters { get; set; } + public System.Collections.Generic.IList TraceIgnoreStatusCodes { get; set; } public System.Collections.Generic.IList TracePropagationTargets { get; set; } public double? TracesSampleRate { get; set; } public System.Func? TracesSampler { get; set; } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index d614075852..94c19b4fc1 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -838,6 +838,7 @@ namespace Sentry public string SpotlightUrl { get; set; } public Sentry.StackTraceMode StackTraceMode { get; set; } public System.Collections.Generic.IList TagFilters { get; set; } + public System.Collections.Generic.IList TraceIgnoreStatusCodes { get; set; } public System.Collections.Generic.IList TracePropagationTargets { get; set; } public double? TracesSampleRate { get; set; } public System.Func? TracesSampler { get; set; } diff --git a/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs b/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs new file mode 100644 index 0000000000..7ee0db4fda --- /dev/null +++ b/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs @@ -0,0 +1,126 @@ +using Sentry.Internal.OpenTelemetry; + +namespace Sentry.Tests; + +public class TraceIgnoreStatusCodeTransactionProcessorTests +{ + private static SentryOptions OptionsWithIgnoredCodes(params HttpStatusCodeRange[] ranges) + { + var options = new SentryOptions(); + foreach (var range in ranges) + { + options.TraceIgnoreStatusCodes.Add(range); + } + return options; + } + + private static SentryTransaction TransactionWithStatusCode(int statusCode) + { + var transaction = new SentryTransaction("name", "operation"); + transaction.SetData(OtelSemanticConventions.AttributeHttpResponseStatusCode, statusCode); + return transaction; + } + + [Fact] + public void Process_EmptyIgnoreList_ReturnsTransaction() + { + // Arrange + var options = new SentryOptions(); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = TransactionWithStatusCode(404); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeSameAs(transaction); + } + + [Fact] + public void Process_StatusCodeNotInIgnoreList_ReturnsTransaction() + { + // Arrange + var options = OptionsWithIgnoredCodes(404); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = TransactionWithStatusCode(200); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeSameAs(transaction); + } + + [Fact] + public void Process_StatusCodeInIgnoreList_ReturnsNull() + { + // Arrange + var options = OptionsWithIgnoredCodes(404); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = TransactionWithStatusCode(404); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void Process_StatusCodeInIgnoredRange_ReturnsNull() + { + // Arrange + var options = OptionsWithIgnoredCodes((400, 499)); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = TransactionWithStatusCode(404); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeNull(); + } + + [Fact] + public void Process_StatusCodeOutsideIgnoredRange_ReturnsTransaction() + { + // Arrange + var options = OptionsWithIgnoredCodes((400, 499)); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = TransactionWithStatusCode(500); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeSameAs(transaction); + } + + [Fact] + public void Process_NoStatusCodeExtra_ReturnsTransaction() + { + // Arrange + var options = OptionsWithIgnoredCodes(404); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = new SentryTransaction("name", "operation"); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeSameAs(transaction); + } + + [Fact] + public void Process_MultipleIgnoredCodes_MatchesAny() + { + // Arrange + var options = OptionsWithIgnoredCodes(404, 429); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + + // Act & Assert + processor.Process(TransactionWithStatusCode(404)).Should().BeNull(); + processor.Process(TransactionWithStatusCode(429)).Should().BeNull(); + processor.Process(TransactionWithStatusCode(200)).Should().NotBeNull(); + } +} From 5c17c8249aa0c04d3ebe05739c4ab7764e4260c1 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 17 Mar 2026 21:53:59 +1300 Subject: [PATCH 02/11] Windows verify --- test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 8d15f59117..46eb253298 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -825,6 +825,7 @@ namespace Sentry public string SpotlightUrl { get; set; } public Sentry.StackTraceMode StackTraceMode { get; set; } public System.Collections.Generic.IList TagFilters { get; set; } + public System.Collections.Generic.IList TraceIgnoreStatusCodes { get; set; } public System.Collections.Generic.IList TracePropagationTargets { get; set; } public double? TracesSampleRate { get; set; } public System.Func? TracesSampler { get; set; } From e9e00d96f083b1cb555edf9a72d037bcaaa2f05c Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Wed, 18 Mar 2026 13:02:50 +1300 Subject: [PATCH 03/11] Use collection operators from c# 12 --- src/Sentry/SentryOptions.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index e5028f4778..3fb6863725 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -1323,10 +1323,9 @@ public SentryOptions() SettingLocator = new SettingLocator(this); _lazyInstallationId = new(() => new InstallationIdHelper(this).TryGetInstallationId()); - TransactionProcessorsProviders = new() { - () => TransactionProcessors ?? Enumerable.Empty(), - () => new[] { new TraceIgnoreStatusCodeTransactionProcessor(this) } - }; + TransactionProcessorsProviders = [ + () => [.. TransactionProcessors ?? [], new TraceIgnoreStatusCodeTransactionProcessor(this)] + ]; _clientReportRecorder = new Lazy(() => new ClientReportRecorder(this)); From 09b4a44b9b70296c4aca11742aaa24dfaa5c3ccd Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 24 Mar 2026 14:41:01 +1300 Subject: [PATCH 04/11] Integration now only registered for relevant Sentry packages --- src/Sentry.AspNet/SentryAspNetOptionsExtensions.cs | 2 ++ .../WebAssemblyHostBuilderExtensions.cs | 2 ++ src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs | 3 +++ src/Sentry/SentryOptions.cs | 4 +--- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Sentry.AspNet/SentryAspNetOptionsExtensions.cs b/src/Sentry.AspNet/SentryAspNetOptionsExtensions.cs index 11a21cc9a7..14f9aa1154 100644 --- a/src/Sentry.AspNet/SentryAspNetOptionsExtensions.cs +++ b/src/Sentry.AspNet/SentryAspNetOptionsExtensions.cs @@ -1,6 +1,7 @@ using Sentry.AspNet.Internal; using Sentry.Extensibility; using Sentry.Infrastructure; +using Sentry.Internal; namespace Sentry.AspNet; @@ -36,6 +37,7 @@ public static SentryOptions AddAspNet(this SentryOptions options, RequestSize ma options.Release ??= SystemWebVersionLocator.Resolve(options, HttpContext.Current); options.AddEventProcessor(eventProcessor); options.AddDiagnosticSourceIntegration(); + options.AddTransactionProcessor(new TraceIgnoreStatusCodeTransactionProcessor(options)); return options; } diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs b/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs index 8985e9cc5e..25c3070e68 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs @@ -4,6 +4,7 @@ using Sentry; using Sentry.AspNetCore.Blazor.WebAssembly.Internal; using Sentry.Extensions.Logging; +using Sentry.Internal; // ReSharper disable once CheckNamespace - Discoverability namespace Microsoft.AspNetCore.Components.WebAssembly.Hosting; @@ -31,6 +32,7 @@ public static WebAssemblyHostBuilder UseSentry(this WebAssemblyHostBuilder build blazorOptions.RequestBodyCompressionLevel = CompressionLevel.NoCompression; // Since the WebAssemblyHost is a client-side application blazorOptions.IsGlobalModeEnabled = true; + blazorOptions.AddTransactionProcessor(new TraceIgnoreStatusCodeTransactionProcessor(blazorOptions)); }); builder.Services.AddSingleton, BlazorWasmOptionsSetup>(); diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs index 3f428f58f3..60f364e5e4 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; +using Sentry.Internal; namespace Sentry.AspNetCore; @@ -28,6 +29,7 @@ public override void Configure(SentryAspNetCoreOptions options) base.Configure(options); options.AddDiagnosticSourceIntegration(); options.DeduplicateUnhandledException(); + options.AddTransactionProcessor(new TraceIgnoreStatusCodeTransactionProcessor(options)); } } @@ -65,6 +67,7 @@ public void Configure(SentryAspNetCoreOptions options) bindable.ApplyTo(options); options.DeduplicateUnhandledException(); + options.AddTransactionProcessor(new TraceIgnoreStatusCodeTransactionProcessor(options)); } } #endif diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 282c553c5d..70fad7d405 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -1317,9 +1317,7 @@ public SentryOptions() SettingLocator = new SettingLocator(this); _lazyInstallationId = new(() => new InstallationIdHelper(this).TryGetInstallationId()); - TransactionProcessorsProviders = [ - () => [.. TransactionProcessors ?? [], new TraceIgnoreStatusCodeTransactionProcessor(this)] - ]; + TransactionProcessorsProviders = [ () => TransactionProcessors ?? []]; _clientReportRecorder = new Lazy(() => new ClientReportRecorder(this)); From 16e2759fda69f08979b97c3f5b1d228b94c8328f Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 24 Mar 2026 01:54:19 +0000 Subject: [PATCH 05/11] Format code --- modules/sentry-native | 2 +- src/Sentry/SentryOptions.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/sentry-native b/modules/sentry-native index 45e4a1d3f8..490ca8cbaa 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit 45e4a1d3f8078043eb23e3d695689a1a1fa24020 +Subproject commit 490ca8cbaaa5f232ab73bc909017c9faad396f63 diff --git a/src/Sentry/SentryOptions.cs b/src/Sentry/SentryOptions.cs index 70fad7d405..b6ccbb66bf 100644 --- a/src/Sentry/SentryOptions.cs +++ b/src/Sentry/SentryOptions.cs @@ -1317,7 +1317,7 @@ public SentryOptions() SettingLocator = new SettingLocator(this); _lazyInstallationId = new(() => new InstallationIdHelper(this).TryGetInstallationId()); - TransactionProcessorsProviders = [ () => TransactionProcessors ?? []]; + TransactionProcessorsProviders = [() => TransactionProcessors ?? []]; _clientReportRecorder = new Lazy(() => new ClientReportRecorder(this)); From dc9fd9777c7e28f88b9a24bc5a410e90c19dc040 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 24 Mar 2026 14:57:18 +1300 Subject: [PATCH 06/11] Added unit tests --- .../SentryAspNetOptionsExtensionsTests.cs | 9 +++++++++ .../SentryAspNetCoreOptionsSetupTests.cs | 9 +++++++++ test/Sentry.Tests/SentryOptionsTests.cs | 7 +++++++ 3 files changed, 25 insertions(+) diff --git a/test/Sentry.AspNet.Tests/SentryAspNetOptionsExtensionsTests.cs b/test/Sentry.AspNet.Tests/SentryAspNetOptionsExtensionsTests.cs index f0cf5bc5ca..7aa24a1018 100644 --- a/test/Sentry.AspNet.Tests/SentryAspNetOptionsExtensionsTests.cs +++ b/test/Sentry.AspNet.Tests/SentryAspNetOptionsExtensionsTests.cs @@ -1,4 +1,5 @@ using Sentry.AspNet.Internal; +using Sentry.Internal; namespace Sentry.AspNet.Tests; @@ -25,6 +26,14 @@ public void AddAspNet_EventProcessorsContainBodyExtractor() Assert.Contains(extractor.Extractors, p => p.GetType() == typeof(DefaultRequestPayloadExtractor)); } + [Fact] + public void AddAspNet_RegistersTraceIgnoreStatusCodeTransactionProcessor() + { + var options = new SentryOptions(); + options.AddAspNet(); + Assert.Contains(options.GetAllTransactionProcessors(), p => p is TraceIgnoreStatusCodeTransactionProcessor); + } + [Fact] public void AddAspNet_UsedMoreThanOnce_RegisterOnce() { diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs index 2f24d05416..916b2a615d 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; +using Sentry.Internal; #if NETCOREAPP3_1_OR_GREATER using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; @@ -30,6 +31,14 @@ public SentryAspNetCoreOptionsSetup GetSut() private readonly Fixture _fixture = new(); private readonly SentryAspNetCoreOptions _target = new(); + [Fact] + public void Configure_RegistersTraceIgnoreStatusCodeTransactionProcessor() + { + var sut = _fixture.GetSut(); + sut.Configure(_target); + Assert.Contains(_target.GetAllTransactionProcessors(), p => p is TraceIgnoreStatusCodeTransactionProcessor); + } + [Fact] public void Filters_KestrelApplicationEvent_NoException_Filtered() { diff --git a/test/Sentry.Tests/SentryOptionsTests.cs b/test/Sentry.Tests/SentryOptionsTests.cs index d563665bb4..270f8a2ab3 100644 --- a/test/Sentry.Tests/SentryOptionsTests.cs +++ b/test/Sentry.Tests/SentryOptionsTests.cs @@ -489,6 +489,13 @@ public void AddEventProcessorProvider_StoredInOptions() Assert.Contains(sut.GetAllEventProcessors(), actual => actual == second); } + [Fact] + public void GetAllTransactionProcessors_ByDefault_DoesNotIncludeTraceIgnoreStatusCodeTransactionProcessor() + { + var sut = new SentryOptions(); + Assert.DoesNotContain(sut.GetAllTransactionProcessors(), p => p is TraceIgnoreStatusCodeTransactionProcessor); + } + [Fact] public void AddTransactionProcessor_StoredInOptions() { From 6dd6af86f9bd656767df863a0cc1f350d0e34595 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 24 Mar 2026 15:27:06 +1300 Subject: [PATCH 07/11] Review suggestion --- .../TraceIgnoreStatusCodeTransactionProcessorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs b/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs index 7ee0db4fda..a5b6059f82 100644 --- a/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs +++ b/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs @@ -100,7 +100,7 @@ public void Process_StatusCodeOutsideIgnoredRange_ReturnsTransaction() public void Process_NoStatusCodeExtra_ReturnsTransaction() { // Arrange - var options = OptionsWithIgnoredCodes(404); + var options = OptionsWithIgnoredCodes((100, 599)); var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); var transaction = new SentryTransaction("name", "operation"); From 3b4f7ffd07440dfb2920b1a15b87cb53f9840493 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 24 Mar 2026 15:36:04 +1300 Subject: [PATCH 08/11] Ensure otel http transactions will be processed by the trace-ignore-status-codes --- src/Sentry.OpenTelemetry/SentrySpanProcessor.cs | 1 + test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs index 5aa9758c47..461dfd2038 100644 --- a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs +++ b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs @@ -239,6 +239,7 @@ public override void OnEnd(Activity data) if (statusCode is { } responseStatusCode) { transaction.Contexts.Response.StatusCode = responseStatusCode; + transaction.SetData(OtelSemanticConventions.AttributeHttpResponseStatusCode, responseStatusCode); } // Use the end timestamp from the activity data. diff --git a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs index c97e15f6a6..a26baac5ce 100644 --- a/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs +++ b/test/Sentry.OpenTelemetry.Tests/SentrySpanProcessorTests.cs @@ -463,6 +463,7 @@ public void OnEnd_Transaction_SetsResponseStatusCode() using (new AssertionScope()) { transaction.Contexts.Response.StatusCode.Should().Be(404); + transaction.Data.Should().Contain(OtelSemanticConventions.AttributeHttpResponseStatusCode, 404); } } From 9393cdca3091693b17fd394ef9d9984d7fd358d3 Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 24 Mar 2026 20:44:46 +1300 Subject: [PATCH 09/11] Fix processor for short status codes --- ...TraceIgnoreStatusCodeTransactionProcessor.cs | 4 ++-- ...IgnoreStatusCodeTransactionProcessorTests.cs | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs b/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs index 819a816495..f80847877a 100644 --- a/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs +++ b/src/Sentry/Internal/TraceIgnoreStatusCodeTransactionProcessor.cs @@ -20,8 +20,8 @@ public TraceIgnoreStatusCodeTransactionProcessor(SentryOptions options) } if (transaction.Data.TryGetValue(OtelSemanticConventions.AttributeHttpResponseStatusCode, out var statusCodeObj) - && statusCodeObj is int statusCode - && _options.TraceIgnoreStatusCodes.ContainsStatusCode(statusCode)) + && statusCodeObj is IConvertible convertible + && _options.TraceIgnoreStatusCodes.ContainsStatusCode(convertible.ToInt32(null))) { return null; } diff --git a/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs b/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs index a5b6059f82..052e948b47 100644 --- a/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs +++ b/test/Sentry.Tests/TraceIgnoreStatusCodeTransactionProcessorTests.cs @@ -111,6 +111,23 @@ public void Process_NoStatusCodeExtra_ReturnsTransaction() result.Should().BeSameAs(transaction); } + [Fact] + public void Process_StatusCodeStoredAsShort_IsDropped() + { + // Regression test: OTel stores the status code as short, not int. + // Arrange + var options = OptionsWithIgnoredCodes(404); + var processor = new TraceIgnoreStatusCodeTransactionProcessor(options); + var transaction = new SentryTransaction("name", "operation"); + transaction.SetData(OtelSemanticConventions.AttributeHttpResponseStatusCode, (short)404); + + // Act + var result = processor.Process(transaction); + + // Assert + result.Should().BeNull(); + } + [Fact] public void Process_MultipleIgnoredCodes_MatchesAny() { From e90dd9b6389d18eaee49dce69e29170b7e53db2e Mon Sep 17 00:00:00 2001 From: James Crosswell Date: Tue, 24 Mar 2026 21:03:04 +1300 Subject: [PATCH 10/11] Set response status codes on ASP.NET so trace ignore status codes work --- src/Sentry.AspNet/HttpContextExtensions.cs | 6 ++++-- test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Sentry.AspNet/HttpContextExtensions.cs b/src/Sentry.AspNet/HttpContextExtensions.cs index 54aa715379..6ebadd582c 100644 --- a/src/Sentry.AspNet/HttpContextExtensions.cs +++ b/src/Sentry.AspNet/HttpContextExtensions.cs @@ -1,5 +1,6 @@ using Sentry.Extensibility; using Sentry.Internal; +using Sentry.Internal.OpenTelemetry; using Sentry.Protocol; namespace Sentry.AspNet; @@ -145,7 +146,8 @@ public static void FinishSentryTransaction(this HttpContext httpContext) return; } - var status = SpanStatusConverter.FromHttpStatusCode(httpContext.Response.StatusCode); - transaction.Finish(status); + var statusCode = httpContext.Response.StatusCode; + transaction.SetData(OtelSemanticConventions.AttributeHttpResponseStatusCode, statusCode); + transaction.Finish(SpanStatusConverter.FromHttpStatusCode(statusCode)); } } diff --git a/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs b/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs index 5cf274b260..220841b771 100644 --- a/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs +++ b/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs @@ -1,4 +1,5 @@ using HttpCookie = System.Web.HttpCookie; +using Sentry.Internal.OpenTelemetry; namespace Sentry.AspNet.Tests; @@ -73,6 +74,7 @@ public void FinishSentryTransaction_FinishesTransaction() // Assert transaction.IsFinished.Should().BeTrue(); transaction.Status.Should().Be(SpanStatus.NotFound); + transaction.Data.Should().Contain(OtelSemanticConventions.AttributeHttpResponseStatusCode, 404); } [Fact] From a2e315743fecb96b9a830242472e617e633024b3 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 24 Mar 2026 23:34:11 +0000 Subject: [PATCH 11/11] Format code --- test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs b/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs index 220841b771..7c78c01b84 100644 --- a/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs +++ b/test/Sentry.AspNet.Tests/HttpContextExtensionsTests.cs @@ -1,5 +1,5 @@ -using HttpCookie = System.Web.HttpCookie; using Sentry.Internal.OpenTelemetry; +using HttpCookie = System.Web.HttpCookie; namespace Sentry.AspNet.Tests;