diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f5ef1ec5..5732b5040a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Fix logging loop with Serilog sentry ([#1828](https://github.com/getsentry/sentry-dotnet/pull/1828)) - Skip attachment if stream is empty ([#1854](https://github.com/getsentry/sentry-dotnet/pull/1854)) - Allow some mobile options to be modified from defaults ([#1857](https://github.com/getsentry/sentry-dotnet/pull/1857)) +- Fix environment name casing issue ([#1861](https://github.com/getsentry/sentry-dotnet/pull/1861)) ## 3.20.1 diff --git a/SentryAspNetCore.slnf b/SentryAspNetCore.slnf new file mode 100644 index 0000000000..d4e67be91e --- /dev/null +++ b/SentryAspNetCore.slnf @@ -0,0 +1,16 @@ +{ + "solution": { + "path": "Sentry.sln", + "projects": [ + "src\\Sentry\\Sentry.csproj", + "src\\Sentry.AspNetCore\\Sentry.AspNetCore.csproj", + "src\\Sentry.AspNetCore.Grpc\\Sentry.AspNetCore.Grpc.csproj", + "src\\Sentry.Extensions.Logging\\Sentry.Extensions.Logging.csproj", + "test\\Sentry.Testing\\Sentry.Testing.csproj", + "test\\Sentry.Tests\\Sentry.Tests.csproj", + "test\\Sentry.AspNetCore.Tests\\Sentry.AspNetCore.Tests.csproj", + "test\\Sentry.AspNetCore.Grpc.Tests\\Sentry.AspNetCore.Grpc.Tests.csproj", + "test\\Sentry.Extensions.Logging.Tests\\Sentry.Extensions.Logging.Tests.csproj", + ] + } +} diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs index 346761d703..b729d0fed2 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs @@ -1,7 +1,14 @@ +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; /// @@ -70,4 +77,51 @@ public SentryAspNetCoreOptions() // Don't report Environment.UserName as the user. IsEnvironmentUser = false; } + + internal void SetEnvironment(IWebHostEnvironment hostingEnvironment) + { + // Set environment from AspNetCore hosting environment name, if not set already + // Note: The SettingLocator will take care of the default behavior and assignment, which takes precedence. + // We only need to do anything here if nothing was found by the locator. + if (SettingLocator.GetEnvironment(useDefaultIfNotFound: false) is not null) + { + return; + } + + if (AdjustStandardEnvironmentNameCasing) + { + // NOTE: Sentry prefers to have its environment setting to be all lower case. + // .NET Core sets the ENV variable to 'Production' (upper case P), + // 'Development' (upper case D) or 'Staging' (upper case S) which conflicts with + // the Sentry recommendation. As such, we'll be kind and override those values, + // here ... if applicable. + // Assumption: The Hosting Environment is always set. + // If not set by a developer, then the framework will auto set it. + // Alternatively, developers might set this to a CUSTOM value, which we + // need to respect (especially the case-sensitivity). + // REF: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments + + if (hostingEnvironment.IsProduction()) + { + Environment = Internal.Constants.ProductionEnvironmentSetting; + } + else if (hostingEnvironment.IsStaging()) + { + Environment = Internal.Constants.StagingEnvironmentSetting; + } + else if (hostingEnvironment.IsDevelopment()) + { + Environment = Internal.Constants.DevelopmentEnvironmentSetting; + } + else + { + // Use the value set by the developer. + Environment = hostingEnvironment.EnvironmentName; + } + } + else + { + Environment = hostingEnvironment.EnvironmentName; + } + } } diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs index a3260bfff1..8cfbef1769 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs @@ -17,16 +17,25 @@ namespace Sentry.AspNetCore /// public class SentryAspNetCoreOptionsSetup : ConfigureFromConfigurationOptions { - private readonly IHostingEnvironment _hostingEnvironment; + /// + /// Creates a new instance of . + /// + public SentryAspNetCoreOptionsSetup( + ILoggerProviderConfiguration providerConfiguration) + : base(providerConfiguration.Configuration) + { + } /// /// Creates a new instance of . /// + [Obsolete("Use constructor with no IHostingEnvironment")] public SentryAspNetCoreOptionsSetup( ILoggerProviderConfiguration providerConfiguration, IHostingEnvironment hostingEnvironment) : base(providerConfiguration.Configuration) - => _hostingEnvironment = hostingEnvironment; + { + } /// /// Configures the . @@ -35,48 +44,6 @@ public override void Configure(SentryAspNetCoreOptions options) { base.Configure(options); - // Set environment from AspNetCore hosting environment name, if not set already - // Note: The SettingLocator will take care of the default behavior and assignment, which takes precedence. - // We only need to do anything here if nothing was found by the locator. - if (options.SettingLocator.GetEnvironment(useDefaultIfNotFound: false) is null) - { - if (!options.AdjustStandardEnvironmentNameCasing) - { - options.Environment = _hostingEnvironment.EnvironmentName; - } - else - { - // NOTE: Sentry prefers to have its environment setting to be all lower case. - // .NET Core sets the ENV variable to 'Production' (upper case P), - // 'Development' (upper case D) or 'Staging' (upper case S) which conflicts with - // the Sentry recommendation. As such, we'll be kind and override those values, - // here ... if applicable. - // Assumption: The Hosting Environment is always set. - // If not set by a developer, then the framework will auto set it. - // Alternatively, developers might set this to a CUSTOM value, which we - // need to respect (especially the case-sensitivity). - // REF: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments - - if (_hostingEnvironment.IsProduction()) - { - options.Environment = Internal.Constants.ProductionEnvironmentSetting; - } - else if (_hostingEnvironment.IsStaging()) - { - options.Environment = Internal.Constants.StagingEnvironmentSetting; - } - else if (_hostingEnvironment.IsDevelopment()) - { - options.Environment = Internal.Constants.DevelopmentEnvironmentSetting; - } - else - { - // Use the value set by the developer. - options.Environment = _hostingEnvironment.EnvironmentName; - } - } - } - 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 diff --git a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs index b468181e6b..7af2cac874 100644 --- a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs @@ -49,7 +49,13 @@ public static IWebHostBuilder UseSentry( this IWebHostBuilder builder, Action? configureOptions) => builder.UseSentry((context, sentryBuilder) => - sentryBuilder.AddSentryOptions(options => configureOptions?.Invoke(context, options))); + { + sentryBuilder.AddSentryOptions(options => + { + configureOptions?.Invoke(context, options); + options.SetEnvironment(context.HostingEnvironment); + }); + }); /// /// Uses Sentry integration. @@ -90,6 +96,7 @@ public static IWebHostBuilder UseSentry( var sentryBuilder = logging.Services.AddSentry(); configureSentry?.Invoke(context, sentryBuilder); + }); _ = builder.ConfigureServices(c => _ = c.AddTransient()); diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core2_1.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core2_1.verified.txt index 884358fa3d..c642e92aa5 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core2_1.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core2_1.verified.txt @@ -49,6 +49,8 @@ namespace Sentry.AspNetCore } public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions { + public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } + [System.Obsolete("Use constructor with no IHostingEnvironment")] public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) { } public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core3_1.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core3_1.verified.txt index 3ea27ee7c5..c9fcd65cbf 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core3_1.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.Core3_1.verified.txt @@ -49,6 +49,8 @@ namespace Sentry.AspNetCore } public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions { + public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } + [System.Obsolete("Use constructor with no IHostingEnvironment")] public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration, Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment) { } public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt index 884358fa3d..c642e92aa5 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet4_6.verified.txt @@ -49,6 +49,8 @@ namespace Sentry.AspNetCore } public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions { + public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } + [System.Obsolete("Use constructor with no IHostingEnvironment")] public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration, Microsoft.AspNetCore.Hosting.IHostingEnvironment hostingEnvironment) { } public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet5_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet5_0.verified.txt index 70c9ec4c7d..6efc6e85b5 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet5_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet5_0.verified.txt @@ -49,6 +49,8 @@ namespace Sentry.AspNetCore } public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions { + public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } + [System.Obsolete("Use constructor with no IHostingEnvironment")] public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration, Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment) { } public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } 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 70c9ec4c7d..6efc6e85b5 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet6_0.verified.txt @@ -49,6 +49,8 @@ namespace Sentry.AspNetCore } public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.ConfigureFromConfigurationOptions { + public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } + [System.Obsolete("Use constructor with no IHostingEnvironment")] public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration, Microsoft.AspNetCore.Hosting.IWebHostEnvironment hostingEnvironment) { } public override void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } } diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs index 0da4e41537..f91498b715 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs @@ -1,19 +1,19 @@ -#if NETCOREAPP2_1 || NET461 -using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; -#else -using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; -#endif using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; using Sentry.Testing; +#if NETCOREAPP3_1_OR_GREATER +using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; +#else +using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; +#endif + namespace Sentry.AspNetCore.Tests; public class SentryAspNetCoreOptionsSetupTests { private readonly SentryAspNetCoreOptionsSetup _sut = new( - Substitute.For>(), - Substitute.For()); + Substitute.For>()); private readonly SentryAspNetCoreOptions _target = new(); @@ -55,16 +55,10 @@ public void Filters_Environment_CustomOrASPNETEnvironment_Set(string environment // Arrange. var hostingEnvironment = Substitute.For(); hostingEnvironment.EnvironmentName = hostingEnvironmentSetting; - - var sut = new SentryAspNetCoreOptionsSetup( - Substitute.For>(), - hostingEnvironment); - - //const string environment = "some environment"; _target.Environment = environment; // Act. - sut.Configure(_target); + _target.SetEnvironment(hostingEnvironment); // Assert. Assert.Equal(expectedEnvironment, _target.Environment); @@ -85,15 +79,11 @@ public void Filters_Environment_AdjustStandardEnvironmentNameCasing_AffectsSentr var hostingEnvironment = Substitute.For(); hostingEnvironment.EnvironmentName = hostingEnvironmentSetting; - var sut = new SentryAspNetCoreOptionsSetup( - Substitute.For>(), - hostingEnvironment); - _target.Environment = environment; _target.AdjustStandardEnvironmentNameCasing = adjustStandardEnvironmentNameCasingSetting; // Act. - sut.Configure(_target); + _target.SetEnvironment(hostingEnvironment); // Assert. Assert.Equal(expectedEnvironment, _target.Environment); @@ -105,10 +95,11 @@ public void Filters_Environment_AdjustStandardEnvironmentNameCasing_AffectsSentr public void Filters_Environment_SentryEnvironment_Set(string environment) { // Arrange. + var hostingEnvironment = Substitute.For(); _target.FakeSettings().EnvironmentVariables[Internal.Constants.EnvironmentEnvironmentVariable] = environment; // Act. - _sut.Configure(_target); + _target.SetEnvironment(hostingEnvironment); // Assert. Assert.Equal(environment, _target.Environment);