diff --git a/src/Sentry.Serilog/SentrySerilogOptions.cs b/src/Sentry.Serilog/SentrySerilogOptions.cs index 144d88d806..b06432736a 100644 --- a/src/Sentry.Serilog/SentrySerilogOptions.cs +++ b/src/Sentry.Serilog/SentrySerilogOptions.cs @@ -40,4 +40,16 @@ public class SentrySerilogOptions : SentryOptions /// Optional /// public ITextFormatter? TextFormatter { get; set; } + + /// + /// The minimum level for events passed through the sink. Ignored when is specified. + /// + /// + public LogEventLevel RestrictedToMinimumLevel { get; set; } = LevelAlias.Minimum; + + /// + /// A switch allowing the pass-through minimum level to be changed at runtime. + /// + /// + public LoggingLevelSwitch? LevelSwitch { get; set; } } diff --git a/src/Sentry.Serilog/SentrySinkExtensions.cs b/src/Sentry.Serilog/SentrySinkExtensions.cs index 7a823c3d89..ea363b4c9e 100644 --- a/src/Sentry.Serilog/SentrySinkExtensions.cs +++ b/src/Sentry.Serilog/SentrySinkExtensions.cs @@ -36,6 +36,8 @@ public static class SentrySinkExtensions /// What modes to use for event automatic de-duplication. /// Default tags to add to all events. /// Whether to send structured logs. + /// The minimum level for events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level to be changed at runtime. /// /// This sample shows how each item may be set from within a configuration file: /// @@ -106,7 +108,9 @@ public static LoggerConfiguration Sentry( ReportAssembliesMode? reportAssembliesMode = null, DeduplicateMode? deduplicateMode = null, Dictionary? defaultTags = null, - bool? enableLogs = null) + bool? enableLogs = null, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch? levelSwitch = null) { return loggerConfiguration.Sentry(o => ConfigureSentrySerilogOptions(o, dsn, @@ -132,7 +136,9 @@ public static LoggerConfiguration Sentry( reportAssembliesMode, deduplicateMode, defaultTags, - enableLogs)); + enableLogs, + restrictedToMinimumLevel, + levelSwitch)); } /// @@ -147,6 +153,8 @@ public static LoggerConfiguration Sentry( /// Minimum log level to record a breadcrumb. /// The Serilog format provider. /// The Serilog text formatter. + /// The minimum level for events passed through the sink. Ignored when is specified. + /// A switch allowing the pass-through minimum level to be changed at runtime. /// /// This sample shows how each item may be set from within a configuration file: /// @@ -174,15 +182,18 @@ public static LoggerConfiguration Sentry( LogEventLevel? minimumEventLevel = null, LogEventLevel? minimumBreadcrumbLevel = null, IFormatProvider? formatProvider = null, - ITextFormatter? textFormatter = null - ) + ITextFormatter? textFormatter = null, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch? levelSwitch = null) { return loggerConfiguration.Sentry(o => ConfigureSentrySerilogOptions(o, null, minimumEventLevel, minimumBreadcrumbLevel, formatProvider, - textFormatter)); + textFormatter, + restrictedToMinimumLevel: restrictedToMinimumLevel, + levelSwitch: levelSwitch)); } internal static void ConfigureSentrySerilogOptions( @@ -210,7 +221,9 @@ internal static void ConfigureSentrySerilogOptions( ReportAssembliesMode? reportAssembliesMode = null, DeduplicateMode? deduplicateMode = null, Dictionary? defaultTags = null, - bool? enableLogs = null) + bool? enableLogs = null, + LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum, + LoggingLevelSwitch? levelSwitch = null) { if (dsn is not null) { @@ -327,6 +340,9 @@ internal static void ConfigureSentrySerilogOptions( sentrySerilogOptions.EnableLogs = enableLogs.Value; } + sentrySerilogOptions.RestrictedToMinimumLevel = restrictedToMinimumLevel; + sentrySerilogOptions.LevelSwitch = levelSwitch; + // Serilog-specific items sentrySerilogOptions.InitializeSdk = dsn is not null; // Inferred from the Sentry overload that is used if (defaultTags?.Count > 0) @@ -364,6 +380,6 @@ public static LoggerConfiguration Sentry( sdkDisposable = SentrySdk.Init(options); } - return loggerConfiguration.Sink(new SentrySink(options, sdkDisposable)); + return loggerConfiguration.Sink(new SentrySink(options, sdkDisposable), options.RestrictedToMinimumLevel, options.LevelSwitch); } } diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index bb2d777e40..6374c53e3e 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -11,8 +11,10 @@ namespace Sentry.Serilog public SentrySerilogOptions() { } public System.IFormatProvider? FormatProvider { get; set; } public bool InitializeSdk { get; set; } + public Serilog.Core.LoggingLevelSwitch? LevelSwitch { get; set; } public Serilog.Events.LogEventLevel MinimumBreadcrumbLevel { get; set; } public Serilog.Events.LogEventLevel MinimumEventLevel { get; set; } + public Serilog.Events.LogEventLevel RestrictedToMinimumLevel { get; set; } public Serilog.Formatting.ITextFormatter? TextFormatter { get; set; } } } @@ -21,7 +23,7 @@ namespace Serilog public static class SentrySinkExtensions { public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, System.Action configureOptions) { } - public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null) { } + public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } public static Serilog.LoggerConfiguration Sentry( this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, string dsn, @@ -47,6 +49,8 @@ namespace Serilog Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, System.Collections.Generic.Dictionary? defaultTags = null, - bool? enableLogs = default) { } + bool? enableLogs = default, + Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, + Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index bb2d777e40..6374c53e3e 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -11,8 +11,10 @@ namespace Sentry.Serilog public SentrySerilogOptions() { } public System.IFormatProvider? FormatProvider { get; set; } public bool InitializeSdk { get; set; } + public Serilog.Core.LoggingLevelSwitch? LevelSwitch { get; set; } public Serilog.Events.LogEventLevel MinimumBreadcrumbLevel { get; set; } public Serilog.Events.LogEventLevel MinimumEventLevel { get; set; } + public Serilog.Events.LogEventLevel RestrictedToMinimumLevel { get; set; } public Serilog.Formatting.ITextFormatter? TextFormatter { get; set; } } } @@ -21,7 +23,7 @@ namespace Serilog public static class SentrySinkExtensions { public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, System.Action configureOptions) { } - public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null) { } + public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } public static Serilog.LoggerConfiguration Sentry( this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, string dsn, @@ -47,6 +49,8 @@ namespace Serilog Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, System.Collections.Generic.Dictionary? defaultTags = null, - bool? enableLogs = default) { } + bool? enableLogs = default, + Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, + Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index bb2d777e40..6374c53e3e 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -11,8 +11,10 @@ namespace Sentry.Serilog public SentrySerilogOptions() { } public System.IFormatProvider? FormatProvider { get; set; } public bool InitializeSdk { get; set; } + public Serilog.Core.LoggingLevelSwitch? LevelSwitch { get; set; } public Serilog.Events.LogEventLevel MinimumBreadcrumbLevel { get; set; } public Serilog.Events.LogEventLevel MinimumEventLevel { get; set; } + public Serilog.Events.LogEventLevel RestrictedToMinimumLevel { get; set; } public Serilog.Formatting.ITextFormatter? TextFormatter { get; set; } } } @@ -21,7 +23,7 @@ namespace Serilog public static class SentrySinkExtensions { public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, System.Action configureOptions) { } - public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null) { } + public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } public static Serilog.LoggerConfiguration Sentry( this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, string dsn, @@ -47,6 +49,8 @@ namespace Serilog Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, System.Collections.Generic.Dictionary? defaultTags = null, - bool? enableLogs = default) { } + bool? enableLogs = default, + Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, + Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index bb2d777e40..6374c53e3e 100644 --- a/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Serilog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -11,8 +11,10 @@ namespace Sentry.Serilog public SentrySerilogOptions() { } public System.IFormatProvider? FormatProvider { get; set; } public bool InitializeSdk { get; set; } + public Serilog.Core.LoggingLevelSwitch? LevelSwitch { get; set; } public Serilog.Events.LogEventLevel MinimumBreadcrumbLevel { get; set; } public Serilog.Events.LogEventLevel MinimumEventLevel { get; set; } + public Serilog.Events.LogEventLevel RestrictedToMinimumLevel { get; set; } public Serilog.Formatting.ITextFormatter? TextFormatter { get; set; } } } @@ -21,7 +23,7 @@ namespace Serilog public static class SentrySinkExtensions { public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, System.Action configureOptions) { } - public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null) { } + public static Serilog.LoggerConfiguration Sentry(this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, Serilog.Events.LogEventLevel? minimumEventLevel = default, Serilog.Events.LogEventLevel? minimumBreadcrumbLevel = default, System.IFormatProvider? formatProvider = null, Serilog.Formatting.ITextFormatter? textFormatter = null, Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } public static Serilog.LoggerConfiguration Sentry( this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration, string dsn, @@ -47,6 +49,8 @@ namespace Serilog Sentry.ReportAssembliesMode? reportAssembliesMode = default, Sentry.DeduplicateMode? deduplicateMode = default, System.Collections.Generic.Dictionary? defaultTags = null, - bool? enableLogs = default) { } + bool? enableLogs = default, + Serilog.Events.LogEventLevel restrictedToMinimumLevel = 0, + Serilog.Core.LoggingLevelSwitch? levelSwitch = null) { } } } \ No newline at end of file diff --git a/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj b/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj index ea7b7b84c3..21bfce809c 100644 --- a/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj +++ b/test/Sentry.Serilog.Tests/Sentry.Serilog.Tests.csproj @@ -14,6 +14,7 @@ + diff --git a/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs b/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs index 36c3f1f802..d146bb87ed 100644 --- a/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs +++ b/test/Sentry.Serilog.Tests/SentrySerilogSinkExtensionsTests.cs @@ -29,6 +29,8 @@ private class Fixture public bool InitializeSdk { get; } = false; public LogEventLevel MinimumEventLevel { get; } = LogEventLevel.Verbose; public LogEventLevel MinimumBreadcrumbLevel { get; } = LogEventLevel.Fatal; + public LogEventLevel RestrictedToMinimumLevel { get; } = LogEventLevel.Warning; + public LoggingLevelSwitch LevelSwitch { get; } = new(LogEventLevel.Error); public static SentrySerilogOptions GetSut() => new(); } @@ -98,7 +100,8 @@ public void ConfigureSentrySerilogOptions_WithAllParameters_MakesAppropriateChan _fixture.SampleRate, _fixture.Release, _fixture.Environment, _fixture.MaxQueueItems, _fixture.ShutdownTimeout, _fixture.DecompressionMethods, _fixture.RequestBodyCompressionLevel, _fixture.RequestBodyCompressionBuffered, _fixture.Debug, _fixture.DiagnosticLevel, - _fixture.ReportAssembliesMode, _fixture.DeduplicateMode, null, _fixture.EnableLogs); + _fixture.ReportAssembliesMode, _fixture.DeduplicateMode, null, _fixture.EnableLogs, + _fixture.RestrictedToMinimumLevel, _fixture.LevelSwitch); // Compare individual properties Assert.Equal(_fixture.SendDefaultPii, sut.SendDefaultPii); @@ -123,6 +126,52 @@ public void ConfigureSentrySerilogOptions_WithAllParameters_MakesAppropriateChan Assert.True(sut.InitializeSdk); Assert.Equal(_fixture.MinimumEventLevel, sut.MinimumEventLevel); Assert.Equal(_fixture.MinimumBreadcrumbLevel, sut.MinimumBreadcrumbLevel); + Assert.Equal(_fixture.RestrictedToMinimumLevel, sut.RestrictedToMinimumLevel); + Assert.Same(_fixture.LevelSwitch, sut.LevelSwitch); + } + + [Fact] + public void Sentry_WithRestrictedToMinimumLevel_ConfigureOptions_FiltersLogsBelow() + { + // Arrange + var hub = Substitute.For(); + hub.IsEnabled.Returns(true); + var options = new SentrySerilogOptions + { + InitializeSdk = false, + MinimumBreadcrumbLevel = LogEventLevel.Verbose, + MinimumEventLevel = LogEventLevel.Verbose, + RestrictedToMinimumLevel = LogEventLevel.Error, + }; + var sink = new SentrySink(options, () => hub, null, new MockClock()); + using var logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Sink(sink, options.RestrictedToMinimumLevel, options.LevelSwitch) + .CreateLogger(); + + // Act + logger.Warning("Below threshold"); + logger.Error("At threshold"); + + // Assert: Warning is filtered by Serilog before reaching the sink; only Error gets through + hub.Received(1).CaptureEvent(Arg.Any()); + hub.DidNotReceive().CaptureEvent(Arg.Is(e => + e.Message.Message == "Below threshold")); + } + + [Fact] + public void Sentry_WithRestrictedToMinimumLevel_NoDsn_ParameterIsAccepted() + { + // Verify the no-DSN overload accepts restrictedToMinimumLevel without throwing + var ex = Record.Exception(() => + new LoggerConfiguration() + .WriteTo.Sentry( + minimumBreadcrumbLevel: LogEventLevel.Verbose, + minimumEventLevel: LogEventLevel.Error, + restrictedToMinimumLevel: LogEventLevel.Warning) + .CreateLogger()); + + Assert.Null(ex); } private static void AssertEqualDeep(object expected, object actual)