Skip to content

Commit 65a7bba

Browse files
Add SentryOptions.AutoRegisterTracing (#2871)
1 parent bbd42fb commit 65a7bba

10 files changed

+174
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Fixes
66

77
- `CaptureFailedRequests` and `FailedRequestStatusCodes` are now getting respected by the Cocoa SDK. This is relevant for MAUI apps where requests are getting handled natively. ([#2826](https://github.com/getsentry/sentry-dotnet/issues/2826))
8+
- Added `SentryOptions.AutoRegisterTracing` for users who need to control registration of Sentry's tracing middleware ([#2871](https://github.com/getsentry/sentry-dotnet/pull/2871))
89

910
### Dependencies
1011

src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.AspNetCore.Builder;
12
using Microsoft.AspNetCore.Hosting;
23
using Microsoft.AspNetCore.Http;
34
using Sentry.Extensibility;
@@ -65,6 +66,21 @@ public class SentryAspNetCoreOptions : SentryLoggingOptions
6566
/// </remarks>
6667
public bool AdjustStandardEnvironmentNameCasing { get; set; } = true;
6768

69+
/// <summary>
70+
/// <para>
71+
/// When true (by default) Sentry automatically registers its tracing middleware immediately after
72+
/// `EndpointRoutingApplicationBuilderExtensions.UseRouting`.
73+
/// </para>
74+
/// <para>
75+
/// If you need to control when Sentry's tracing middleware is registered, you can set
76+
/// <see cref="AutoRegisterTracing"/> to false and call
77+
/// <see cref="SentryTracingMiddlewareExtensions.UseSentryTracing"/> yourself, sometime after calling
78+
/// `EndpointRoutingApplicationBuilderExtensions.UseRouting` and before calling
79+
/// `EndpointRoutingApplicationBuilderExtensions.UseEndpoints`.
80+
/// </para>
81+
/// </summary>
82+
public bool AutoRegisterTracing { get; set; } = true;
83+
6884
/// <summary>
6985
/// Creates a new instance of <see cref="SentryAspNetCoreOptions"/>.
7086
/// </summary>

src/Sentry.AspNetCore/SentryTracingBuilder.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware
4949
{
5050
var options = InnerBuilder.ApplicationServices.GetService<IOptions<SentryAspNetCoreOptions>>();
5151
var instrumenter = options?.Value.Instrumenter ?? Instrumenter.Sentry;
52-
if (instrumenter == Instrumenter.Sentry)
52+
var autoRegisterTracing = options?.Value.AutoRegisterTracing ?? true;
53+
if (instrumenter == Instrumenter.Sentry && autoRegisterTracing)
5354
{
54-
InnerBuilder.Use(middleware).UseSentryTracing();
55+
InnerBuilder.Use(middleware).UseSentryTracingInternal();
5556
return this; // Make sure we return the same builder (not the inner builder), for chaining
5657
}
57-
this.StoreInstrumenter(instrumenter); // Saves us from having to resolve the options to make this check again
58+
this.StoreRegistrationDecision(false); // Saves us from having to resolve the options to make this check again
5859
}
5960

6061
InnerBuilder.Use(middleware);
Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.Extensions.Logging;
13
using Sentry;
24
using Sentry.AspNetCore;
5+
using Sentry.Extensibility;
36
using Sentry.Internal.Extensions;
47

58
// ReSharper disable once CheckNamespace
@@ -11,32 +14,30 @@ namespace Microsoft.AspNetCore.Builder;
1114
[EditorBrowsable(EditorBrowsableState.Never)]
1215
public static class SentryTracingMiddlewareExtensions
1316
{
17+
internal const string AlreadyRegisteredWarning = "Sentry tracing middleware was already registered. This call to UseSentryTracing is unnecessary.";
1418
private const string UseSentryTracingKey = "__UseSentryTracing";
15-
private const string InstrumenterKey = "__SentryInstrumenter";
19+
private const string ShouldRegisterKey = "__ShouldRegisterSentryTracing";
1620

1721
internal static bool IsSentryTracingRegistered(this IApplicationBuilder builder)
1822
=> builder.Properties.ContainsKey(UseSentryTracingKey);
19-
internal static void StoreInstrumenter(this IApplicationBuilder builder, Instrumenter instrumenter)
20-
=> builder.Properties[InstrumenterKey] = instrumenter;
23+
internal static void StoreRegistrationDecision(this IApplicationBuilder builder, bool shouldRegisterSentryTracing)
24+
=> builder.Properties[ShouldRegisterKey] = shouldRegisterSentryTracing;
2125

2226
internal static bool ShouldRegisterSentryTracing(this IApplicationBuilder builder)
2327
{
2428
if (builder.Properties.ContainsKey(UseSentryTracingKey))
2529
{
30+
// It's already been registered
2631
return false;
2732
}
28-
if (builder.Properties.TryGetTypedValue(InstrumenterKey, out Instrumenter instrumenter))
33+
if (builder.Properties.TryGetTypedValue(ShouldRegisterKey, out bool shouldRegisterSentryTracing))
2934
{
30-
return instrumenter == Instrumenter.Sentry;
35+
return shouldRegisterSentryTracing;
3136
}
3237
return true;
3338
}
3439

35-
/// <summary>
36-
/// Adds Sentry's tracing middleware to the pipeline.
37-
/// Make sure to place this middleware after <c>UseRouting(...)</c>.
38-
/// </summary>
39-
public static IApplicationBuilder UseSentryTracing(this IApplicationBuilder builder)
40+
internal static IApplicationBuilder UseSentryTracingInternal(this IApplicationBuilder builder)
4041
{
4142
// Don't register twice
4243
if (builder.IsSentryTracingRegistered())
@@ -48,4 +49,21 @@ public static IApplicationBuilder UseSentryTracing(this IApplicationBuilder buil
4849
builder.UseMiddleware<SentryTracingMiddleware>();
4950
return builder;
5051
}
52+
53+
/// <summary>
54+
/// Adds Sentry's tracing middleware to the pipeline.
55+
/// Make sure to place this middleware after <c>UseRouting(...)</c>.
56+
/// </summary>
57+
public static IApplicationBuilder UseSentryTracing(this IApplicationBuilder builder)
58+
{
59+
if (!builder.IsSentryTracingRegistered())
60+
{
61+
return builder.UseSentryTracingInternal();
62+
}
63+
// Warn on multiple calls
64+
var log = builder.ApplicationServices.GetService<ILoggerFactory>()
65+
?.CreateLogger<SentryTracingMiddleware>();
66+
log?.LogWarning(AlreadyRegisteredWarning);
67+
return builder;
68+
}
5169
}

test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core3_1.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
6060
{
6161
public SentryAspNetCoreOptions() { }
6262
public bool AdjustStandardEnvironmentNameCasing { get; set; }
63+
public bool AutoRegisterTracing { get; set; }
6364
public bool FlushOnCompletedRequest { get; set; }
6465
public bool IncludeActivityData { get; set; }
6566
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }

test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
6060
{
6161
public SentryAspNetCoreOptions() { }
6262
public bool AdjustStandardEnvironmentNameCasing { get; set; }
63+
public bool AutoRegisterTracing { get; set; }
6364
public bool FlushOnCompletedRequest { get; set; }
6465
public bool IncludeActivityData { get; set; }
6566
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }

test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
6060
{
6161
public SentryAspNetCoreOptions() { }
6262
public bool AdjustStandardEnvironmentNameCasing { get; set; }
63+
public bool AutoRegisterTracing { get; set; }
6364
public bool FlushOnCompletedRequest { get; set; }
6465
public bool IncludeActivityData { get; set; }
6566
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }

test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Net4_8.verified.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
6060
{
6161
public SentryAspNetCoreOptions() { }
6262
public bool AdjustStandardEnvironmentNameCasing { get; set; }
63+
public bool AutoRegisterTracing { get; set; }
6364
public bool FlushOnCompletedRequest { get; set; }
6465
public bool IncludeActivityData { get; set; }
6566
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }

test/Sentry.AspNetCore.Tests/ApplicationBuilderExtensionsTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ private class Fixture
1111
public ISentryEventExceptionProcessor SentryEventExceptionProcessor { get; set; } = Substitute.For<ISentryEventExceptionProcessor>();
1212
public SentryAspNetCoreOptions SentryAspNetCoreOptions { get; set; } = new();
1313

14+
public Dictionary<string, object> Properties { get; } = new();
15+
1416
public IApplicationBuilder GetSut()
1517
{
1618
var provider = Substitute.For<IServiceProvider>();
@@ -39,6 +41,7 @@ public IApplicationBuilder GetSut()
3941
: Enumerable.Empty<ISentryEventExceptionProcessor>());
4042

4143
var sut = Substitute.For<IApplicationBuilder>();
44+
sut.Properties.Returns(Properties);
4245
_ = sut.ApplicationServices.Returns(provider);
4346
return sut;
4447
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#if NETCOREAPP3_0_OR_GREATER
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.AspNetCore.TestHost;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Logging;
7+
using Sentry.AspNetCore.TestUtils;
8+
9+
namespace Sentry.AspNetCore.Tests;
10+
11+
public class SentryTracingBuilderTests
12+
{
13+
class Fixture
14+
{
15+
public Action<IServiceCollection> ConfigureServices { get; set; }
16+
public Action<IApplicationBuilder> Configure { get; set; }
17+
public Action<SentryAspNetCoreOptions> ConfigureOptions { get; set; } = _ => { };
18+
19+
public (IServiceCollection services, IApplicationBuilder builder) GetSut()
20+
{
21+
IServiceCollection servicesCollection = null;
22+
IApplicationBuilder applicationBuilder = null;
23+
_ = new TestServer(new WebHostBuilder()
24+
.UseDefaultServiceProvider(di => di.EnableValidation())
25+
.UseSentry(ConfigureOptions)
26+
.ConfigureServices(services =>
27+
{
28+
ConfigureServices?.Invoke(services);
29+
servicesCollection = services;
30+
})
31+
.Configure(app =>
32+
{
33+
Configure?.Invoke(app);
34+
applicationBuilder = app;
35+
}));
36+
return (servicesCollection, applicationBuilder);
37+
}
38+
}
39+
40+
private readonly Fixture _fixture = new();
41+
42+
[Fact]
43+
public void UseRouting_AutoRegisterTracingDisabled_SentryTracingNotRegistered()
44+
{
45+
// Arrange
46+
_fixture.ConfigureServices = services => services.AddRouting();
47+
_fixture.Configure = applicationBuilder => applicationBuilder.UseRouting();
48+
_fixture.ConfigureOptions = options => options.AutoRegisterTracing = false;
49+
50+
// Act - implicit
51+
var (_, builder) = _fixture.GetSut();
52+
53+
// Assert
54+
builder.IsSentryTracingRegistered().Should().BeFalse();
55+
}
56+
57+
[Fact]
58+
public void UseRouting_OtelInstrumentation_SentryTracingNotRegistered()
59+
{
60+
// Arrange
61+
_fixture.ConfigureServices = services => services.AddRouting();
62+
_fixture.Configure = applicationBuilder => applicationBuilder.UseRouting();
63+
_fixture.ConfigureOptions = options => options.Instrumenter = Instrumenter.OpenTelemetry;
64+
65+
// Act - implicit
66+
var (_, builder) = _fixture.GetSut();
67+
68+
// Assert
69+
builder.IsSentryTracingRegistered().Should().BeFalse();
70+
}
71+
72+
[Fact]
73+
public void UseRouting_SentryTracingRegisteredWithoutWarning()
74+
{
75+
// Arrange
76+
var logger = Substitute.For<ILogger<SentryTracingMiddleware>>();
77+
var loggerFactory = Substitute.For<ILoggerFactory>();
78+
loggerFactory.CreateLogger<SentryTracingMiddleware>().Returns(logger);
79+
_fixture.ConfigureServices = services =>
80+
{
81+
services.AddSingleton(loggerFactory);
82+
services.AddRouting();
83+
};
84+
_fixture.Configure = applicationBuilder => applicationBuilder.UseRouting();
85+
86+
// Act
87+
var (_, builder) = _fixture.GetSut();
88+
89+
builder.IsSentryTracingRegistered().Should().BeTrue();
90+
logger.Received(0).LogWarning(SentryTracingMiddlewareExtensions.AlreadyRegisteredWarning);
91+
}
92+
93+
[Fact]
94+
public void UseSentryTracing_AutoRegisterTracing_Warning()
95+
{
96+
// Arrange
97+
var logger = Substitute.For<ILogger<SentryTracingMiddleware>>();
98+
var loggerFactory = Substitute.For<ILoggerFactory>();
99+
loggerFactory.CreateLogger<SentryTracingMiddleware>().Returns(logger);
100+
_fixture.ConfigureServices = services =>
101+
{
102+
services.AddSingleton(loggerFactory);
103+
services.AddRouting();
104+
};
105+
_fixture.Configure = applicationBuilder =>
106+
{
107+
applicationBuilder.UseRouting();
108+
applicationBuilder.UseSentryTracing();
109+
};
110+
111+
// Act
112+
var _ = _fixture.GetSut();
113+
114+
// Assert
115+
logger.Received(1).LogWarning(SentryTracingMiddlewareExtensions.AlreadyRegisteredWarning);
116+
}
117+
}
118+
#endif

0 commit comments

Comments
 (0)