Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SentryOptions.AutoRegisterTracing #2871

Merged
merged 6 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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