From 2ded09202bc5f90c2c533c75bc61beadf5b8c179 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Mon, 2 Jun 2025 20:05:45 +0200 Subject: [PATCH 01/12] Add non allocating ConfigureScope overload --- src/Sentry/Extensibility/DisabledHub.cs | 12 ++++ src/Sentry/Extensibility/HubAdapter.cs | 14 ++++ src/Sentry/ISentryScopeManager.cs | 23 ++++-- src/Sentry/Internal/Hub.cs | 24 +++++++ src/Sentry/Internal/SentryScopeManager.cs | 12 ++++ src/Sentry/SentrySdk.cs | 37 +++++++++- ...piApprovalTests.Run.DotNet8_0.verified.txt | 8 +++ ...piApprovalTests.Run.DotNet9_0.verified.txt | 8 +++ .../ApiApprovalTests.Run.Net4_8.verified.txt | 8 +++ test/Sentry.Tests/SentrySdkTests.cs | 72 +++++++++++++++++++ 10 files changed, 212 insertions(+), 6 deletions(-) diff --git a/src/Sentry/Extensibility/DisabledHub.cs b/src/Sentry/Extensibility/DisabledHub.cs index 86eb6a1df5..339c295233 100644 --- a/src/Sentry/Extensibility/DisabledHub.cs +++ b/src/Sentry/Extensibility/DisabledHub.cs @@ -30,11 +30,23 @@ public void ConfigureScope(Action configureScope) { } + /// + /// No-Op. + /// + public void ConfigureScope(Action configureScope, TArg arg) + { + } + /// /// No-Op. /// public Task ConfigureScopeAsync(Func configureScope) => Task.CompletedTask; + /// + /// No-Op. + /// + public Task ConfigureScopeAsync(Func configureScope, TArg arg) => Task.CompletedTask; + /// /// No-Op. /// diff --git a/src/Sentry/Extensibility/HubAdapter.cs b/src/Sentry/Extensibility/HubAdapter.cs index fb9196b718..c5953eeefa 100644 --- a/src/Sentry/Extensibility/HubAdapter.cs +++ b/src/Sentry/Extensibility/HubAdapter.cs @@ -39,6 +39,13 @@ private HubAdapter() { } public void ConfigureScope(Action configureScope) => SentrySdk.ConfigureScope(configureScope); + /// + /// Forwards the call to . + /// + [DebuggerStepThrough] + public void ConfigureScope(Action configureScope, TArg arg) + => SentrySdk.ConfigureScope(configureScope, arg); + /// /// Forwards the call to . /// @@ -46,6 +53,13 @@ public void ConfigureScope(Action configureScope) public Task ConfigureScopeAsync(Func configureScope) => SentrySdk.ConfigureScopeAsync(configureScope); + /// + /// Forwards the call to . + /// + [DebuggerStepThrough] + public Task ConfigureScopeAsync(Func configureScope, TArg arg) + => SentrySdk.ConfigureScopeAsync(configureScope, arg); + /// /// Forwards the call to . /// diff --git a/src/Sentry/ISentryScopeManager.cs b/src/Sentry/ISentryScopeManager.cs index 25b58069db..d48f9ad4c6 100644 --- a/src/Sentry/ISentryScopeManager.cs +++ b/src/Sentry/ISentryScopeManager.cs @@ -10,18 +10,33 @@ namespace Sentry; public interface ISentryScopeManager { /// - /// Configures the current scope. + /// Configures the current scope through the callback. /// - /// The configure scope. + /// The configure scope callback. public void ConfigureScope(Action configureScope); /// - /// Asynchronously configure the current scope. + /// Configures the current scope through the callback. /// - /// The configure scope. + /// The configure scope callback. + /// The argument to pass to the configure scope callback. + public void ConfigureScope(Action configureScope, TArg arg); + + /// + /// Configures the current scope through the callback asynchronously. + /// + /// The configure scope callback. /// A task that completes when the callback is done or a completed task if the SDK is disabled. public Task ConfigureScopeAsync(Func configureScope); + /// + /// Configures the current scope through the callback asynchronously. + /// + /// The configure scope callback. + /// The argument to pass to the configure scope callback. + /// A task that completes when the callback is done or a completed task if the SDK is disabled. + public Task ConfigureScopeAsync(Func configureScope, TArg arg); + /// /// Sets a tag on the current scope. /// diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 9176ca3ea4..001252327b 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -99,6 +99,18 @@ public void ConfigureScope(Action configureScope) } } + public void ConfigureScope(Action configureScope, TArg arg) + { + try + { + ScopeManager.ConfigureScope(configureScope, arg); + } + catch (Exception e) + { + _options.LogError(e, "Failure to ConfigureScope"); + } + } + public async Task ConfigureScopeAsync(Func configureScope) { try @@ -111,6 +123,18 @@ public async Task ConfigureScopeAsync(Func configureScope) } } + public async Task ConfigureScopeAsync(Func configureScope, TArg arg) + { + try + { + await ScopeManager.ConfigureScopeAsync(configureScope, arg).ConfigureAwait(false); + } + catch (Exception e) + { + _options.LogError(e, "Failure to ConfigureScopeAsync"); + } + } + public void SetTag(string key, string value) => ScopeManager.SetTag(key, value); public void UnsetTag(string key) => ScopeManager.UnsetTag(key); diff --git a/src/Sentry/Internal/SentryScopeManager.cs b/src/Sentry/Internal/SentryScopeManager.cs index 61e68ac1f4..01fdba0303 100644 --- a/src/Sentry/Internal/SentryScopeManager.cs +++ b/src/Sentry/Internal/SentryScopeManager.cs @@ -38,12 +38,24 @@ public void ConfigureScope(Action? configureScope) configureScope?.Invoke(scope); } + public void ConfigureScope(Action? configureScope, TArg arg) + { + var (scope, _) = GetCurrent(); + configureScope?.Invoke(scope, arg); + } + public Task ConfigureScopeAsync(Func? configureScope) { var (scope, _) = GetCurrent(); return configureScope?.Invoke(scope) ?? Task.CompletedTask; } + public Task ConfigureScopeAsync(Func? configureScope, TArg arg) + { + var (scope, _) = GetCurrent(); + return configureScope?.Invoke(scope, arg) ?? Task.CompletedTask; + } + public void SetTag(string key, string value) { var (scope, _) = GetCurrent(); diff --git a/src/Sentry/SentrySdk.cs b/src/Sentry/SentrySdk.cs index decd76f6d1..d00b002576 100644 --- a/src/Sentry/SentrySdk.cs +++ b/src/Sentry/SentrySdk.cs @@ -381,14 +381,47 @@ public static void ConfigureScope(Action configureScope) => CurrentHub.ConfigureScope(configureScope); /// - /// Configures the scope asynchronously. + /// Configures the scope through the callback. + /// + /// + /// object someValue = ...; + /// SentrySdk.ConfigureScope(static (scope, arg) => scope.SetExtra("key", arg), someValue); + /// + /// /// /// The configure scope callback. - /// The Id of the event. + /// The argument to pass to the configure scope callback. + public static void ConfigureScope(Action configureScope, TArg arg) + => CurrentHub.ConfigureScope(configureScope, arg); + + /// + /// Configures the scope through the callback asynchronously. + /// + /// The configure scope callback. + /// A task that completes when the callback is done or a completed task if the SDK is disabled. [DebuggerStepThrough] public static Task ConfigureScopeAsync(Func configureScope) => CurrentHub.ConfigureScopeAsync(configureScope); + /// + /// Configures the scope through the callback asynchronously. + /// + /// + /// object someValue = ...; + /// SentrySdk.ConfigureScopeAsync(static async (scope, arg) => + /// { + /// scope.SetExtra("key", arg); + /// }, someValue); + /// + /// + /// + /// The configure scope callback. + /// The argument to pass to the configure scope callback. + /// A task that completes when the callback is done or a completed task if the SDK is disabled. + [DebuggerStepThrough] + public static Task ConfigureScopeAsync(Func configureScope, TArg arg) + => CurrentHub.ConfigureScopeAsync(configureScope, arg); + /// /// Sets a tag on the current scope. /// diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index b9ad168f30..82d7cdf7f7 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -256,7 +256,9 @@ namespace Sentry { void BindClient(Sentry.ISentryClient client); void ConfigureScope(System.Action configureScope); + void ConfigureScope(System.Action configureScope, TArg arg); System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope); + System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg); System.IDisposable PushScope(); System.IDisposable PushScope(TState state); void SetTag(string key, string value); @@ -846,7 +848,9 @@ namespace Sentry public static void CauseCrash(Sentry.CrashType crashType) { } public static void Close() { } public static void ConfigureScope(System.Action configureScope) { } + public static void ConfigureScope(System.Action configureScope, TArg arg) { } public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public static void EndSession(Sentry.SessionEndStatus status = 0) { } @@ -1358,7 +1362,9 @@ namespace Sentry.Extensibility [System.Obsolete("Use CaptureFeedback instead.")] public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public void Dispose() { } @@ -1407,7 +1413,9 @@ namespace Sentry.Extensibility [System.Obsolete("Use CaptureFeedback instead.")] public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public void EndSession(Sentry.SessionEndStatus status = 0) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index b9ad168f30..82d7cdf7f7 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -256,7 +256,9 @@ namespace Sentry { void BindClient(Sentry.ISentryClient client); void ConfigureScope(System.Action configureScope); + void ConfigureScope(System.Action configureScope, TArg arg); System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope); + System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg); System.IDisposable PushScope(); System.IDisposable PushScope(TState state); void SetTag(string key, string value); @@ -846,7 +848,9 @@ namespace Sentry public static void CauseCrash(Sentry.CrashType crashType) { } public static void Close() { } public static void ConfigureScope(System.Action configureScope) { } + public static void ConfigureScope(System.Action configureScope, TArg arg) { } public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public static void EndSession(Sentry.SessionEndStatus status = 0) { } @@ -1358,7 +1362,9 @@ namespace Sentry.Extensibility [System.Obsolete("Use CaptureFeedback instead.")] public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public void Dispose() { } @@ -1407,7 +1413,9 @@ namespace Sentry.Extensibility [System.Obsolete("Use CaptureFeedback instead.")] public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public void EndSession(Sentry.SessionEndStatus status = 0) { } diff --git a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 97dac16884..701eaa25b3 100644 --- a/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -244,7 +244,9 @@ namespace Sentry { void BindClient(Sentry.ISentryClient client); void ConfigureScope(System.Action configureScope); + void ConfigureScope(System.Action configureScope, TArg arg); System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope); + System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg); System.IDisposable PushScope(); System.IDisposable PushScope(TState state); void SetTag(string key, string value); @@ -827,7 +829,9 @@ namespace Sentry public static void CauseCrash(Sentry.CrashType crashType) { } public static void Close() { } public static void ConfigureScope(System.Action configureScope) { } + public static void ConfigureScope(System.Action configureScope, TArg arg) { } public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public static System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public static Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public static Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public static void EndSession(Sentry.SessionEndStatus status = 0) { } @@ -1339,7 +1343,9 @@ namespace Sentry.Extensibility [System.Obsolete("Use CaptureFeedback instead.")] public void CaptureUserFeedback(Sentry.UserFeedback userFeedback) { } public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public void Dispose() { } @@ -1388,7 +1394,9 @@ namespace Sentry.Extensibility [System.Obsolete("Use CaptureFeedback instead.")] public void CaptureUserFeedback(Sentry.UserFeedback sentryUserFeedback) { } public void ConfigureScope(System.Action configureScope) { } + public void ConfigureScope(System.Action configureScope, TArg arg) { } public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope) { } + public System.Threading.Tasks.Task ConfigureScopeAsync(System.Func configureScope, TArg arg) { } public Sentry.TransactionContext ContinueTrace(Sentry.SentryTraceHeader? traceHeader, Sentry.BaggageHeader? baggageHeader, string? name = null, string? operation = null) { } public Sentry.TransactionContext ContinueTrace(string? traceHeader, string? baggageHeader, string? name = null, string? operation = null) { } public void EndSession(Sentry.SessionEndStatus status = 0) { } diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index 49ca287d59..d3ccdab357 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -439,6 +439,36 @@ public void ConfigureScope_Sync_CallbackNeverInvoked() Assert.False(invoked); } + [Fact] + public void ConfigureScope_SyncWithArg_CallbackNeverInvoked() + { + var invoked = false; + SentrySdk.ConfigureScope((_,_) => invoked = true, "arg"); + Assert.False(invoked); + } + + [Fact] + public void ConfigureScope_SyncWithArg_ArgIsUsed() + { + using var _ = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.AutoSessionTracking = false; + o.BackgroundWorker = Substitute.For(); + o.InitNativeSdks = false; + }); + + const string key = "key"; + const string arg = "arg"; + + SentrySdk.ConfigureScope((s,a) => s.SetTag(key, a), arg); + + string actual = null; + SentrySdk.ConfigureScope(s => actual = s.Tags[key]); + + Assert.Equal(arg, actual); + } + [SkippableFact] public async Task ConfigureScope_OnTask_PropagatedToCaller() { @@ -671,6 +701,48 @@ await SentrySdk.ConfigureScopeAsync(_ => Assert.False(invoked); } + [Fact] + public async Task ConfigureScope_AsyncWithArg_CallbackNeverInvoked() + { + var invoked = false; + await SentrySdk.ConfigureScopeAsync((_,_) => + { + invoked = true; + return Task.CompletedTask; + }, "arg"); + Assert.False(invoked); + } + + [Fact] + public async Task ConfigureScope_AsyncWithArg_ArgIsUsed() + { + using var _ = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.AutoSessionTracking = false; + o.BackgroundWorker = Substitute.For(); + o.InitNativeSdks = false; + }); + + const string key = "key"; + const string arg = "arg"; + + await SentrySdk.ConfigureScopeAsync((s, a) => + { + s.SetTag(key, a); + return Task.CompletedTask; + }, arg); + + string actual = null; + await SentrySdk.ConfigureScopeAsync(s => + { + actual = s.Tags[key]; + return Task.CompletedTask; + }); + + Assert.Equal(arg, actual); + } + [Fact] public void CaptureEvent_Instance_NoOp() => SentrySdk.CaptureEvent(new SentryEvent()); From 3c74a665c1239aa15d1e243401187f7e4204203e Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Tue, 3 Jun 2025 20:42:43 +0200 Subject: [PATCH 02/12] Use non allocating ConfigureScope overload --- src/Sentry.AspNetCore/SentryMiddleware.cs | 18 +++++++++--------- .../SentryTracingMiddleware.cs | 5 +---- .../SentryLoggerProvider.cs | 2 +- src/Sentry.OpenTelemetry/AspNetCoreEnricher.cs | 6 +++--- .../SentrySpanProcessor.cs | 2 +- src/Sentry/HubExtensions.cs | 13 +++++-------- src/Sentry/Internal/Hub.cs | 2 +- src/Sentry/Internal/UnsampledTransaction.cs | 6 +++--- src/Sentry/TransactionTracer.cs | 6 +++--- 9 files changed, 27 insertions(+), 33 deletions(-) diff --git a/src/Sentry.AspNetCore/SentryMiddleware.cs b/src/Sentry.AspNetCore/SentryMiddleware.cs index 33fca3e622..0b710a99b3 100644 --- a/src/Sentry.AspNetCore/SentryMiddleware.cs +++ b/src/Sentry.AspNetCore/SentryMiddleware.cs @@ -118,7 +118,7 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) context.Items.TryAdd(BaggageHeaderItemKey, baggageHeader); context.Items.TryAdd(TransactionContextItemKey, transactionContext); - hub.ConfigureScope(scope => + hub.ConfigureScope(static (scope, arg) => { // At the point lots of stuff from the request are not yet filled // Identity for example is added later on in the pipeline @@ -131,16 +131,16 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) // when the event fires. Use `activeScope`, not `scope` or `hub`. scope.OnEvaluating += (_, activeScope) => { - SyncOptionsScope(activeScope); - PopulateScope(context, activeScope); + arg.middleware.SyncOptionsScope(activeScope); + arg.middleware.PopulateScope(arg.context, activeScope); }; - }); + }, (middleware: this, context)); // Pre-create the Sentry Event ID and save it on the scope it so it's available throughout the pipeline, // even if there's no event actually being sent to Sentry. This allows for things like a custom exception // handler page to access the event ID, enabling user feedback, etc. var eventId = SentryId.Create(); - hub.ConfigureScope(scope => scope.LastEventId = eventId); + hub.ConfigureScope(static (scope, eventId) => scope.LastEventId = eventId, eventId); try { @@ -167,7 +167,7 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) { // The middleware pipeline finishes up before the Otel Activity.OnEnd callback is invoked so we need // so save a copy of the scope that can be restored by our SentrySpanProcessor - hub.ConfigureScope(scope => activity.SetFused(scope)); + hub.ConfigureScope(static (scope, activity) => activity.SetFused(scope), activity); } // When an exception was handled by other component (i.e: UseExceptionHandler feature). @@ -179,12 +179,12 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) "The web server likely returned a customized error page as a result of this exception."; #if NET6_0_OR_GREATER - hub.ConfigureScope(scope => + hub.ConfigureScope(static (scope, arg) => { scope.ExceptionProcessors.Add( - new ExceptionHandlerFeatureProcessor(originalMethod, exceptionFeature) + new ExceptionHandlerFeatureProcessor(arg.originalMethod, arg.exceptionFeature) ); - }); + }, (originalMethod, exceptionFeature)); #endif CaptureException(exceptionFeature.Error, eventId, "IExceptionHandlerFeature", description); } diff --git a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs index 8c192a89a1..96f5e5a95b 100644 --- a/src/Sentry.AspNetCore/SentryTracingMiddleware.cs +++ b/src/Sentry.AspNetCore/SentryTracingMiddleware.cs @@ -130,10 +130,7 @@ public async Task InvokeAsync(HttpContext context) // Expose the transaction on the scope so that the user // can retrieve it and start child spans off of it. - hub.ConfigureScope(scope => - { - scope.Transaction = transaction; - }); + hub.ConfigureScope(static (scope, transaction) => scope.Transaction = transaction, transaction); Exception? exception = null; try diff --git a/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs b/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs index 94ca40dd80..454f070dd8 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs @@ -48,7 +48,7 @@ internal SentryLoggerProvider( if (hub.IsEnabled) { _scope = hub.PushScope(); - hub.ConfigureScope(s => + hub.ConfigureScope(static s => { if (s.Sdk is { } sdk) { diff --git a/src/Sentry.OpenTelemetry/AspNetCoreEnricher.cs b/src/Sentry.OpenTelemetry/AspNetCoreEnricher.cs index 6d2c53991a..832da26308 100644 --- a/src/Sentry.OpenTelemetry/AspNetCoreEnricher.cs +++ b/src/Sentry.OpenTelemetry/AspNetCoreEnricher.cs @@ -10,13 +10,13 @@ public void Enrich(ISpan span, Activity activity, IHub hub, SentryOptions? optio { if (options?.SendDefaultPii is true) { - hub.ConfigureScope(scope => + hub.ConfigureScope(static (scope, enricher) => { - if (!scope.HasUser() && _userFactory.Create() is { } user) + if (!scope.HasUser() && enricher._userFactory.Create() is { } user) { scope.User = user; } - }); + }, this); } } } diff --git a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs index b0b747e2d4..eba966138a 100644 --- a/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs +++ b/src/Sentry.OpenTelemetry/SentrySpanProcessor.cs @@ -172,7 +172,7 @@ private void CreateRootSpan(Activity data) tracer.Contexts.Trace.Origin = OpenTelemetryOrigin; tracer.StartTimestamp = data.StartTimeUtc; } - _hub.ConfigureScope(scope => scope.Transaction = transaction); + _hub.ConfigureScope(static (scope, transaction) => scope.Transaction = transaction, transaction); transaction.SetFused(data); _map[data.SpanId] = transaction; } diff --git a/src/Sentry/HubExtensions.cs b/src/Sentry/HubExtensions.cs index a63ef45aa4..eb9608c160 100644 --- a/src/Sentry/HubExtensions.cs +++ b/src/Sentry/HubExtensions.cs @@ -56,14 +56,11 @@ public static ITransactionTracer StartTransaction( /// public static ISpan StartSpan(this IHub hub, string operation, string description) { - ITransactionTracer? currentTransaction = null; - hub.ConfigureScope(s => currentTransaction = s.Transaction); - return currentTransaction is { } transaction + return hub.GetTransaction() is { } transaction ? transaction.StartChild(operation, description) : hub.StartTransaction(operation, description); // this is actually in the wrong order but changing it may break other things } - /// /// Adds a breadcrumb to the current scope. /// @@ -158,8 +155,8 @@ public static void AddBreadcrumb( } hub.ConfigureScope( - s => s.AddBreadcrumb(breadcrumb, hint ?? new SentryHint()) - ); + static(s, arg) => s.AddBreadcrumb(arg.breadcrumb, arg.hint ?? new SentryHint()), + (breadcrumb, hint)); } /// @@ -175,13 +172,13 @@ public static void AddBreadcrumb( /// like Loggers which guarantee log messages are not lost. /// [EditorBrowsable(EditorBrowsableState.Never)] - public static void LockScope(this IHub hub) => hub.ConfigureScope(c => c.Locked = true); + public static void LockScope(this IHub hub) => hub.ConfigureScope(static s => s.Locked = true); /// /// Unlocks the current scope to allow subsequent calls to create new scopes. /// [EditorBrowsable(EditorBrowsableState.Never)] - public static void UnlockScope(this IHub hub) => hub.ConfigureScope(c => c.Locked = false); + public static void UnlockScope(this IHub hub) => hub.ConfigureScope(static s => s.Locked = false); private sealed class LockedScope : IDisposable { diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 001252327b..046ca5cc18 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -296,7 +296,7 @@ public TransactionContext ContinueTrace( string? operation = null) { var propagationContext = SentryPropagationContext.CreateFromHeaders(_options.DiagnosticLogger, traceHeader, baggageHeader, _replaySession); - ConfigureScope(scope => scope.SetPropagationContext(propagationContext)); + ConfigureScope(static (scope, propagationContext) => scope.SetPropagationContext(propagationContext), propagationContext); return new TransactionContext( name: name ?? string.Empty, diff --git a/src/Sentry/Internal/UnsampledTransaction.cs b/src/Sentry/Internal/UnsampledTransaction.cs index 20b8ad013c..a14698f7f3 100644 --- a/src/Sentry/Internal/UnsampledTransaction.cs +++ b/src/Sentry/Internal/UnsampledTransaction.cs @@ -64,11 +64,11 @@ public override void Finish() // Clear the transaction from the scope and regenerate the Propagation Context, so new events don't have a // trace context that is "older" than the transaction that just finished - _hub.ConfigureScope(scope => + _hub.ConfigureScope(static (scope, transactionTracer) => { - scope.ResetTransaction(this); + scope.ResetTransaction(transactionTracer); scope.SetPropagationContext(new SentryPropagationContext()); - }); + }, this); // Record the discarded events var spanCount = Spans.Count + 1; // 1 for each span + 1 for the transaction itself diff --git a/src/Sentry/TransactionTracer.cs b/src/Sentry/TransactionTracer.cs index d3f60ceb5f..5966a224da 100644 --- a/src/Sentry/TransactionTracer.cs +++ b/src/Sentry/TransactionTracer.cs @@ -386,11 +386,11 @@ public void Finish() // Clear the transaction from the scope and regenerate the Propagation Context // We do this so new events don't have a trace context that is "older" than the transaction that just finished - _hub.ConfigureScope(scope => + _hub.ConfigureScope(static (scope, transactionTracer) => { - scope.ResetTransaction(this); + scope.ResetTransaction(transactionTracer); scope.SetPropagationContext(new SentryPropagationContext()); - }); + }, this); // Client decides whether to discard this transaction based on sampling _hub.CaptureTransaction(new SentryTransaction(this)); From c53044e90fcc5aa682ae0c78e7dfc12e5c6dc516 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Wed, 4 Jun 2025 17:38:56 +0200 Subject: [PATCH 03/12] Prevent closure allocation in GetTransaction --- src/Sentry/HubExtensions.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Sentry/HubExtensions.cs b/src/Sentry/HubExtensions.cs index eb9608c160..d6ab122387 100644 --- a/src/Sentry/HubExtensions.cs +++ b/src/Sentry/HubExtensions.cs @@ -242,9 +242,14 @@ internal static ITransactionTracer StartTransaction( _ => hub.StartTransaction(context, customSamplingContext) }; - internal static ITransactionTracer? GetTransaction(this IHub hub) + internal static ITransactionTracer ? GetTransaction(this IHub hub) { - ITransactionTracer? transaction = null; + if (hub is Hub fullHub) + { + return fullHub.ScopeManager.GetCurrent().Key.Transaction; + } + + ITransactionTracer ? transaction = null; hub.ConfigureScope(scope => transaction = scope.Transaction); return transaction; } From 96c41ee90a580c43ea6bb78b338f89d313892b16 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Wed, 4 Jun 2025 17:43:48 +0200 Subject: [PATCH 04/12] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67fdc8d632..db228e8ecd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Rename MemoryInfo.AllocatedBytes to MemoryInfo.TotalAllocatedBytes ([#4243](https://github.com/getsentry/sentry-dotnet/pull/4243)) - Replace libcurl with .NET HttpClient for sentry-native ([#4222](https://github.com/getsentry/sentry-dotnet/pull/4222)) +- Added non allocating SentrySdk.ConfigureScope overload ([#4244](https://github.com/getsentry/sentry-dotnet/pull/4244)) ### Fixes From 2456363e5c1f86ca710b2b1134f718ba93a65a1b Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Wed, 4 Jun 2025 19:40:20 +0200 Subject: [PATCH 05/12] Substitute both ConfigureScope overloads in tests --- .../SentryMiddlewareTests.cs | 28 ++++++------------- .../SentryQueryLoggerTests.cs | 2 +- .../SentryLoggerTests.cs | 2 +- .../SentryAppenderTests.cs | 2 +- test/Sentry.NLog.Tests/SentryTargetTests.cs | 2 +- .../AspNetCoreEnricherTests.cs | 2 +- test/Sentry.Serilog.Tests/SentrySinkTests.cs | 2 +- .../Sentry.Testing/HubSubstituteExtensions.cs | 23 +++++++++++++++ .../Extensibility/HubAdapterTests.cs | 3 +- test/Sentry.Tests/HubExtensionsTests.cs | 3 +- .../Protocol/SentryTransactionTests.cs | 8 +++--- .../SentryGraphQlHttpMessageHandlerTests.cs | 3 +- .../SentryHttpMessageHandlerTests.cs | 6 ++-- 13 files changed, 47 insertions(+), 39 deletions(-) create mode 100644 test/Sentry.Testing/HubSubstituteExtensions.cs diff --git a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs index d6cedd9b73..e15e1fb280 100644 --- a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs @@ -31,8 +31,7 @@ private class Fixture public Fixture() { Scope = new(); - Hub.When(hub => hub.ConfigureScope(Arg.Any>())) - .Do(callback => callback.Arg>().Invoke(Scope)); + Hub.SubstituteConfigureScope(Scope); Hub.When(hub => hub.CaptureEvent(Arg.Any(), Arg.Any())) .Do(_ => Scope.Evaluate()); @@ -157,23 +156,16 @@ public async Task InvokeAsync_ScopePushed_BeforeConfiguringScope() [Fact] public async Task InvokeAsync_LocksScope_BeforeConfiguringScope() { - var verified = false; - var scope = new Scope(); - _fixture.Hub - .When(h => h.ConfigureScope(Arg.Any>())) - .Do(Callback - .First(c => c.ArgAt>(0)(scope)) - .Then(_ => - { - Assert.True(scope.Locked); - verified = true; - })); + var scopeLocked = false; + _fixture.Hub.When(h => h.PushAndLockScope()).Do(_ => scopeLocked = true); + _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>())) + .Do(_ => Assert.True(scopeLocked)); var sut = _fixture.GetSut(); await sut.InvokeAsync(_fixture.HttpContext, _fixture.RequestDelegate); - Assert.True(verified); + _fixture.Hub.Received().ConfigureScope(Arg.Any>()); } [Fact] @@ -182,8 +174,7 @@ public async Task InvokeAsync_OnEvaluating_HttpContextDataSet() const string expectedTraceIdentifier = "trace id"; _ = _fixture.HttpContext.TraceIdentifier.Returns(expectedTraceIdentifier); var scope = new Scope(); - _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(scope)); + _fixture.Hub.SubstituteConfigureScope(scope); var sut = _fixture.GetSut(); @@ -200,8 +191,7 @@ public async Task InvokeAsync_OnEvaluatingClonedScope_HttpContextDataSet() const string expectedTraceIdentifier = "trace id"; _ = _fixture.HttpContext.TraceIdentifier.Returns(expectedTraceIdentifier); var scope = new Scope(); - _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(scope)); + _fixture.Hub.SubstituteConfigureScope(scope); var sut = _fixture.GetSut(); @@ -664,7 +654,7 @@ public async Task InvokeAsync_InstrumenterOpenTelemetry_SavesScope() // Arrange _fixture.Options.Instrumenter = Instrumenter.OpenTelemetry; var scope = new Scope(); - _fixture.Hub.ConfigureScope(Arg.Do>(action => action.Invoke(scope))); + _fixture.Hub.SubstituteConfigureScope(scope); var sut = _fixture.GetSut(); var activity = new Activity("test").Start(); diff --git a/test/Sentry.EntityFramework.Tests/SentryQueryLoggerTests.cs b/test/Sentry.EntityFramework.Tests/SentryQueryLoggerTests.cs index ec1686fa7e..c961a309eb 100644 --- a/test/Sentry.EntityFramework.Tests/SentryQueryLoggerTests.cs +++ b/test/Sentry.EntityFramework.Tests/SentryQueryLoggerTests.cs @@ -8,7 +8,7 @@ public void Log_QueryLogger_CaptureEvent() var scope = new Scope(new SentryOptions()); var hub = Substitute.For(); hub.IsEnabled.Returns(true); - hub.ConfigureScope(Arg.Invoke(scope)); + hub.SubstituteConfigureScope(scope); var expected = new { diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs index 5f06633293..baf89cee44 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs @@ -14,7 +14,7 @@ private class Fixture public Fixture() { _ = Hub.IsEnabled.Returns(true); - Hub.ConfigureScope(Arg.Invoke(Scope)); + Hub.SubstituteConfigureScope(Scope); } public SentryLogger GetSut() => new(CategoryName, Options, new MockClock(), Hub); diff --git a/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs b/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs index b4e5b58a89..03cd2501a3 100644 --- a/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs +++ b/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs @@ -16,7 +16,7 @@ private class Fixture public Fixture() { HubAccessor = () => Hub; - Hub.ConfigureScope(Arg.Invoke(Scope)); + Hub.SubstituteConfigureScope(Scope); InitAction = s => { DsnReceivedOnInit = s; diff --git a/test/Sentry.NLog.Tests/SentryTargetTests.cs b/test/Sentry.NLog.Tests/SentryTargetTests.cs index f0ef530a68..30fae65e90 100644 --- a/test/Sentry.NLog.Tests/SentryTargetTests.cs +++ b/test/Sentry.NLog.Tests/SentryTargetTests.cs @@ -23,7 +23,7 @@ public Fixture() Hub.IsEnabled.Returns(true); HubAccessor = () => Hub; Scope = new Scope(new SentryOptions()); - Hub.ConfigureScope(Arg.Invoke(Scope)); + Hub.SubstituteConfigureScope(Scope); } public Target GetTarget(bool asyncTarget = false) diff --git a/test/Sentry.OpenTelemetry.Tests/AspNetCoreEnricherTests.cs b/test/Sentry.OpenTelemetry.Tests/AspNetCoreEnricherTests.cs index f1106a2b10..430fd2a769 100644 --- a/test/Sentry.OpenTelemetry.Tests/AspNetCoreEnricherTests.cs +++ b/test/Sentry.OpenTelemetry.Tests/AspNetCoreEnricherTests.cs @@ -9,7 +9,7 @@ public void Enrich_SendDefaultPii_UserOnScope() var scope = new Scope(); var options = new SentryOptions { SendDefaultPii = true }; var hub = Substitute.For(); - hub.ConfigureScope(Arg.Do>(action => action(scope))); + hub.SubstituteConfigureScope(scope); var user = new SentryUser { Id = "foo" }; var userFactory = Substitute.For(); diff --git a/test/Sentry.Serilog.Tests/SentrySinkTests.cs b/test/Sentry.Serilog.Tests/SentrySinkTests.cs index c660fe44e7..ce1011aa2b 100644 --- a/test/Sentry.Serilog.Tests/SentrySinkTests.cs +++ b/test/Sentry.Serilog.Tests/SentrySinkTests.cs @@ -14,7 +14,7 @@ public Fixture() { Hub.IsEnabled.Returns(true); HubAccessor = () => Hub; - Hub.ConfigureScope(Arg.Invoke(Scope)); + Hub.SubstituteConfigureScope(Scope); } public SentrySink GetSut() diff --git a/test/Sentry.Testing/HubSubstituteExtensions.cs b/test/Sentry.Testing/HubSubstituteExtensions.cs new file mode 100644 index 0000000000..de793446ae --- /dev/null +++ b/test/Sentry.Testing/HubSubstituteExtensions.cs @@ -0,0 +1,23 @@ +namespace Sentry.Testing; + +public static class HubSubstituteExtensions +{ + public static void SubstituteConfigureScope( + this IHub hub, + Scope scope) + { + hub.When(h => h.ConfigureScope(Arg.Any>())) + .Do(c => c.Arg>().Invoke(scope)); + + hub.When(h => h.ConfigureScope(Arg.Any>(), Arg.Any())) + .Do(c => + { + // If we use Arg.AnyType to look up the arguments, NSubstitute is unable to find + // them. So as a workaround, we get the arguments directly and use reflection to + // invoke the method. + var action = c[0]; + var arg = c[1]; + action.GetType().GetMethod("Invoke")!.Invoke(action, [scope, arg]); + }); + } +} diff --git a/test/Sentry.Tests/Extensibility/HubAdapterTests.cs b/test/Sentry.Tests/Extensibility/HubAdapterTests.cs index 83de560c53..824b5e08ad 100644 --- a/test/Sentry.Tests/Extensibility/HubAdapterTests.cs +++ b/test/Sentry.Tests/Extensibility/HubAdapterTests.cs @@ -162,8 +162,7 @@ private void TestAddBreadcrumbExtension( const BreadcrumbLevel level = BreadcrumbLevel.Critical; var scope = new Scope(); - Hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(scope)); + Hub.SubstituteConfigureScope(scope); action(message, category, type, data, level); diff --git a/test/Sentry.Tests/HubExtensionsTests.cs b/test/Sentry.Tests/HubExtensionsTests.cs index ce64c0f641..c01657890c 100644 --- a/test/Sentry.Tests/HubExtensionsTests.cs +++ b/test/Sentry.Tests/HubExtensionsTests.cs @@ -7,8 +7,7 @@ public class HubExtensionsTests public HubExtensionsTests() { - Sut.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(Scope)); + Sut.SubstituteConfigureScope(Scope); } [Fact] diff --git a/test/Sentry.Tests/Protocol/SentryTransactionTests.cs b/test/Sentry.Tests/Protocol/SentryTransactionTests.cs index 7f210116d8..eb1eaf1bbf 100644 --- a/test/Sentry.Tests/Protocol/SentryTransactionTests.cs +++ b/test/Sentry.Tests/Protocol/SentryTransactionTests.cs @@ -582,18 +582,18 @@ public void Finish_ResetsScopeAndSetsNewPropagationContext() var hub = Substitute.For(); var transaction = new TransactionTracer(hub, "test name", "test op"); - Action capturedAction = null; - hub.ConfigureScope(Arg.Do>(action => capturedAction = action)); + Action capturedAction = null; + hub.ConfigureScope(Arg.Do>(action => capturedAction = action), Arg.Any()); // Act transaction.Finish(); // Assert - hub.Received(1).ConfigureScope(Arg.Any>()); + hub.Received(1).ConfigureScope(Arg.Any>(), Arg.Any()); capturedAction.Should().NotBeNull(); // Sanity Check var mockScope = Substitute.For(); - capturedAction(mockScope); + capturedAction(mockScope, transaction); mockScope.Received(1).ResetTransaction(transaction); mockScope.Received(1).SetPropagationContext(Arg.Any()); diff --git a/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs b/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs index a545f501d1..26b23d13b9 100644 --- a/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs +++ b/test/Sentry.Tests/SentryGraphQlHttpMessageHandlerTests.cs @@ -83,8 +83,7 @@ public void HandleResponse_AddsBreadcrumb() var scope = new Scope(); var hub = Substitute.For(); - hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(scope)); + hub.SubstituteConfigureScope(scope); var query = ValidQuery; var request = SentryGraphQlTestHelpers.GetRequestQuery(query, url); diff --git a/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs b/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs index a2783714a9..a15727e7b4 100644 --- a/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs +++ b/test/Sentry.Tests/SentryHttpMessageHandlerTests.cs @@ -214,8 +214,7 @@ public async Task SendAsync_Executed_BreadcrumbCreated() // Arrange var scope = new Scope(); var hub = Substitute.For(); - hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(scope)); + hub.SubstituteConfigureScope(scope); var url = "https://localhost/"; @@ -496,8 +495,7 @@ public void Send_Executed_BreadcrumbCreated() // Arrange var scope = new Scope(); var hub = Substitute.For(); - hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => c.Arg>()(scope)); + hub.SubstituteConfigureScope(scope); var url = "https://localhost/"; From ab18778c774cf7125b02e3b34b54c32e0e5b0971 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Thu, 5 Jun 2025 11:44:24 +0200 Subject: [PATCH 06/12] Substitute both ConfigureScope overloads in mobile tests --- .../MauiCommunityToolkitMvvmEventsBinderTests.cs | 6 +----- test/Sentry.Maui.Tests/MauiEventsBinderTests.cs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/test/Sentry.Maui.CommunityToolkit.Mvvm.Tests/MauiCommunityToolkitMvvmEventsBinderTests.cs b/test/Sentry.Maui.CommunityToolkit.Mvvm.Tests/MauiCommunityToolkitMvvmEventsBinderTests.cs index 39d4e9f1bd..1f51552842 100644 --- a/test/Sentry.Maui.CommunityToolkit.Mvvm.Tests/MauiCommunityToolkitMvvmEventsBinderTests.cs +++ b/test/Sentry.Maui.CommunityToolkit.Mvvm.Tests/MauiCommunityToolkitMvvmEventsBinderTests.cs @@ -19,11 +19,7 @@ private class Fixture public Fixture() { Hub = Substitute.For(); - Hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => - { - c.Arg>()(Scope); - }); + Hub.SubstituteConfigureScope(Scope); Options.Debug = true; var logger = Substitute.For(); diff --git a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs index 2a84ec92b3..9b050b8bdb 100644 --- a/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs +++ b/test/Sentry.Maui.Tests/MauiEventsBinderTests.cs @@ -17,11 +17,7 @@ private class Fixture public Fixture() { Hub = Substitute.For(); - Hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(c => - { - c.Arg>()(Scope); - }); + Hub.SubstituteConfigureScope(Scope); Scope.Transaction = Substitute.For(); From ee4ce7d955049064ff4eb3b130add7fd06dc56f6 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Thu, 5 Jun 2025 12:38:15 +0200 Subject: [PATCH 07/12] Fix formatting --- src/Sentry/HubExtensions.cs | 6 +++--- test/Sentry.Tests/SentrySdkTests.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Sentry/HubExtensions.cs b/src/Sentry/HubExtensions.cs index d6ab122387..736c06ed12 100644 --- a/src/Sentry/HubExtensions.cs +++ b/src/Sentry/HubExtensions.cs @@ -155,7 +155,7 @@ public static void AddBreadcrumb( } hub.ConfigureScope( - static(s, arg) => s.AddBreadcrumb(arg.breadcrumb, arg.hint ?? new SentryHint()), + static (s, arg) => s.AddBreadcrumb(arg.breadcrumb, arg.hint ?? new SentryHint()), (breadcrumb, hint)); } @@ -242,14 +242,14 @@ internal static ITransactionTracer StartTransaction( _ => hub.StartTransaction(context, customSamplingContext) }; - internal static ITransactionTracer ? GetTransaction(this IHub hub) + internal static ITransactionTracer? GetTransaction(this IHub hub) { if (hub is Hub fullHub) { return fullHub.ScopeManager.GetCurrent().Key.Transaction; } - ITransactionTracer ? transaction = null; + ITransactionTracer? transaction = null; hub.ConfigureScope(scope => transaction = scope.Transaction); return transaction; } diff --git a/test/Sentry.Tests/SentrySdkTests.cs b/test/Sentry.Tests/SentrySdkTests.cs index d3ccdab357..00fcc95b82 100644 --- a/test/Sentry.Tests/SentrySdkTests.cs +++ b/test/Sentry.Tests/SentrySdkTests.cs @@ -443,7 +443,7 @@ public void ConfigureScope_Sync_CallbackNeverInvoked() public void ConfigureScope_SyncWithArg_CallbackNeverInvoked() { var invoked = false; - SentrySdk.ConfigureScope((_,_) => invoked = true, "arg"); + SentrySdk.ConfigureScope((_, _) => invoked = true, "arg"); Assert.False(invoked); } @@ -461,7 +461,7 @@ public void ConfigureScope_SyncWithArg_ArgIsUsed() const string key = "key"; const string arg = "arg"; - SentrySdk.ConfigureScope((s,a) => s.SetTag(key, a), arg); + SentrySdk.ConfigureScope((s, a) => s.SetTag(key, a), arg); string actual = null; SentrySdk.ConfigureScope(s => actual = s.Tags[key]); @@ -705,7 +705,7 @@ await SentrySdk.ConfigureScopeAsync(_ => public async Task ConfigureScope_AsyncWithArg_CallbackNeverInvoked() { var invoked = false; - await SentrySdk.ConfigureScopeAsync((_,_) => + await SentrySdk.ConfigureScopeAsync((_, _) => { invoked = true; return Task.CompletedTask; From fad5a1d974d81eeaf06ac95a1c5c839befcb450e Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Fri, 6 Jun 2025 20:18:40 +0200 Subject: [PATCH 08/12] Fix test --- .../SentryMiddlewareTests.cs | 25 +++++++++++++++---- .../Sentry.Testing/HubSubstituteExtensions.cs | 22 +++++++++------- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs index e15e1fb280..feb08b0995 100644 --- a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs @@ -156,16 +156,31 @@ public async Task InvokeAsync_ScopePushed_BeforeConfiguringScope() [Fact] public async Task InvokeAsync_LocksScope_BeforeConfiguringScope() { - var scopeLocked = false; - _fixture.Hub.When(h => h.PushAndLockScope()).Do(_ => scopeLocked = true); - _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>())) - .Do(_ => Assert.True(scopeLocked)); + var verified = false; + var scope = new Scope(); + _fixture.Hub + .When(h => h.ConfigureScope(Arg.Any>())) + .Do( + Callback + .First(c => c.ArgAt>(0).Invoke(scope)) + .Then(_ => + { + Assert.True(scope.Locked); + verified = true; + })); + + _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>(), Arg.Any())) + .Do(Callback.First(c => c.InvokeGenericConfigureScopeMethod(scope)).Then(_ => + { + Assert.True(scope.Locked); + verified = true; + })); var sut = _fixture.GetSut(); await sut.InvokeAsync(_fixture.HttpContext, _fixture.RequestDelegate); - _fixture.Hub.Received().ConfigureScope(Arg.Any>()); + Assert.True(verified); } [Fact] diff --git a/test/Sentry.Testing/HubSubstituteExtensions.cs b/test/Sentry.Testing/HubSubstituteExtensions.cs index de793446ae..d039e92456 100644 --- a/test/Sentry.Testing/HubSubstituteExtensions.cs +++ b/test/Sentry.Testing/HubSubstituteExtensions.cs @@ -10,14 +10,18 @@ public static void SubstituteConfigureScope( .Do(c => c.Arg>().Invoke(scope)); hub.When(h => h.ConfigureScope(Arg.Any>(), Arg.Any())) - .Do(c => - { - // If we use Arg.AnyType to look up the arguments, NSubstitute is unable to find - // them. So as a workaround, we get the arguments directly and use reflection to - // invoke the method. - var action = c[0]; - var arg = c[1]; - action.GetType().GetMethod("Invoke")!.Invoke(action, [scope, arg]); - }); + .Do(c => c.InvokeGenericConfigureScopeMethod(scope)); + } + + public static void InvokeGenericConfigureScopeMethod( + this CallInfo c, + Scope scope) + { + // If we use Arg.AnyType to look up the arguments, NSubstitute is unable to find + // them. So as a workaround, we get the arguments directly and use reflection to + // invoke the method. + var action = c[0]; + var arg = c[1]; + action.GetType().GetMethod("Invoke")!.Invoke(action, [scope, arg]); } } From 864912252143d322b6caa9f52856ad7357829ae4 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Sat, 7 Jun 2025 09:38:47 +0200 Subject: [PATCH 09/12] Address review comments --- src/Sentry/Internal/Hub.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sentry/Internal/Hub.cs b/src/Sentry/Internal/Hub.cs index 046ca5cc18..2084e20295 100644 --- a/src/Sentry/Internal/Hub.cs +++ b/src/Sentry/Internal/Hub.cs @@ -107,7 +107,7 @@ public void ConfigureScope(Action configureScope, TArg arg) } catch (Exception e) { - _options.LogError(e, "Failure to ConfigureScope"); + _options.LogError(e, "Failure to ConfigureScope"); } } @@ -131,7 +131,7 @@ public async Task ConfigureScopeAsync(Func configureSco } catch (Exception e) { - _options.LogError(e, "Failure to ConfigureScopeAsync"); + _options.LogError(e, "Failure to ConfigureScopeAsync"); } } From 91914d4d649d2df88c59bb50a08d6a7c521901de Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Sat, 7 Jun 2025 10:46:33 +0200 Subject: [PATCH 10/12] Fix CHANGELOG.md --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db228e8ecd..85765b1688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Features + +- Added non-allocating `ConfigureScope` and `ConfigureScopeAsync` overloads ([#4244](https://github.com/getsentry/sentry-dotnet/pull/4244)) + ### Fixes - The HTTP instrumentation uses the span created for the outgoing request in the sentry-trace header, fixing the parent-child relationship between client and server ([#4264](https://github.com/getsentry/sentry-dotnet/pull/4264)) @@ -18,7 +22,6 @@ - Rename MemoryInfo.AllocatedBytes to MemoryInfo.TotalAllocatedBytes ([#4243](https://github.com/getsentry/sentry-dotnet/pull/4243)) - Replace libcurl with .NET HttpClient for sentry-native ([#4222](https://github.com/getsentry/sentry-dotnet/pull/4222)) -- Added non allocating SentrySdk.ConfigureScope overload ([#4244](https://github.com/getsentry/sentry-dotnet/pull/4244)) ### Fixes From aad5e0b3606c9919ccceccc909784a47c5376a90 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Tue, 10 Jun 2025 17:19:27 +0200 Subject: [PATCH 11/12] Revert indentation change in SentryMiddlewareTests --- .../SentryMiddlewareTests.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs index feb08b0995..2125dc949e 100644 --- a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs @@ -159,15 +159,14 @@ public async Task InvokeAsync_LocksScope_BeforeConfiguringScope() var verified = false; var scope = new Scope(); _fixture.Hub - .When(h => h.ConfigureScope(Arg.Any>())) - .Do( - Callback - .First(c => c.ArgAt>(0).Invoke(scope)) - .Then(_ => - { - Assert.True(scope.Locked); - verified = true; - })); + .When(h => h.ConfigureScope(Arg.Any>())) + .Do(Callback + .First(c => c.ArgAt>(0).Invoke(scope)) + .Then(_ => + { + Assert.True(scope.Locked); + verified = true; + })); _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>(), Arg.Any())) .Do(Callback.First(c => c.InvokeGenericConfigureScopeMethod(scope)).Then(_ => From dc5c85318afa7abbf931060d9ac84cece3014d88 Mon Sep 17 00:00:00 2001 From: Casper Verhaar Date: Tue, 10 Jun 2025 17:23:41 +0200 Subject: [PATCH 12/12] Format arguments consistently in test --- .../SentryMiddlewareTests.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs index 2125dc949e..3f53589e67 100644 --- a/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryMiddlewareTests.cs @@ -168,12 +168,15 @@ public async Task InvokeAsync_LocksScope_BeforeConfiguringScope() verified = true; })); - _fixture.Hub.When(h => h.ConfigureScope(Arg.Any>(), Arg.Any())) - .Do(Callback.First(c => c.InvokeGenericConfigureScopeMethod(scope)).Then(_ => - { - Assert.True(scope.Locked); - verified = true; - })); + _fixture.Hub + .When(h => h.ConfigureScope(Arg.Any>(), Arg.Any())) + .Do(Callback + .First(c => c.InvokeGenericConfigureScopeMethod(scope)) + .Then(_ => + { + Assert.True(scope.Locked); + verified = true; + })); var sut = _fixture.GetSut();