diff --git a/benchmarks/Sentry.Benchmarks/Extensions.Logging/SentryStructuredLoggerBenchmarks.cs b/benchmarks/Sentry.Benchmarks/Extensions.Logging/SentryStructuredLoggerBenchmarks.cs index 08beda17f3..08f7296882 100644 --- a/benchmarks/Sentry.Benchmarks/Extensions.Logging/SentryStructuredLoggerBenchmarks.cs +++ b/benchmarks/Sentry.Benchmarks/Extensions.Logging/SentryStructuredLoggerBenchmarks.cs @@ -23,10 +23,6 @@ public void Setup() { Dsn = DsnSamples.ValidDsn, EnableLogs = true, - ExperimentalLogging = - { - MinimumLogLevel = LogLevel.Information, - } }; options.SetBeforeSendLog((SentryLog log) => { diff --git a/samples/Sentry.Samples.AspNetCore.Basic/Program.cs b/samples/Sentry.Samples.AspNetCore.Basic/Program.cs index 18cee62d80..96788a87c0 100644 --- a/samples/Sentry.Samples.AspNetCore.Basic/Program.cs +++ b/samples/Sentry.Samples.AspNetCore.Basic/Program.cs @@ -16,7 +16,12 @@ options.Debug = true; #endif - // This option enables Logs sent to Sentry. + // Configure the minimum Log Level of Breadcrumbs and Events + options.MinimumBreadcrumbLevel = LogLevel.Information; + options.MinimumEventLevel = LogLevel.Error; + + // This option enables Logs sent to Sentry + // Configure the minimum Log Level of Structured-Logs via e.g. "appsettings.json" and "appsettings.{HostEnvironment}.json" options.EnableLogs = true; }); diff --git a/samples/Sentry.Samples.AspNetCore.Basic/appsettings.json b/samples/Sentry.Samples.AspNetCore.Basic/appsettings.json new file mode 100644 index 0000000000..d61e9906ed --- /dev/null +++ b/samples/Sentry.Samples.AspNetCore.Basic/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + }, + "Sentry": { + "LogLevel": { + "Default": "Trace" + } + } + }, + "AllowedHosts": "*" +} diff --git a/samples/Sentry.Samples.GenericHost/SampleHostedService.cs b/samples/Sentry.Samples.GenericHost/SampleHostedService.cs index c99f00d7dc..aeb8149460 100644 --- a/samples/Sentry.Samples.GenericHost/SampleHostedService.cs +++ b/samples/Sentry.Samples.GenericHost/SampleHostedService.cs @@ -7,6 +7,8 @@ internal class SampleHostedService(IHub hub, ILogger logger { public Task StartAsync(CancellationToken cancellationToken) { + // Configure structured logging via appsettings.json (Logging:Sentry:LogLevel:) + logger.LogTrace("LogLevel.Trace is not configured to be sent as structured log"); // Logging integration by default keeps informational logs as Breadcrumb logger.LogInformation("Starting sample hosted service. This goes as a breadcrumb"); // You can also add breadcrumb directly through Sentry.Hub: diff --git a/samples/Sentry.Samples.GenericHost/appsettings.json b/samples/Sentry.Samples.GenericHost/appsettings.json index 256a4dc806..dbd12c4887 100644 --- a/samples/Sentry.Samples.GenericHost/appsettings.json +++ b/samples/Sentry.Samples.GenericHost/appsettings.json @@ -2,12 +2,18 @@ "Logging": { "LogLevel": { "Default": "Trace" + }, + "Sentry": { + "LogLevel": { + "Default": "Information" // Configure structured logs + } } }, "Sentry": { //"Dsn": "TODO: Configure your DSN here and uncomment this line", "MinimumBreadcrumbLevel": "Debug", "MinimumEventLevel": "Warning", - "SendDefaultPii": true // Send user name and machine name + "SendDefaultPii": true, // Send user name and machine name + "EnableLogs": true // Send structured logs } } diff --git a/samples/Sentry.Samples.Google.Cloud.Functions/Function.cs b/samples/Sentry.Samples.Google.Cloud.Functions/Function.cs index 7c151d6ee8..3abcd098e4 100644 --- a/samples/Sentry.Samples.Google.Cloud.Functions/Function.cs +++ b/samples/Sentry.Samples.Google.Cloud.Functions/Function.cs @@ -16,6 +16,7 @@ public class Function : IHttpFunction public Task HandleAsync(HttpContext context) { + _logger.LogTrace("LogLevel.Trace is not configured to be sent as structured log"); _logger.LogInformation("Useful info that is added to the breadcrumb list."); throw new Exception("Bad function"); } diff --git a/samples/Sentry.Samples.Google.Cloud.Functions/appsettings.json b/samples/Sentry.Samples.Google.Cloud.Functions/appsettings.json index 0f384e48c8..b572cc23fa 100644 --- a/samples/Sentry.Samples.Google.Cloud.Functions/appsettings.json +++ b/samples/Sentry.Samples.Google.Cloud.Functions/appsettings.json @@ -1,8 +1,19 @@ { + "Logging": { + "LogLevel": { + "Default": "Debug" + }, + "Sentry": { + "LogLevel": { + "Default": "Information" + } + } + }, "Sentry": { //"Dsn": "TODO: Configure your DSN here and uncomment this line", "MaxRequestBodySize": "Always", "SendDefaultPii": true, - "EnableTracing": true + "EnableTracing": true, + "EnableLogs" : true } } diff --git a/samples/Sentry.Samples.ME.Logging/Program.cs b/samples/Sentry.Samples.ME.Logging/Program.cs index 996a2aa59f..b966a243ed 100644 --- a/samples/Sentry.Samples.ME.Logging/Program.cs +++ b/samples/Sentry.Samples.ME.Logging/Program.cs @@ -23,7 +23,6 @@ // Optionally configure options: The default values are: options.MinimumBreadcrumbLevel = LogLevel.Information; // It requires at least this level to store breadcrumb options.MinimumEventLevel = LogLevel.Error; // This level or above will result in event sent to Sentry - options.ExperimentalLogging.MinimumLogLevel = LogLevel.Trace; // This level or above will result in log sent to Sentry // This option enables Logs sent to Sentry. options.EnableLogs = true; diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs b/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs index a1e7516ea4..5ad52c8e9b 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs @@ -1,14 +1,15 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; +using Sentry.Infrastructure; namespace Sentry.AspNetCore; /// -/// Logger provider for Sentry. +/// Sentry Logger Provider for and . /// [ProviderAlias("Sentry")] -public class SentryAspNetCoreLoggerProvider : SentryLoggerProvider +internal sealed class SentryAspNetCoreLoggerProvider : SentryLoggerProvider { /// /// Creates a new instance of @@ -17,4 +18,9 @@ public SentryAspNetCoreLoggerProvider(IOptions options, : base(options, hub) { } + + internal SentryAspNetCoreLoggerProvider(SentryAspNetCoreOptions options, IHub hub, ISystemClock clock) + : base(hub, clock, options) + { + } } diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs index d5af7f3e3a..3f428f58f3 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs @@ -9,7 +9,7 @@ namespace Sentry.AspNetCore; /// Sets up ASP.NET Core option for Sentry. /// #if NETSTANDARD2_0 -public class SentryAspNetCoreOptionsSetup : ConfigureFromConfigurationOptions +internal sealed class SentryAspNetCoreOptionsSetup : ConfigureFromConfigurationOptions { /// /// Creates a new instance of . @@ -32,7 +32,7 @@ public override void Configure(SentryAspNetCoreOptions options) } #else -public class SentryAspNetCoreOptionsSetup : IConfigureOptions +internal sealed class SentryAspNetCoreOptionsSetup : IConfigureOptions { private readonly IConfiguration _config; diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs b/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs index 90cb033375..42f7cd8cc3 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs @@ -6,9 +6,9 @@ namespace Sentry.AspNetCore; /// -/// Structured Logger Provider for Sentry. +/// Sentry Logger Provider for . /// -[ProviderAlias("SentryLogs")] +[ProviderAlias("Sentry")] internal sealed class SentryAspNetCoreStructuredLoggerProvider : SentryStructuredLoggerProvider { public SentryAspNetCoreStructuredLoggerProvider(IOptions options, IHub hub) diff --git a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs index 2b5f74bf4d..3b021d6c65 100644 --- a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs @@ -95,14 +95,15 @@ public static IWebHostBuilder UseSentry( _ = logging.Services.AddSingleton(); _ = logging.Services.AddSingleton(); - _ = logging.AddFilter( - "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", - LogLevel.None); - _ = logging.AddFilter(static (string? categoryName, LogLevel logLevel) => + // Add a delegate rule in order to ignore Configuration like "appsettings.json" and "appsettings.{HostEnvironment}.json" + _ = logging.AddFilter(static (string? categoryName, LogLevel logLevel) => { return categoryName is null - || (categoryName != "Sentry.ISentryClient" && categoryName != "Sentry.AspNetCore.SentryMiddleware"); + || categoryName != "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware"; }); + // Add non-delegate rules in order to respect Configuration like "appsettings.json" and "appsettings.{HostEnvironment}.json" + _ = logging.AddFilter("Sentry.ISentryClient", LogLevel.None); + _ = logging.AddFilter("Sentry.AspNetCore.SentryMiddleware", LogLevel.None); var sentryBuilder = logging.Services.AddSentry(); configureSentry?.Invoke(context, sentryBuilder); diff --git a/src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs b/src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs index 7843ad9975..299e61a21c 100644 --- a/src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs +++ b/src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs @@ -9,20 +9,11 @@ internal class BindableSentryLoggingOptions : BindableSentryOptions public LogLevel? MinimumEventLevel { get; set; } public bool? InitializeSdk { get; set; } - public BindableSentryLoggingExperimentalOptions ExperimentalLogging { get; set; } = new(); - - internal sealed class BindableSentryLoggingExperimentalOptions - { - public LogLevel? MinimumLogLevel { get; set; } - } - public void ApplyTo(SentryLoggingOptions options) { base.ApplyTo(options); options.MinimumBreadcrumbLevel = MinimumBreadcrumbLevel ?? options.MinimumBreadcrumbLevel; options.MinimumEventLevel = MinimumEventLevel ?? options.MinimumEventLevel; options.InitializeSdk = InitializeSdk ?? options.InitializeSdk; - - options.ExperimentalLogging.MinimumLogLevel = ExperimentalLogging.MinimumLogLevel ?? options.ExperimentalLogging.MinimumLogLevel; } } diff --git a/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs b/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs index f2a4957c11..7731288078 100644 --- a/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs +++ b/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs @@ -60,12 +60,8 @@ internal static ILoggingBuilder AddSentry( builder.AddFilter(_ => true); // Logs from the SentryLogger should not flow to the SentryStructuredLogger as this may cause recursive invocations. - // Filtering of logs is handled in SentryStructuredLogger, using SentryOptions.MinimumLogLevel - builder.AddFilter(static (string? categoryName, LogLevel logLevel) => - { - return categoryName is null - || categoryName != "Sentry.ISentryClient"; - }); + // Filtering of structured logs is handled by Microsoft.Extensions.Configuration and Microsoft.Extensions.Options. + builder.AddFilter("Sentry.ISentryClient", LogLevel.None); return builder; } diff --git a/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs b/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs index 454f070dd8..034e7b4694 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs @@ -6,10 +6,10 @@ namespace Sentry.Extensions.Logging; /// -/// Sentry Logger Provider. +/// Sentry Logger Provider for and . /// [ProviderAlias("Sentry")] -public class SentryLoggerProvider : ILoggerProvider +internal class SentryLoggerProvider : ILoggerProvider { private readonly ISystemClock _clock; private readonly SentryLoggingOptions _options; diff --git a/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs b/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs index d181b645bf..3014f7cd0e 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs @@ -50,39 +50,4 @@ public class SentryLoggingOptions : SentryOptions /// List of callbacks to be invoked when initializing the SDK /// internal Action[] ConfigureScopeCallbacks { get; set; } = Array.Empty>(); - - /// - /// Experimental Sentry Logging features. - /// - /// - /// This and related experimental APIs may change in the future. - /// - [Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)] - public SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } = new(); - - /// - /// Experimental Sentry Logging options. - /// - /// - /// This and related experimental APIs may change in the future. - /// - [Experimental(Infrastructure.DiagnosticId.ExperimentalFeature)] - public sealed class SentryLoggingExperimentalOptions - { - internal SentryLoggingExperimentalOptions() - { - } - - /// - /// Gets or sets the minimum log level. - /// This API is experimental and it may change in the future. - /// - /// - /// Logs with this level or higher will be stored as . - /// - /// - /// The minimum log level. - /// - public LogLevel MinimumLogLevel { get; set; } = LogLevel.Trace; - } } diff --git a/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs b/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs index b220d936b5..401d3a864a 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs @@ -5,7 +5,7 @@ namespace Sentry.Extensions.Logging; -internal class SentryLoggingOptionsSetup : IConfigureOptions +internal sealed class SentryLoggingOptionsSetup : IConfigureOptions { private readonly IConfiguration _config; @@ -30,7 +30,7 @@ public void Configure(SentryLoggingOptions options) namespace Sentry.Extensions.Logging; -internal class SentryLoggingOptionsSetup : ConfigureFromConfigurationOptions +internal sealed class SentryLoggingOptionsSetup : ConfigureFromConfigurationOptions { public SentryLoggingOptionsSetup( ILoggerProviderConfiguration providerConfiguration) diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index d38fcf6f70..c5d526253d 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -30,8 +30,7 @@ public bool IsEnabled(LogLevel logLevel) { return _hub.IsEnabled && _options.EnableLogs - && logLevel != LogLevel.None - && logLevel >= _options.ExperimentalLogging.MinimumLogLevel; + && logLevel != LogLevel.None; } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs b/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs index fdfe0a527c..bf74a36da0 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs @@ -5,9 +5,9 @@ namespace Sentry.Extensions.Logging; /// -/// Sentry Structured Logger Provider. +/// Sentry Logger Provider for . /// -[ProviderAlias("SentryLogs")] +[ProviderAlias("Sentry")] internal class SentryStructuredLoggerProvider : ILoggerProvider { private readonly SentryLoggingOptions _options; @@ -20,6 +20,11 @@ public SentryStructuredLoggerProvider(IOptions options, IH { } + internal SentryStructuredLoggerProvider(IHub hub, ISystemClock clock, SentryLoggingOptions options) + : this(options, hub, clock, CreateSdkVersion()) + { + } + internal SentryStructuredLoggerProvider(SentryLoggingOptions options, IHub hub, ISystemClock clock, SdkVersion sdk) { _options = options; diff --git a/src/Sentry.Google.Cloud.Functions/SentryStartup.cs b/src/Sentry.Google.Cloud.Functions/SentryStartup.cs index 73cae34ce8..ad7e485043 100644 --- a/src/Sentry.Google.Cloud.Functions/SentryStartup.cs +++ b/src/Sentry.Google.Cloud.Functions/SentryStartup.cs @@ -57,10 +57,17 @@ public override void ConfigureLogging(WebHostBuilderContext context, ILoggingBui logging.Services.AddSingleton, SentryAspNetCoreOptionsSetup>(); logging.Services.AddSingleton(); + logging.Services.AddSingleton(); - logging.AddFilter( - "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware", - LogLevel.None); + // Add a delegate rule in order to ignore Configuration like "appsettings.json" and "appsettings.{HostEnvironment}.json" + logging.AddFilter(static (string? categoryName, LogLevel logLevel) => + { + return categoryName is null + || categoryName != "Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware"; + }); + // Add non-delegate rules in order to respect Configuration like "appsettings.json" and "appsettings.{HostEnvironment}.json" + logging.AddFilter("Sentry.ISentryClient", LogLevel.None); + logging.AddFilter("Sentry.AspNetCore.SentryMiddleware", LogLevel.None); logging.Services.AddSentry(); } diff --git a/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs b/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs index df33ef2342..dddd59bae2 100644 --- a/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs +++ b/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs @@ -1,14 +1,23 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; +using Sentry.Infrastructure; namespace Sentry.Maui.Internal; +/// +/// Sentry Logger Provider for and . +/// [ProviderAlias("Sentry")] -internal class SentryMauiLoggerProvider : SentryLoggerProvider +internal sealed class SentryMauiLoggerProvider : SentryLoggerProvider { public SentryMauiLoggerProvider(IOptions options, IHub hub) : base(options, hub) { } + + internal SentryMauiLoggerProvider(SentryMauiOptions options, IHub hub, ISystemClock clock) + : base(hub, clock, options) + { + } } diff --git a/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs b/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs index d0525a4b49..a72ffafc46 100644 --- a/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs +++ b/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs @@ -5,7 +5,10 @@ namespace Sentry.Maui.Internal; -[ProviderAlias("SentryLogs")] +/// +/// Sentry Logger Provider for . +/// +[ProviderAlias("Sentry")] internal sealed class SentryMauiStructuredLoggerProvider : SentryStructuredLoggerProvider { public SentryMauiStructuredLoggerProvider(IOptions options, IHub hub) diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index 9274cf5812..579c2b63e6 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -56,11 +56,10 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, services.AddSingleton, SentryMauiOptionsSetup>(); services.AddSingleton(); - builder.Logging.AddFilter(static (string? categoryName, LogLevel logLevel) => - { - return categoryName is null - || categoryName != "Sentry.ISentryClient"; - }); + // Add a delegate rule in order to ignore Configuration like "appsettings.json" and "appsettings.{HostEnvironment}.json" + builder.Logging.AddFilter(_ => true); + // Add non-delegate rules in order to respect Configuration like "appsettings.json" and "appsettings.{HostEnvironment}.json" + builder.Logging.AddFilter("Sentry.ISentryClient", LogLevel.None); // Add default event binders services.AddSingleton(); diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 4ca83a4f8f..7712a7f158 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -39,11 +39,6 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryAspNetCoreLoggerProvider : Sentry.Extensions.Logging.SentryLoggerProvider - { - public SentryAspNetCoreLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - } public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions { public SentryAspNetCoreOptions() { } @@ -55,11 +50,6 @@ namespace Sentry.AspNetCore public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } } - public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions - { - public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } - public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } - } public static class SentryBuilderExtensions { public static Sentry.AspNetCore.ISentryBuilder AddSentryOptions(this Sentry.AspNetCore.ISentryBuilder builder, System.Action? configureOptions) { } 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 4ca83a4f8f..7712a7f158 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -39,11 +39,6 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryAspNetCoreLoggerProvider : Sentry.Extensions.Logging.SentryLoggerProvider - { - public SentryAspNetCoreLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - } public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions { public SentryAspNetCoreOptions() { } @@ -55,11 +50,6 @@ namespace Sentry.AspNetCore public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } } - public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions - { - public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } - public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } - } public static class SentryBuilderExtensions { public static Sentry.AspNetCore.ISentryBuilder AddSentryOptions(this Sentry.AspNetCore.ISentryBuilder builder, System.Action? configureOptions) { } diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 4ca83a4f8f..7712a7f158 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -39,11 +39,6 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryAspNetCoreLoggerProvider : Sentry.Extensions.Logging.SentryLoggerProvider - { - public SentryAspNetCoreLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - } public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions { public SentryAspNetCoreOptions() { } @@ -55,11 +50,6 @@ namespace Sentry.AspNetCore public Sentry.Extensibility.RequestSize MaxRequestBodySize { get; set; } public Sentry.AspNetCore.TransactionNameProvider? TransactionNameProvider { get; set; } } - public class SentryAspNetCoreOptionsSetup : Microsoft.Extensions.Options.IConfigureOptions - { - public SentryAspNetCoreOptionsSetup(Microsoft.Extensions.Logging.Configuration.ILoggerProviderConfiguration providerConfiguration) { } - public void Configure(Sentry.AspNetCore.SentryAspNetCoreOptions options) { } - } public static class SentryBuilderExtensions { public static Sentry.AspNetCore.ISentryBuilder AddSentryOptions(this Sentry.AspNetCore.ISentryBuilder builder, System.Action? configureOptions) { } diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreLoggerProviderTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreLoggerProviderTests.cs new file mode 100644 index 0000000000..7dad3fdf71 --- /dev/null +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreLoggerProviderTests.cs @@ -0,0 +1,70 @@ +#nullable enable + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Sentry.Extensions.Logging; + +namespace Sentry.AspNetCore.Tests; + +public class SentryAspNetCoreLoggerProviderTests +{ + private class Fixture + { + public IOptions Options { get; } + public IHub Hub { get; } + public MockClock Clock { get; } + + public Fixture() + { + var loggingOptions = new SentryAspNetCoreOptions(); + + Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); + Hub = Substitute.For(); + Clock = new MockClock(); + } + + public SentryAspNetCoreLoggerProvider GetSut() + { + return new SentryAspNetCoreLoggerProvider(Options.Value, Hub, Clock); + } + } + + private readonly Fixture _fixture = new(); + + [Fact] + public void Type_CustomAttributes_HasProviderAliasAttribute() + { + var type = typeof(SentryAspNetCoreLoggerProvider); + + type.GetCustomAttributes().Should() + .ContainSingle().Which + .Alias.Should().Be("Sentry"); + } + + [Fact] + public void Ctor_DependencyInjection_CanCreate() + { + using var services = new ServiceCollection() + .AddLogging() + .AddSingleton() + .AddSingleton(_fixture.Options) + .AddSingleton(_fixture.Hub) + .BuildServiceProvider(); + + var logger = services.GetRequiredService>(); + + logger.Should().BeOfType>(); + } + + [Fact] + public void CreateLogger_OfType() + { + var provider = _fixture.GetSut(); + + var logger = provider.CreateLogger("CategoryName"); + + logger.Should().BeOfType() + .Which.CategoryName.Should().Be("CategoryName"); + } +} diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs index def969676a..f7fc58be9b 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs @@ -40,6 +40,16 @@ public SentryAspNetCoreStructuredLoggerProvider GetSut() private readonly Fixture _fixture = new(); + [Fact] + public void Type_CustomAttributes_HasProviderAliasAttribute() + { + var type = typeof(SentryAspNetCoreStructuredLoggerProvider); + + type.GetCustomAttributes().Should() + .ContainSingle().Which + .Alias.Should().Be("Sentry"); + } + [Fact] public void Ctor_DependencyInjection_CanCreate() { diff --git a/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs b/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs index 09a70d85b2..236e482fc6 100644 --- a/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs @@ -1,6 +1,9 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Sentry.Extensions.Logging; #if NETCOREAPP3_1_OR_GREATER using IHostingEnvironment = Microsoft.AspNetCore.Hosting.IWebHostEnvironment; @@ -74,4 +77,63 @@ public static IEnumerable ExpectedServices() new Action(c => Assert.Single(c, d => d.ImplementationType == typeof(SentryStartupFilter)))}; } + + [Fact] + public void UseSentry_Logging_AddLoggerProviders() + { +#if NET8_0 + var section = Substitute.For(); + section[Arg.Any()].Returns((string)null); + Configuration.GetSection("Sentry").Returns(section); +#endif + WebHostBuilder.UseSentry((SentryAspNetCoreOptions options) => + { + options.EnableLogs = true; + options.InitializeSdk = false; + }); + using var serviceProvider = Services.BuildServiceProvider(); + + var providers = serviceProvider.GetRequiredService>().ToArray(); + + providers.Should().HaveCount(2); + providers[0].Should().BeOfType(); + providers[1].Should().BeOfType(); + } + + [Fact] + public void UseSentry_Logging_AddLoggerFilterRules() + { + WebHostBuilder.UseSentry((SentryAspNetCoreOptions options) => + { + options.EnableLogs = true; + options.InitializeSdk = false; + }); + using var serviceProvider = Services.BuildServiceProvider(); + + var loggerFilterOptions = serviceProvider.GetRequiredService>().Value; + + loggerFilterOptions.Rules.Should().HaveCount(3); + var one = loggerFilterOptions.Rules[0]; + var two = loggerFilterOptions.Rules[1]; + var three = loggerFilterOptions.Rules[2]; + + one.ProviderName.Should().Be(typeof(SentryAspNetCoreLoggerProvider).FullName); + one.CategoryName.Should().BeNull(); + one.LogLevel.Should().BeNull(); + one.Filter.Should().NotBeNull(); + one.Filter!.Invoke(null, null, LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("", "", LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("type", "category", LogLevel.None).Should().BeTrue(); + one.Filter.Invoke(null, typeof(Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware).FullName, LogLevel.None).Should().BeFalse(); + + two.ProviderName.Should().Be(typeof(SentryAspNetCoreStructuredLoggerProvider).FullName); + two.CategoryName.Should().Be(typeof(ISentryClient).FullName); + two.LogLevel.Should().Be(LogLevel.None); + two.Filter.Should().BeNull(); + + three.ProviderName.Should().Be(typeof(SentryAspNetCoreStructuredLoggerProvider).FullName); + three.CategoryName.Should().Be(typeof(SentryMiddleware).FullName); + three.LogLevel.Should().Be(LogLevel.None); + three.Filter.Should().BeNull(); + } } diff --git a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 9112ddfffa..8650a342f4 100644 --- a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -28,27 +28,13 @@ namespace Sentry.Extensions.Logging public bool IsEnabled(Sentry.SentryLevel level) { } public void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, System.IDisposable - { - public SentryLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } - public void Dispose() { } - } public class SentryLoggingOptions : Sentry.SentryOptions { public SentryLoggingOptions() { } - [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public Sentry.Extensions.Logging.SentryLoggingOptions.SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } public bool InitializeSdk { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } public void ConfigureScope(System.Action action) { } - [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public sealed class SentryLoggingExperimentalOptions - { - public Microsoft.Extensions.Logging.LogLevel MinimumLogLevel { get; set; } - } } public static class SentryLoggingOptionsExtensions { diff --git a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 9112ddfffa..8650a342f4 100644 --- a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -28,27 +28,13 @@ namespace Sentry.Extensions.Logging public bool IsEnabled(Sentry.SentryLevel level) { } public void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, System.IDisposable - { - public SentryLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } - public void Dispose() { } - } public class SentryLoggingOptions : Sentry.SentryOptions { public SentryLoggingOptions() { } - [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public Sentry.Extensions.Logging.SentryLoggingOptions.SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } public bool InitializeSdk { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } public void ConfigureScope(System.Action action) { } - [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public sealed class SentryLoggingExperimentalOptions - { - public Microsoft.Extensions.Logging.LogLevel MinimumLogLevel { get; set; } - } } public static class SentryLoggingOptionsExtensions { diff --git a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 9112ddfffa..8650a342f4 100644 --- a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -28,27 +28,13 @@ namespace Sentry.Extensions.Logging public bool IsEnabled(Sentry.SentryLevel level) { } public void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, System.IDisposable - { - public SentryLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } - public void Dispose() { } - } public class SentryLoggingOptions : Sentry.SentryOptions { public SentryLoggingOptions() { } - [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public Sentry.Extensions.Logging.SentryLoggingOptions.SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } public bool InitializeSdk { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } public void ConfigureScope(System.Action action) { } - [System.Diagnostics.CodeAnalysis.Experimental("SENTRY0001")] - public sealed class SentryLoggingExperimentalOptions - { - public Microsoft.Extensions.Logging.LogLevel MinimumLogLevel { get; set; } - } } public static class SentryLoggingOptionsExtensions { diff --git a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index e4dd758823..8650a342f4 100644 --- a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -28,25 +28,13 @@ namespace Sentry.Extensions.Logging public bool IsEnabled(Sentry.SentryLevel level) { } public void Log(Sentry.SentryLevel logLevel, string message, System.Exception? exception = null, params object?[] args) { } } - [Microsoft.Extensions.Logging.ProviderAlias("Sentry")] - public class SentryLoggerProvider : Microsoft.Extensions.Logging.ILoggerProvider, System.IDisposable - { - public SentryLoggerProvider(Microsoft.Extensions.Options.IOptions options, Sentry.IHub hub) { } - public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName) { } - public void Dispose() { } - } public class SentryLoggingOptions : Sentry.SentryOptions { public SentryLoggingOptions() { } - public Sentry.Extensions.Logging.SentryLoggingOptions.SentryLoggingExperimentalOptions ExperimentalLogging { get; set; } public bool InitializeSdk { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } public void ConfigureScope(System.Action action) { } - public sealed class SentryLoggingExperimentalOptions - { - public Microsoft.Extensions.Logging.LogLevel MinimumLogLevel { get; set; } - } } public static class SentryLoggingOptionsExtensions { diff --git a/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs b/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs new file mode 100644 index 0000000000..ab4cc48a26 --- /dev/null +++ b/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs @@ -0,0 +1,65 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Sentry.Extensions.Logging.Tests; + +public class LoggingBuilderExtensionsTests +{ + [Fact] + public void AddSentry_LoggingBuilder_AddLoggerProviders() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging((ILoggingBuilder builder) => builder.AddSentry(options => + { + options.EnableLogs = true; + options.InitializeSdk = false; + })); + using var serviceProvider = serviceCollection.BuildServiceProvider(); + using var loggerFactory = serviceProvider.GetRequiredService(); + + // Act + var providers = serviceProvider.GetRequiredService>().ToArray(); + + // Assert + providers.Should().HaveCount(2); + providers[0].Should().BeOfType(); + providers[1].Should().BeOfType(); + } + + [Fact] + public void AddSentry_LoggingBuilder_AddLoggerFilterRules() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging((ILoggingBuilder builder) => builder.AddSentry(options => + { + options.EnableLogs = true; + options.InitializeSdk = false; + })); + using var serviceProvider = serviceCollection.BuildServiceProvider(); + using var loggerFactory = serviceProvider.GetRequiredService(); + + // Act + var loggerFilterOptions = serviceProvider.GetRequiredService>().Value; + + // Assert + loggerFilterOptions.Rules.Should().HaveCount(2); + var one = loggerFilterOptions.Rules[0]; + var two = loggerFilterOptions.Rules[1]; + + one.ProviderName.Should().Be(typeof(SentryLoggerProvider).FullName); + one.CategoryName.Should().BeNull(); + one.LogLevel.Should().BeNull(); + one.Filter.Should().NotBeNull(); + one.Filter!.Invoke(null, null, LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("", "", LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("type", "category", LogLevel.None).Should().BeTrue(); + + two.ProviderName.Should().Be(typeof(SentryStructuredLoggerProvider).FullName); + two.CategoryName.Should().Be(typeof(ISentryClient).FullName); + two.LogLevel.Should().Be(LogLevel.None); + two.Filter.Should().BeNull(); + } +} diff --git a/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs b/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs index c16e5521ab..3c5559ccaf 100644 --- a/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs @@ -5,6 +5,8 @@ namespace Sentry.Extensions.Logging.Tests; public class LoggingTests { + private static readonly string CategoryName = "test_category"; + [SkippableTheory] [InlineData(LogLevel.Critical)] [InlineData(LogLevel.Error)] @@ -26,17 +28,19 @@ public void Log_CapturesEvent(LogLevel logLevel) serviceCollection.AddLogging(builder => builder.AddSentry(o => { o.Dsn = ValidDsn; - o.MinimumEventLevel = logLevel; o.MinimumBreadcrumbLevel = LogLevel.None; + o.MinimumEventLevel = logLevel; o.BackgroundWorker = worker; o.InitNativeSdks = false; + o.EnableLogs = false; })); + serviceCollection.Configure(options => options.AddFilter(CategoryName, LogLevel.None)); using var serviceProvider = serviceCollection.BuildServiceProvider(); using var loggerFactory = serviceProvider.GetRequiredService(); // Act const string message = "test message"; - var logger = loggerFactory.CreateLogger("test"); + var logger = loggerFactory.CreateLogger(CategoryName); logger.Log(logLevel, message); // Assert @@ -75,13 +79,15 @@ public void Log_AddsBreadcrumb(LogLevel logLevel) o.MinimumEventLevel = LogLevel.None; o.BackgroundWorker = worker; o.InitNativeSdks = false; + o.EnableLogs = false; })); + serviceCollection.Configure(options => options.AddFilter(CategoryName, LogLevel.None)); using var serviceProvider = serviceCollection.BuildServiceProvider(); using var loggerFactory = serviceProvider.GetRequiredService(); // Act const string message = "test message"; - var logger = loggerFactory.CreateLogger("test"); + var logger = loggerFactory.CreateLogger(CategoryName); logger.Log(logLevel, message); var hub = serviceProvider.GetRequiredService(); @@ -100,4 +106,52 @@ public void Log_AddsBreadcrumb(LogLevel logLevel) b.Message == message) != null)); } + + [SkippableTheory] + [InlineData(LogLevel.Critical)] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Information)] + [InlineData(LogLevel.Debug)] + [InlineData(LogLevel.Trace)] + public void Log_CapturesStructuredLog(LogLevel logLevel) + { +#if __IOS__ + Skip.If(true, "Flaky on iOS"); +#endif + + // Arrange + var worker = Substitute.For(); + + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(builder => builder.AddSentry(o => + { + o.Dsn = ValidDsn; + o.MinimumBreadcrumbLevel = LogLevel.None; + o.MinimumEventLevel = LogLevel.None; + o.BackgroundWorker = worker; + o.InitNativeSdks = false; + o.EnableLogs = true; + })); + serviceCollection.Configure(options => options.AddFilter(CategoryName, logLevel)); + using var serviceProvider = serviceCollection.BuildServiceProvider(); + using var loggerFactory = serviceProvider.GetRequiredService(); + + // Act + const string message = "test message"; + var logger = loggerFactory.CreateLogger(CategoryName); + logger.Log(logLevel, message); + + var hub = serviceProvider.GetRequiredService(); + hub.Logger.Flush(); + + // Assert + worker.Received(1).EnqueueEnvelope( + Arg.Is(e => + e.Items + .Select(i => i.Payload).OfType() + .Select(i => i.Source).OfType() + .SingleOrDefault(log => log.Length == 1) + != null)); + } } diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs index 20b608b74b..db4bb02e73 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Logging; + namespace Sentry.Extensions.Logging.Tests; public class SentryLoggerProviderTests @@ -11,6 +13,16 @@ private class Fixture private readonly Fixture _fixture = new(); + [Fact] + public void Type_CustomAttributes_HasProviderAliasAttribute() + { + var type = typeof(SentryLoggerProvider); + + type.GetCustomAttributes().Should() + .ContainSingle().Which + .Alias.Should().Be("Sentry"); + } + [Fact] public void CreateLogger_LoggerType_SentryLogger() { diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs index 798dad7bf2..6417344f91 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs @@ -58,7 +58,6 @@ public void Configure_BindsConfigurationToOptions() MinimumEventLevel = LogLevel.Error, InitializeSdk = true }; - expected.ExperimentalLogging.MinimumLogLevel = LogLevel.None; var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { @@ -109,8 +108,6 @@ public void Configure_BindsConfigurationToOptions() ["MinimumBreadcrumbLevel"] = expected.MinimumBreadcrumbLevel.ToString(), ["MinimumEventLevel"] = expected.MinimumEventLevel.ToString(), ["InitializeSdk"] = expected.InitializeSdk.ToString(), - - ["ExperimentalLogging:MinimumLogLevel"] = expected.ExperimentalLogging.MinimumLogLevel.ToString(), }) .Build(); @@ -169,8 +166,6 @@ public void Configure_BindsConfigurationToOptions() actual.MinimumBreadcrumbLevel.Should().Be(expected.MinimumBreadcrumbLevel); actual.MinimumEventLevel.Should().Be(expected.MinimumEventLevel); actual.InitializeSdk.Should().Be(expected.InitializeSdk); - - actual.ExperimentalLogging.MinimumLogLevel.Should().Be(expected.ExperimentalLogging.MinimumLogLevel); } } } diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs index 4d0afafcc0..2342e43e06 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs @@ -40,6 +40,16 @@ public SentryStructuredLoggerProvider GetSut() private readonly Fixture _fixture = new(); + [Fact] + public void Type_CustomAttributes_HasProviderAliasAttribute() + { + var type = typeof(SentryStructuredLoggerProvider); + + type.GetCustomAttributes().Should() + .ContainSingle().Which + .Alias.Should().Be("Sentry"); + } + [Fact] public void Ctor_DependencyInjection_CanCreate() { diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs index 4c2424967a..3153c0526a 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs @@ -44,12 +44,10 @@ public Fixture() EnableHub(true); EnableLogs(true); - SetMinimumLogLevel(default); } public void EnableHub(bool isEnabled) => Hub.IsEnabled.Returns(isEnabled); public void EnableLogs(bool isEnabled) => Options.Value.EnableLogs = isEnabled; - public void SetMinimumLogLevel(LogLevel logLevel) => Options.Value.ExperimentalLogging.MinimumLogLevel = logLevel; public void WithActiveSpan(SentryId traceId, SpanId parentSpanId) { @@ -251,20 +249,18 @@ public void Log_WithoutEventName_CaptureLog() } [Theory] - [InlineData(true, true, LogLevel.Warning, LogLevel.Warning, true)] - [InlineData(false, true, LogLevel.Warning, LogLevel.Warning, false)] - [InlineData(true, false, LogLevel.Warning, LogLevel.Warning, false)] - [InlineData(true, true, LogLevel.Information, LogLevel.Warning, true)] - [InlineData(true, true, LogLevel.Error, LogLevel.Warning, false)] - public void IsEnabled_HubOptionsMinimumLogLevel_Returns(bool isHubEnabled, bool isLogsEnabled, LogLevel minimumLogLevel, LogLevel actualLogLevel, bool expectedIsEnabled) + [InlineData(true, true, LogLevel.Information, true)] + [InlineData(false, true, LogLevel.Information, false)] + [InlineData(true, false, LogLevel.Information, false)] + [InlineData(true, true, LogLevel.None, false)] + public void IsEnabled_HubAndOptions_Returns(bool isHubEnabled, bool isLogsEnabled, LogLevel logLevel, bool expectedIsEnabled) { _fixture.EnableHub(isHubEnabled); _fixture.EnableLogs(isLogsEnabled); - _fixture.SetMinimumLogLevel(minimumLogLevel); var logger = _fixture.GetSut(); - var isEnabled = logger.IsEnabled(actualLogLevel); - logger.Log(actualLogLevel, "message"); + var isEnabled = logger.IsEnabled(logLevel); + logger.Log(logLevel, "message"); isEnabled.Should().Be(expectedIsEnabled); if (expectedIsEnabled) diff --git a/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs b/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs index 8006f81b5a..2057d17ffc 100644 --- a/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs +++ b/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs @@ -96,7 +96,7 @@ public void ConfigureLogging_SentryAspNetCoreOptions_FlushOnCompletedRequestTrue } [Theory, MemberData(nameof(ExpectedServices))] - public void UseSentry_Parameterless_ServicesRegistered(Action assert) + public void ConfigureLogging_Parameterless_ServicesRegistered(Action assert) { var sut = new SentryStartup(); sut.ConfigureLogging(WebHostBuilderContext, LoggingBuilder); @@ -121,4 +121,64 @@ public static IEnumerable ExpectedServices() new Action(c => Assert.Single(c, d => d.ImplementationType == typeof(AspNetCoreEventProcessor)))}; } + + [Fact] + public void ConfigureLogging_Logging_AddLoggerProviders() + { + LoggingBuilder.Services.Configure(options => + { + options.EnableLogs = true; + options.InitializeSdk = false; + }); + + var sut = new SentryStartup(); + sut.ConfigureLogging(WebHostBuilderContext, LoggingBuilder); + + using var serviceProvider = LoggingBuilder.Services.BuildServiceProvider(); + var providers = serviceProvider.GetRequiredService>().ToArray(); + + providers.Should().HaveCount(2); + providers[0].Should().BeOfType(); + providers[1].Should().BeOfType(); + } + + [Fact] + public void ConfigureLogging_Logging_AddLoggerFilterRules() + { + LoggingBuilder.Services.Configure(options => + { + options.EnableLogs = true; + options.InitializeSdk = false; + }); + + var sut = new SentryStartup(); + sut.ConfigureLogging(WebHostBuilderContext, LoggingBuilder); + + using var serviceProvider = LoggingBuilder.Services.BuildServiceProvider(); + var loggerFilterOptions = serviceProvider.GetRequiredService>().Value; + + loggerFilterOptions.Rules.Should().HaveCount(3); + var one = loggerFilterOptions.Rules[0]; + var two = loggerFilterOptions.Rules[1]; + var three = loggerFilterOptions.Rules[2]; + + one.ProviderName.Should().Be(typeof(SentryAspNetCoreLoggerProvider).FullName); + one.CategoryName.Should().BeNull(); + one.LogLevel.Should().BeNull(); + one.Filter.Should().NotBeNull(); + one.Filter!.Invoke(null, null, LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("", "", LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("type", "category", LogLevel.None).Should().BeTrue(); + one.Filter.Invoke(null, typeof(Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware).FullName, LogLevel.None).Should().BeFalse(); + + two.ProviderName.Should().Be(typeof(SentryAspNetCoreStructuredLoggerProvider).FullName); + two.CategoryName.Should().Be(typeof(ISentryClient).FullName); + two.LogLevel.Should().Be(LogLevel.None); + two.Filter.Should().BeNull(); + + three.ProviderName.Should().Be(typeof(SentryAspNetCoreStructuredLoggerProvider).FullName); + three.CategoryName.Should().Be(typeof(SentryMiddleware).FullName); + three.LogLevel.Should().Be(LogLevel.None); + three.Filter.Should().BeNull(); + } } diff --git a/test/Sentry.Maui.Tests/Internal/SentryMauiLoggerProviderTests.cs b/test/Sentry.Maui.Tests/Internal/SentryMauiLoggerProviderTests.cs new file mode 100644 index 0000000000..7548941a35 --- /dev/null +++ b/test/Sentry.Maui.Tests/Internal/SentryMauiLoggerProviderTests.cs @@ -0,0 +1,70 @@ +#nullable enable + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Sentry.Extensions.Logging; +using Sentry.Maui.Internal; + +namespace Sentry.Maui.Tests.Internal; + +public class SentryMauiLoggerProviderTests +{ + private class Fixture + { + public IOptions Options { get; } + public IHub Hub { get; } + public MockClock Clock { get; } + + public Fixture() + { + var loggingOptions = new SentryMauiOptions(); + + Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); + Hub = Substitute.For(); + Clock = new MockClock(); + } + + public SentryMauiLoggerProvider GetSut() + { + return new SentryMauiLoggerProvider(Options.Value, Hub, Clock); + } + } + + private readonly Fixture _fixture = new(); + + [Fact] + public void Type_CustomAttributes_HasProviderAliasAttribute() + { + var type = typeof(SentryMauiLoggerProvider); + + type.GetCustomAttributes().Should() + .ContainSingle().Which + .Alias.Should().Be("Sentry"); + } + + [Fact] + public void Ctor_DependencyInjection_CanCreate() + { + using var services = new ServiceCollection() + .AddLogging() + .AddSingleton() + .AddSingleton(_fixture.Options) + .AddSingleton(_fixture.Hub) + .BuildServiceProvider(); + + var logger = services.GetRequiredService>(); + + logger.Should().BeOfType>(); + } + + [Fact] + public void CreateLogger_OfType() + { + var provider = _fixture.GetSut(); + + var logger = provider.CreateLogger("CategoryName"); + + logger.Should().BeOfType() + .Which.CategoryName.Should().Be("CategoryName"); + } +} diff --git a/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs b/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs index 5399ff6749..27eb41fd21 100644 --- a/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs +++ b/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs @@ -40,6 +40,16 @@ public SentryMauiStructuredLoggerProvider GetSut() private readonly Fixture _fixture = new(); + [Fact] + public void Type_CustomAttributes_HasProviderAliasAttribute() + { + var type = typeof(SentryMauiStructuredLoggerProvider); + + type.GetCustomAttributes().Should() + .ContainSingle().Which + .Alias.Should().Be("Sentry"); + } + [Fact] public void Ctor_DependencyInjection_CanCreate() { diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index c464b2ec53..ae307a62d0 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Sentry.Internal.Http; using Sentry.Maui.Internal; @@ -333,4 +334,61 @@ public void UseSentry_DebugTrue_CustomDiagnosticsLogger() // Assert options.DiagnosticLogger.Should().BeOfType(); } + + [Fact] + public void UseSentry_Logging_AddLoggerProviders() + { + // Arrange + var builder = _fixture.Builder; + + // Act + builder.UseSentry((SentryMauiOptions options) => + { + options.EnableLogs = true; + options.InitializeSdk = false; + }); + + using var serviceProvider = builder.Services.BuildServiceProvider(); + var providers = serviceProvider.GetRequiredService>().ToArray(); + + // Assert + providers.Should().HaveCount(2); + providers[0].Should().BeOfType(); + providers[1].Should().BeOfType(); + } + + [Fact] + public void UseSentry_Logging_AddLoggerFilterRules() + { + // Arrange + var builder = _fixture.Builder; + + // Act + builder.UseSentry((SentryMauiOptions options) => + { + options.EnableLogs = true; + options.InitializeSdk = false; + }); + + using var serviceProvider = builder.Services.BuildServiceProvider(); + var loggerFilterOptions = serviceProvider.GetRequiredService>().Value; + + // Assert + loggerFilterOptions.Rules.Should().HaveCount(2); + var one = loggerFilterOptions.Rules[0]; + var two = loggerFilterOptions.Rules[1]; + + one.ProviderName.Should().Be(typeof(SentryMauiLoggerProvider).FullName); + one.CategoryName.Should().BeNull(); + one.LogLevel.Should().BeNull(); + one.Filter.Should().NotBeNull(); + one.Filter!.Invoke(null, null, LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("", "", LogLevel.None).Should().BeTrue(); + one.Filter.Invoke("type", "category", LogLevel.None).Should().BeTrue(); + + two.ProviderName.Should().Be(typeof(SentryMauiStructuredLoggerProvider).FullName); + two.CategoryName.Should().Be(typeof(ISentryClient).FullName); + two.LogLevel.Should().Be(LogLevel.None); + two.Filter.Should().BeNull(); + } } diff --git a/test/Sentry.Testing/BindableTests.cs b/test/Sentry.Testing/BindableTests.cs index f513d34e5d..68dd553a36 100644 --- a/test/Sentry.Testing/BindableTests.cs +++ b/test/Sentry.Testing/BindableTests.cs @@ -65,21 +65,11 @@ private static KeyValuePair GetDummyBindableValue(Property {$"key1", $"{propertyInfo.Name}value1"}, {$"key2", $"{propertyInfo.Name}value2"} }, - not null when propertyType.FullName == "Sentry.Extensions.Logging.SentryLoggingOptions+SentryLoggingExperimentalOptions" => CreateSentryLoggingExperimentalOptions(), _ => throw new NotSupportedException($"Unsupported property type on property {propertyInfo.Name}") }; return new KeyValuePair(propertyInfo, value); } - private static object CreateSentryLoggingExperimentalOptions() - { - var options = Activator.CreateInstance("Sentry.Extensions.Logging", "Sentry.Extensions.Logging.SentryLoggingOptions+SentryLoggingExperimentalOptions", false, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.CreateInstance, null, null, null, null); - var instance = options.Unwrap(); - var property = instance.GetType().GetProperty("MinimumLogLevel", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - property.SetValue(instance, int.MaxValue); - return instance; - } - private static IEnumerable> ToConfigValues(KeyValuePair item) { var (prop, value) = item; @@ -91,11 +81,6 @@ private static IEnumerable> ToConfigValues(KeyValue yield return new KeyValuePair($"{prop.Name}:{kvp.Key}", kvp.Value); } } - else if (propertyType.FullName == "Sentry.Extensions.Logging.SentryLoggingOptions+SentryLoggingExperimentalOptions") - { - var property = value.GetType().GetProperty("MinimumLogLevel"); - yield return new KeyValuePair($"{prop.Name}:MinimumLogLevel", Convert.ToString(property.GetValue(value), CultureInfo.InvariantCulture)); - } else { yield return new KeyValuePair(prop.Name, Convert.ToString(value, CultureInfo.InvariantCulture)); @@ -130,10 +115,6 @@ protected void AssertContainsExpectedPropertyValues(TOptions actual) { actualValue.Should().BeEquivalentTo(expectedValue); } - else if (prop.PropertyType.FullName == "Sentry.Extensions.Logging.SentryLoggingOptions+SentryLoggingExperimentalOptions") - { - actualValue.Should().BeEquivalentTo(expectedValue); - } else { actualValue.Should().Be(expectedValue);