diff --git a/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs b/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs new file mode 100644 index 0000000000..83b0aeabe4 --- /dev/null +++ b/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Sentry.Extensibility; +using Sentry.Extensions.Logging; + +#if NETSTANDARD2_0 +using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; +#else +using Microsoft.Extensions.Hosting; +#endif + +namespace Sentry.AspNetCore; + +/// +internal class BindableSentryAspNetCoreOptions : BindableSentryLoggingOptions +{ + public bool? IncludeActivityData { get; set; } + public RequestSize? MaxRequestBodySize { get; set; } + public bool? FlushOnCompletedRequest { get; set; } + public bool? FlushBeforeRequestCompleted { get; set; } + public bool? AdjustStandardEnvironmentNameCasing { get; set; } + + public void ApplyTo(SentryAspNetCoreOptions options) + { + base.ApplyTo(options); + options.IncludeActivityData = IncludeActivityData ?? options.IncludeActivityData; + options.MaxRequestBodySize = MaxRequestBodySize ?? options.MaxRequestBodySize; + options.FlushOnCompletedRequest = FlushOnCompletedRequest ?? options.FlushOnCompletedRequest; + options.FlushBeforeRequestCompleted = FlushBeforeRequestCompleted ?? options.FlushBeforeRequestCompleted; + options.AdjustStandardEnvironmentNameCasing = AdjustStandardEnvironmentNameCasing ?? options.AdjustStandardEnvironmentNameCasing; + } +} diff --git a/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj b/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj index b792f3d350..8522f35022 100644 --- a/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj +++ b/src/Sentry.AspNetCore/Sentry.AspNetCore.csproj @@ -6,6 +6,11 @@ Official ASP.NET Core integration for Sentry - Open-source error tracking that helps developers monitor and fix crashes in real time. + + true + true + + @@ -26,6 +31,7 @@ + diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs index 900953a495..882ae922a4 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; @@ -13,6 +14,7 @@ namespace Sentry.AspNetCore; /// /// Sets up ASP.NET Core option for Sentry. /// +#if NETSTANDARD2_0 public class SentryAspNetCoreOptionsSetup : ConfigureFromConfigurationOptions { /// @@ -30,7 +32,53 @@ public SentryAspNetCoreOptionsSetup( public override void Configure(SentryAspNetCoreOptions options) { base.Configure(options); + options.AddDiagnosticSourceIntegration(); + options.DeduplicateUnhandledException(); + } +} + +#else +public class SentryAspNetCoreOptionsSetup : IConfigureOptions +{ + private readonly IConfiguration _config; + + /// + /// Creates a new instance of . + /// + public SentryAspNetCoreOptionsSetup(ILoggerProviderConfiguration providerConfiguration) + : this(providerConfiguration.Configuration) + { + } + /// + /// Creates a new instance of . + /// + internal SentryAspNetCoreOptionsSetup(IConfiguration config) + { + ArgumentNullException.ThrowIfNull(config); + _config = config; + } + + /// + /// Configures the . + /// + public void Configure(SentryAspNetCoreOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + var bindable = new BindableSentryAspNetCoreOptions(); + _config.Bind(bindable); + bindable.ApplyTo(options); + + options.DeduplicateUnhandledException(); + } +} +#endif + +internal static class SentryAspNetCoreOptionsExtensions +{ + internal static void DeduplicateUnhandledException(this SentryAspNetCoreOptions options) + { options.AddLogEntryFilter((category, _, eventId, _) // https://github.com/aspnet/KestrelHttpServer/blob/0aff4a0440c2f393c0b98e9046a8e66e30a56cb0/src/Kestrel.Core/Internal/Infrastructure/KestrelTrace.cs#L33 // 13 = Application unhandled exception, which is captured by the middleware so the LogError of kestrel ends up as a duplicate with less info @@ -39,9 +87,5 @@ public override void Configure(SentryAspNetCoreOptions options) category, "Microsoft.AspNetCore.Server.Kestrel", StringComparison.Ordinal)); - -#if NETSTANDARD2_0 - options.AddDiagnosticSourceIntegration(); -#endif } } diff --git a/src/Sentry.AspNetCore/SentryTunnelMiddleware.cs b/src/Sentry.AspNetCore/SentryTunnelMiddleware.cs index 4be7cbf8d5..2278f61a01 100644 --- a/src/Sentry.AspNetCore/SentryTunnelMiddleware.cs +++ b/src/Sentry.AspNetCore/SentryTunnelMiddleware.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; +using Sentry.Internal.Extensions; namespace Sentry.AspNetCore; @@ -70,7 +71,14 @@ public async Task InvokeAsync(HttpContext context, RequestDelegate next) try { +#if NETSTANDARD2_0 var headerJson = JsonSerializer.Deserialize>(header); +#else + var headerJson = JsonSerializer.Deserialize( + header, + SentryJsonContext.Default.DictionaryStringObject + ); +#endif if (headerJson == null) { response.StatusCode = StatusCodes.Status400BadRequest; diff --git a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs index ee83bf9560..931da5bcf2 100644 --- a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; @@ -83,8 +84,13 @@ public static IWebHostBuilder UseSentry( logging.AddConfiguration(); var section = context.Configuration.GetSection("Sentry"); +#if NETSTANDARD2_0 _ = logging.Services.Configure(section); - +#else + _ = logging.Services.AddSingleton>(_ => + new SentryAspNetCoreOptionsSetup(section) + ); +#endif _ = logging.Services .AddSingleton, SentryAspNetCoreOptionsSetup>(); _ = logging.Services.AddSingleton(); diff --git a/src/Sentry/Internal/Extensions/JsonExtensions.cs b/src/Sentry/Internal/Extensions/JsonExtensions.cs index 0c95721e05..3dc403efb7 100644 --- a/src/Sentry/Internal/Extensions/JsonExtensions.cs +++ b/src/Sentry/Internal/Extensions/JsonExtensions.cs @@ -887,6 +887,7 @@ public static void WriteString( [JsonSerializable(typeof(GrowableArray))] [JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(Dictionary))] internal partial class SentryJsonContext : JsonSerializerContext { } diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt index d8085ec032..df39c25e34 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -58,10 +58,10 @@ namespace Sentry.AspNetCore public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } } - public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions + public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions { public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } - public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } + public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } public static class SentryBuilderExtensions { diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt index d8085ec032..df39c25e34 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet7_0.verified.txt @@ -58,10 +58,10 @@ namespace Sentry.AspNetCore public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } } - public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions + public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions { public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } - public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } + public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } public static class SentryBuilderExtensions { diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index d8085ec032..df39c25e34 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -58,10 +58,10 @@ namespace Sentry.AspNetCore public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } } - public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions + public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions { public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } - public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } + public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } public static class SentryBuilderExtensions { diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs index 49524f6b9a..8cf76716ab 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; @@ -11,29 +12,60 @@ namespace Sentry.AspNetCore.Tests; public class SentryAspNetCoreOptionsSetupTests { - private readonly SentryAspNetCoreOptionsSetup _sut = new( - Substitute.For>()); + class Fixture + { + public Dictionary Configuration { get; set; } = new(); + + public SentryAspNetCoreOptionsSetup GetSut() + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(Configuration) + .Build(); + var loggingConfig = Substitute.For>(); + loggingConfig.Configuration.Returns(config); + return new(loggingConfig); + } + } + private readonly Fixture _fixture = new(); private readonly SentryAspNetCoreOptions _target = new(); [Fact] public void Filters_KestrelApplicationEvent_NoException_Filtered() { - _sut.Configure(_target); + // Arrange + var sut = _fixture.GetSut(); + + // Act + sut.Configure(_target); + + //Assert Assert.Contains(_target.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Critical, 13, null)); } [Fact] public void Filters_KestrelApplicationEvent_WithException_Filtered() { - _sut.Configure(_target); + // Arrange + var sut = _fixture.GetSut(); + + // Act + sut.Configure(_target); + + // Assert Assert.Contains(_target.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Critical, 13, new Exception())); } [Fact] public void Filters_KestrelEventId1_WithException_NotFiltered() { - _sut.Configure(_target); + // Arrange + var sut = _fixture.GetSut(); + + // Act + sut.Configure(_target); + + // Assert Assert.DoesNotContain(_target.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Trace, 1, null)); }