Skip to content

Commit

Permalink
Add SentryOptions.AutoRegisterTracing (#2871)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamescrosswell committed Nov 22, 2023
1 parent bbd42fb commit 65a7bba
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Fixes

- `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))
- Added `SentryOptions.AutoRegisterTracing` for users who need to control registration of Sentry's tracing middleware ([#2871](https://github.com/getsentry/sentry-dotnet/pull/2871))

### Dependencies

Expand Down
16 changes: 16 additions & 0 deletions src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Sentry.Extensibility;
Expand Down Expand Up @@ -65,6 +66,21 @@ public class SentryAspNetCoreOptions : SentryLoggingOptions
/// </remarks>
public bool AdjustStandardEnvironmentNameCasing { get; set; } = true;

/// <summary>
/// <para>
/// When true (by default) Sentry automatically registers its tracing middleware immediately after
/// `EndpointRoutingApplicationBuilderExtensions.UseRouting`.
/// </para>
/// <para>
/// If you need to control when Sentry's tracing middleware is registered, you can set
/// <see cref="AutoRegisterTracing"/> to false and call
/// <see cref="SentryTracingMiddlewareExtensions.UseSentryTracing"/> yourself, sometime after calling
/// `EndpointRoutingApplicationBuilderExtensions.UseRouting` and before calling
/// `EndpointRoutingApplicationBuilderExtensions.UseEndpoints`.
/// </para>
/// </summary>
public bool AutoRegisterTracing { get; set; } = true;

/// <summary>
/// Creates a new instance of <see cref="SentryAspNetCoreOptions"/>.
/// </summary>
Expand Down
7 changes: 4 additions & 3 deletions src/Sentry.AspNetCore/SentryTracingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,13 @@ public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware
{
var options = InnerBuilder.ApplicationServices.GetService<IOptions<SentryAspNetCoreOptions>>();
var instrumenter = options?.Value.Instrumenter ?? Instrumenter.Sentry;
if (instrumenter == Instrumenter.Sentry)
var autoRegisterTracing = options?.Value.AutoRegisterTracing ?? true;
if (instrumenter == Instrumenter.Sentry && autoRegisterTracing)
{
InnerBuilder.Use(middleware).UseSentryTracing();
InnerBuilder.Use(middleware).UseSentryTracingInternal();
return this; // Make sure we return the same builder (not the inner builder), for chaining
}
this.StoreInstrumenter(instrumenter); // Saves us from having to resolve the options to make this check again
this.StoreRegistrationDecision(false); // Saves us from having to resolve the options to make this check again
}

InnerBuilder.Use(middleware);
Expand Down
38 changes: 28 additions & 10 deletions src/Sentry.AspNetCore/SentryTracingMiddlewareExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sentry;
using Sentry.AspNetCore;
using Sentry.Extensibility;
using Sentry.Internal.Extensions;

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

internal static bool IsSentryTracingRegistered(this IApplicationBuilder builder)
=> builder.Properties.ContainsKey(UseSentryTracingKey);
internal static void StoreInstrumenter(this IApplicationBuilder builder, Instrumenter instrumenter)
=> builder.Properties[InstrumenterKey] = instrumenter;
internal static void StoreRegistrationDecision(this IApplicationBuilder builder, bool shouldRegisterSentryTracing)
=> builder.Properties[ShouldRegisterKey] = shouldRegisterSentryTracing;

internal static bool ShouldRegisterSentryTracing(this IApplicationBuilder builder)
{
if (builder.Properties.ContainsKey(UseSentryTracingKey))
{
// It's already been registered
return false;
}
if (builder.Properties.TryGetTypedValue(InstrumenterKey, out Instrumenter instrumenter))
if (builder.Properties.TryGetTypedValue(ShouldRegisterKey, out bool shouldRegisterSentryTracing))
{
return instrumenter == Instrumenter.Sentry;
return shouldRegisterSentryTracing;
}
return true;
}

/// <summary>
/// Adds Sentry's tracing middleware to the pipeline.
/// Make sure to place this middleware after <c>UseRouting(...)</c>.
/// </summary>
public static IApplicationBuilder UseSentryTracing(this IApplicationBuilder builder)
internal static IApplicationBuilder UseSentryTracingInternal(this IApplicationBuilder builder)
{
// Don't register twice
if (builder.IsSentryTracingRegistered())
Expand All @@ -48,4 +49,21 @@ public static IApplicationBuilder UseSentryTracing(this IApplicationBuilder buil
builder.UseMiddleware<SentryTracingMiddleware>();
return builder;
}

/// <summary>
/// Adds Sentry's tracing middleware to the pipeline.
/// Make sure to place this middleware after <c>UseRouting(...)</c>.
/// </summary>
public static IApplicationBuilder UseSentryTracing(this IApplicationBuilder builder)
{
if (!builder.IsSentryTracingRegistered())
{
return builder.UseSentryTracingInternal();
}
// Warn on multiple calls
var log = builder.ApplicationServices.GetService<ILoggerFactory>()
?.CreateLogger<SentryTracingMiddleware>();
log?.LogWarning(AlreadyRegisteredWarning);
return builder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool FlushOnCompletedRequest { get; set; }
public bool IncludeActivityData { get; set; }
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool FlushOnCompletedRequest { get; set; }
public bool IncludeActivityData { get; set; }
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool FlushOnCompletedRequest { get; set; }
public bool IncludeActivityData { get; set; }
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ namespace Sentry.AspNetCore
{
public SentryAspNetCoreOptions() { }
public bool AdjustStandardEnvironmentNameCasing { get; set; }
public bool AutoRegisterTracing { get; set; }
public bool FlushOnCompletedRequest { get; set; }
public bool IncludeActivityData { get; set; }
public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ private class Fixture
public ISentryEventExceptionProcessor SentryEventExceptionProcessor { get; set; } = Substitute.For<ISentryEventExceptionProcessor>();
public SentryAspNetCoreOptions SentryAspNetCoreOptions { get; set; } = new();

public Dictionary<string, object> Properties { get; } = new();

public IApplicationBuilder GetSut()
{
var provider = Substitute.For<IServiceProvider>();
Expand Down Expand Up @@ -39,6 +41,7 @@ public IApplicationBuilder GetSut()
: Enumerable.Empty<ISentryEventExceptionProcessor>());

var sut = Substitute.For<IApplicationBuilder>();
sut.Properties.Returns(Properties);
_ = sut.ApplicationServices.Returns(provider);
return sut;
}
Expand Down
118 changes: 118 additions & 0 deletions test/Sentry.AspNetCore.Tests/SentryTracingBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#if NETCOREAPP3_0_OR_GREATER
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Sentry.AspNetCore.TestUtils;

namespace Sentry.AspNetCore.Tests;

public class SentryTracingBuilderTests
{
class Fixture
{
public Action<IServiceCollection> ConfigureServices { get; set; }
public Action<IApplicationBuilder> Configure { get; set; }
public Action<SentryAspNetCoreOptions> ConfigureOptions { get; set; } = _ => { };

public (IServiceCollection services, IApplicationBuilder builder) GetSut()
{
IServiceCollection servicesCollection = null;
IApplicationBuilder applicationBuilder = null;
_ = new TestServer(new WebHostBuilder()
.UseDefaultServiceProvider(di => di.EnableValidation())
.UseSentry(ConfigureOptions)
.ConfigureServices(services =>
{
ConfigureServices?.Invoke(services);
servicesCollection = services;
})
.Configure(app =>
{
Configure?.Invoke(app);
applicationBuilder = app;
}));
return (servicesCollection, applicationBuilder);
}
}

private readonly Fixture _fixture = new();

[Fact]
public void UseRouting_AutoRegisterTracingDisabled_SentryTracingNotRegistered()
{
// Arrange
_fixture.ConfigureServices = services => services.AddRouting();
_fixture.Configure = applicationBuilder => applicationBuilder.UseRouting();
_fixture.ConfigureOptions = options => options.AutoRegisterTracing = false;

// Act - implicit
var (_, builder) = _fixture.GetSut();

// Assert
builder.IsSentryTracingRegistered().Should().BeFalse();
}

[Fact]
public void UseRouting_OtelInstrumentation_SentryTracingNotRegistered()
{
// Arrange
_fixture.ConfigureServices = services => services.AddRouting();
_fixture.Configure = applicationBuilder => applicationBuilder.UseRouting();
_fixture.ConfigureOptions = options => options.Instrumenter = Instrumenter.OpenTelemetry;

// Act - implicit
var (_, builder) = _fixture.GetSut();

// Assert
builder.IsSentryTracingRegistered().Should().BeFalse();
}

[Fact]
public void UseRouting_SentryTracingRegisteredWithoutWarning()
{
// Arrange
var logger = Substitute.For<ILogger<SentryTracingMiddleware>>();
var loggerFactory = Substitute.For<ILoggerFactory>();
loggerFactory.CreateLogger<SentryTracingMiddleware>().Returns(logger);
_fixture.ConfigureServices = services =>
{
services.AddSingleton(loggerFactory);
services.AddRouting();
};
_fixture.Configure = applicationBuilder => applicationBuilder.UseRouting();

// Act
var (_, builder) = _fixture.GetSut();

builder.IsSentryTracingRegistered().Should().BeTrue();
logger.Received(0).LogWarning(SentryTracingMiddlewareExtensions.AlreadyRegisteredWarning);
}

[Fact]
public void UseSentryTracing_AutoRegisterTracing_Warning()
{
// Arrange
var logger = Substitute.For<ILogger<SentryTracingMiddleware>>();
var loggerFactory = Substitute.For<ILoggerFactory>();
loggerFactory.CreateLogger<SentryTracingMiddleware>().Returns(logger);
_fixture.ConfigureServices = services =>
{
services.AddSingleton(loggerFactory);
services.AddRouting();
};
_fixture.Configure = applicationBuilder =>
{
applicationBuilder.UseRouting();
applicationBuilder.UseSentryTracing();
};

// Act
var _ = _fixture.GetSut();

// Assert
logger.Received(1).LogWarning(SentryTracingMiddlewareExtensions.AlreadyRegisteredWarning);
}
}
#endif

0 comments on commit 65a7bba

Please sign in to comment.