diff --git a/benchmarks/Sentry.Extensions.Logging.Benchmarks/SentryStructuredLoggerBenchmarks.cs b/benchmarks/Sentry.Extensions.Logging.Benchmarks/SentryStructuredLoggerBenchmarks.cs index d7608082df..2ea4c5b9e4 100644 --- a/benchmarks/Sentry.Extensions.Logging.Benchmarks/SentryStructuredLoggerBenchmarks.cs +++ b/benchmarks/Sentry.Extensions.Logging.Benchmarks/SentryStructuredLoggerBenchmarks.cs @@ -16,7 +16,7 @@ public class SentryStructuredLoggerBenchmarks [GlobalSetup] public void Setup() { - SentryLoggingOptions options = new() + SentryOptions options = new() { Dsn = DsnSamples.ValidDsn, }; @@ -34,7 +34,7 @@ public void Setup() }; _hub = new Hub(options, DisabledHub.Instance); - _logger = new SentryStructuredLogger("CategoryName", options, _hub, clock, sdk); + _logger = new SentryStructuredLogger("CategoryName", _hub, clock, sdk); _logRecord = new LogRecord(LogLevel.Information, new EventId(2025, "EventName"), new InvalidOperationException("exception-message"), "Number={Number}, Text={Text}", 2018, "message"); } diff --git a/samples/Sentry.Samples.GenericHost/Program.cs b/samples/Sentry.Samples.GenericHost/Program.cs index 5bad8f3641..407beb205d 100644 --- a/samples/Sentry.Samples.GenericHost/Program.cs +++ b/samples/Sentry.Samples.GenericHost/Program.cs @@ -5,16 +5,19 @@ var builder = Host.CreateApplicationBuilder(); -builder.Logging.AddConfiguration(builder.Configuration); - +// Initialise the Sentry SDK. The logging integration added below only forwards log messages to Sentry. +using var sentry = SentrySdk.Init(options => +{ #if !SENTRY_DSN_DEFINED_IN_ENV -// A DSN is required. You can set it here in code, via the SENTRY_DSN environment variable or in your -// appsettings.json file. -// See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/#configure -builder.Logging.AddSentry(SamplesShared.Dsn); -#else -builder.Logging.AddSentry(); + // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. + // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ + options.Dsn = SamplesShared.Dsn; #endif + // Send user name and machine name + options.SendDefaultPii = true; +}); + +builder.Logging.AddSentry(); builder.Services.AddHostedService(); diff --git a/samples/Sentry.Samples.GenericHost/appsettings.json b/samples/Sentry.Samples.GenericHost/appsettings.json index 9d20069922..302435a96c 100644 --- a/samples/Sentry.Samples.GenericHost/appsettings.json +++ b/samples/Sentry.Samples.GenericHost/appsettings.json @@ -3,16 +3,13 @@ "LogLevel": { "Default": "Trace" }, + // The logging integration is configured here, under its own provider section "Sentry": { "LogLevel": { "Default": "Information" // Configure structured logs - } + }, + "MinimumBreadcrumbLevel": "Debug", + "MinimumEventLevel": "Warning" } - }, - "Sentry": { - //"Dsn": "TODO: Configure your DSN here and uncomment this line", - "MinimumBreadcrumbLevel": "Debug", - "MinimumEventLevel": "Warning", - "SendDefaultPii": true // Send user name and machine name } } diff --git a/samples/Sentry.Samples.Log4Net/Program.cs b/samples/Sentry.Samples.Log4Net/Program.cs index f35396bf34..ce7a4ec0ef 100644 --- a/samples/Sentry.Samples.Log4Net/Program.cs +++ b/samples/Sentry.Samples.Log4Net/Program.cs @@ -10,6 +10,17 @@ internal class Program private static void Main() { + // Initialise the Sentry SDK. + using var _ = SentrySdk.Init(options => + { +#if !SENTRY_DSN_DEFINED_IN_ENV + // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. + // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ + options.Dsn = SamplesShared.Dsn; +#endif + options.Environment = "dev"; + }); + // Set the user running the process the current principal // Appender was configured to send the user with the event AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); diff --git a/samples/Sentry.Samples.Log4Net/README.md b/samples/Sentry.Samples.Log4Net/README.md index 58e51407d1..4905969628 100644 --- a/samples/Sentry.Samples.Log4Net/README.md +++ b/samples/Sentry.Samples.Log4Net/README.md @@ -2,8 +2,8 @@ This is a simple console application that demonstrates how you can add Sentry to your applications using log4net. -`Program.cs` doesn't have any reference to Sentry. In fact, it doesn't even include a `using Sentry;` directive. -The integration is simply done by adding the [Sentry.Log4Net](https://www.nuget.org/packages/Sentry.Log4Net) package to you project and configuring the appender via xml. +The Sentry SDK is initialised via the call to `SentrySdk.Init` in `Program.cs`. Log events are sent to Sentry by adding the +[Sentry.Log4Net](https://www.nuget.org/packages/Sentry.Log4Net) package to your project and configuring the appender via xml. It's also possible to use the SDK directly on your app as the documentation describes. If you for example call `SentrySdk.AddBreadcrumb("crumb")`, all log4net events will include that breadcrumb. @@ -12,7 +12,7 @@ If you for example call `SentrySdk.AddBreadcrumb("crumb")`, all log4net events w ## Running this sample -> First modify the `app.config` and add **your own DSN**. You can get one at [sentry.io](sentry.io). This will make sure you can see the result of running this app by looking at what this creates in Sentry. +> First modify `Program.cs` and add **your own DSN**. You can get one at [sentry.io](sentry.io). This will make sure you can see the result of running this app by looking at what this creates in Sentry. Now you're ready to run the code. diff --git a/samples/Sentry.Samples.Log4Net/app.config b/samples/Sentry.Samples.Log4Net/app.config index 613a0bed0e..4a6b80f74c 100644 --- a/samples/Sentry.Samples.Log4Net/app.config +++ b/samples/Sentry.Samples.Log4Net/app.config @@ -10,14 +10,9 @@ - - - - - + - diff --git a/samples/Sentry.Samples.ME.Logging/Program.cs b/samples/Sentry.Samples.ME.Logging/Program.cs index fb2e8aa63d..a80e29cd67 100644 --- a/samples/Sentry.Samples.ME.Logging/Program.cs +++ b/samples/Sentry.Samples.ME.Logging/Program.cs @@ -1,39 +1,43 @@ using Microsoft.Extensions.Logging; using Sentry.Extensions.Logging; -using var loggerFactory = LoggerFactory.Create(builder => +// Initialise the Sentry SDK. The logging integration added below only forwards log messages to Sentry. +using var sentry = SentrySdk.Init(options => { - builder.AddConsole(); - builder.AddSentry(options => - { #if !SENTRY_DSN_DEFINED_IN_ENV - // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. - // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ - options.Dsn = SamplesShared.Dsn; + // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. + // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ + options.Dsn = SamplesShared.Dsn; #endif - // Set to true to SDK debugging to see the internal messages through the logging library. - options.Debug = false; - // Configure the level of Sentry internal logging - options.DiagnosticLevel = SentryLevel.Debug; + // Set to true to SDK debugging to see the internal messages through the logging library. + options.Debug = false; + // Configure the level of Sentry internal logging + options.DiagnosticLevel = SentryLevel.Debug; + + options.MaxBreadcrumbs = 150; // Increasing from default 100 + options.Release = "e386dfd"; // If not set here, SDK looks for it on main assembly's AssemblyInformationalVersion and AssemblyVersion + + options.SetBeforeSendLog(static log => + { + log.SetAttribute("attribute-key", "attribute-value"); + return log; + }); +}); - options.MaxBreadcrumbs = 150; // Increasing from default 100 - options.Release = "e386dfd"; // If not set here, SDK looks for it on main assembly's AssemblyInformationalVersion and AssemblyVersion +SentrySdk.ConfigureScope(s => s.SetTag("RootScope", "sent with all events")); +using var loggerFactory = LoggerFactory.Create(builder => +{ + builder.AddConsole(); + builder.AddSentry(options => + { // 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.SetBeforeSendLog(static log => - { - log.SetAttribute("attribute-key", "attribute-value"); - return log; - }); - // Don't keep as a breadcrumb or send events for messages of level less than Critical with exception of type DivideByZeroException options.AddLogEntryFilter((_, level, _, exception) => level < LogLevel.Critical && exception is DivideByZeroException); - - options.ConfigureScope(s => s.SetTag("RootScope", "sent with all events")); }); // Don't send logs for messages of level less than Warning for category Program builder.AddFilter(typeof(Program).FullName, LogLevel.Warning); @@ -86,8 +90,6 @@ Dependency.Work("8 - This unhandled exception is captured and includes Scope (A, B) and crumbs: (2, 4, 5) and event (3) "); } -// Disposing the LoggerFactory will close the SDK since it was initialized through -// the integration while calling .Init() internal static class Dependency { diff --git a/samples/Sentry.Samples.NLog/NLog.config b/samples/Sentry.Samples.NLog/NLog.config index 5022aa23bb..9e512f40e1 100644 --- a/samples/Sentry.Samples.NLog/NLog.config +++ b/samples/Sentry.Samples.NLog/NLog.config @@ -10,11 +10,8 @@ - - + - - - diff --git a/samples/Sentry.Samples.NLog/Program.cs b/samples/Sentry.Samples.NLog/Program.cs index 93d1a56397..c2561f69ab 100644 --- a/samples/Sentry.Samples.NLog/Program.cs +++ b/samples/Sentry.Samples.NLog/Program.cs @@ -12,6 +12,18 @@ public static class Program private static void Main() { + // Initialise the Sentry SDK + using var _ = SentrySdk.Init(options => + { +#if !SENTRY_DSN_DEFINED_IN_ENV + // A DSN is required. You can set here in code, or you can set it in the SENTRY_DSN environment variable. + // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ + options.Dsn = SamplesShared.Dsn; +#endif + options.AttachStacktrace = true; + options.SendDefaultPii = true; // Send Personal Identifiable information like the username of the user logged in to the device + }); + try { // You can configure your logger using a configuration file: @@ -97,28 +109,18 @@ private static void UsingNLogConfigFile() private static void UsingCodeConfiguration() { - // Other overloads exist, for example, configure the SDK with only the DSN or no parameters at all. + // Configure NLog to send logs to Sentry var config = LogManager.Configuration = new LoggingConfiguration(); _ = config .AddSentry(options => { -#if !SENTRY_DSN_DEFINED_IN_ENV - // A DSN is required. You can set here in code, in the SENTRY_DSN environment variable or in the - // NLog.config file. - // See https://docs.sentry.io/product/sentry-basics/dsn-explainer/ - options.Dsn = SamplesShared.Dsn; -#endif options.Layout = "${message}"; options.BreadcrumbLayout = "${logger}: ${message}"; // Optionally specify a separate format for breadcrumbs options.MinimumBreadcrumbLevel = LogLevel.Debug; // Debug and higher are stored as breadcrumbs (default is Info) - options.MinimumEventLevel = LogLevel.Error; // Error and higher is sent as event (default is Error) - - options.AttachStacktrace = true; - options.SendDefaultPii = true; // Send Personal Identifiable information like the username of the user logged in to the device + options.MinimumEventLevel = LogLevel.Error; // Error and higher are sent as events (default is Error) options.IncludeEventDataOnBreadcrumbs = true; // Optionally include event properties with breadcrumbs - options.ShutdownTimeoutSeconds = 5; //Optionally specify user properties via NLog (here using MappedDiagnosticsLogicalContext as an example) options.User = new SentryNLogUser @@ -134,8 +136,6 @@ private static void UsingCodeConfiguration() }; options.AddTag("logger", "${logger}"); // Send the logger name as a tag - - // Other configuration }); config.AddTarget(new DebuggerTarget("debugger")); diff --git a/samples/Sentry.Samples.NLog/README.md b/samples/Sentry.Samples.NLog/README.md index 6cf4e027d2..78c1932a77 100644 --- a/samples/Sentry.Samples.NLog/README.md +++ b/samples/Sentry.Samples.NLog/README.md @@ -2,20 +2,17 @@ This is a simple console application that demonstrates how you can add Sentry to your application using NLog. -This project attempts to sample the integration by using code only and also via the configuration file. -In both cases **you need to add your own DSN** so you can see the events sent in your Sentry project. +This project demonstrates configuring Sentry and NLog via code and configuration. +The Sentry target configures NLog to send logs to Sentry. The Sentry SDK itself is initialised via the call to `SentrySdk.Init` +in `Program.cs`, so **you need to add your own DSN** there to see the events sent in your Sentry project. You can get your [Sentry DSN at sentry.io](https://sentry.io). -Make sure to add it to both `NLog.config` and `Program.cs` in this directory. ## Configuration of NLog.config The following options are available for the NLog Sentry Target: ```xml - - ``` -* **dsn** - Sentry Data Source Name Address. See also https://sentry.io -* **initializeSdk** - Whether the NLog target should initialize the Sentry SDK (Using Dsn). Default: _True_ -* **environment** - Application Environment sent to Sentry -* **release** - Application Release Version sent to Sentry * **layout** - NLog Layout for rendering SentryEvent message. Default: _${message}_ * **includeEventProperties** - Include LogEvent properties as Data on SentryEvent. Default: _True_ * **includeEventPropertiesAsTags** - Include LogEvent properties as extra Tags on SentryEvent. Default: _False_ @@ -61,17 +46,12 @@ The following options are available for the NLog Sentry Target: * **minimumEventLevel** - Send NLog LogEvents as SentryEvent when matching severity (or worse). Default: _Error_ * **minimumBreadcrumbLevel** - Send NLog LogEvents as Breadcrumbs when matching severity (or worse). Default: _Info_ * **ignoreEventsWithNoException** - Ignore NLog LogEvents without an exception. Default: _False_ -* **flushTimeoutSeconds** - Flush timeout in seconds before aborting flush to Sentry. Default: _15_ * **user** * **id** * **username** * **email** * **ipAddress** * **other** - Any arbitrary key-value pairs to be included as properties for a user on every event. -* **options** - * **sendDefaultPii** - Whether to include default Personal Identifiable information (UserName / IP-Address). Default: _False_ - * **isEnvironmentUser** - Lookup Environment.User if having enabled **sendDefaultPii**. Default: _True_ - * **attachStacktrace** - Whether to send the stack trace of a event captured without an exception. Default: _False_ There is filtering logic in the Sentry Target that is usually handled by NLog Logging Rules and Filters. Mostly because the same Sentry Target is writing both breadcrumbs and actual SentryEvents. diff --git a/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs b/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs index 93c91d900f..9342669372 100644 --- a/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs +++ b/samples/Sentry.Samples.OpenTelemetry.AzureFunctions/Program.cs @@ -14,6 +14,16 @@ var dsn = SamplesShared.Dsn; #endif +// Initialise the Sentry SDK. The logging integration added below only forwards log messages to Sentry. +using var sentry = SentrySdk.Init(options => +{ + options.Dsn = dsn; + options.TracesSampleRate = 1.0; + options.UseOtlp(); // <-- Configure Sentry to use open telemetry + options.DisableSentryHttpMessageHandler = true; // So Sentry doesn't also create spans for outbound HTTP requests + options.Debug = true; +}); + var host = new HostBuilder() .ConfigureFunctionsWorkerDefaults() .ConfigureServices(services => @@ -25,17 +35,7 @@ .AddHttpClientInstrumentation(); // From OpenTelemetry.Instrumentation.Http... adds automatic tracing for outgoing HTTP requests }); }) - .ConfigureLogging(logging => - { - logging.AddSentry(options => - { - options.Dsn = dsn; - options.TracesSampleRate = 1.0; - options.UseOtlp(); // <-- Configure Sentry to use open telemetry - options.DisableSentryHttpMessageHandler = true; // So Sentry doesn't also create spans for outbound HTTP requests - options.Debug = true; - }); - }) + .ConfigureLogging(logging => logging.AddSentry()) .Build(); await host.RunAsync(); diff --git a/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs b/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs index 25c3070e68..8b21c1e259 100644 --- a/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore.Blazor.WebAssembly/WebAssemblyHostBuilderExtensions.cs @@ -1,9 +1,13 @@ +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; using Sentry; using Sentry.AspNetCore.Blazor.WebAssembly.Internal; using Sentry.Extensions.Logging; +using Sentry.Extensions.Logging.Extensions.DependencyInjection; +using Sentry.Infrastructure; using Sentry.Internal; // ReSharper disable once CheckNamespace - Discoverability @@ -22,7 +26,23 @@ public static class WebAssemblyHostBuilderExtensions /// public static WebAssemblyHostBuilder UseSentry(this WebAssemblyHostBuilder builder, Action configureOptions) { - builder.Logging.AddSentry(blazorOptions => + builder.Logging.AddSentryBlazor(builder.Configuration, configureOptions); + return builder; + } + + internal static ILoggingBuilder AddSentryBlazor( + this ILoggingBuilder logging, + IConfiguration configuration, + Action configureOptions) + { + logging.AddConfiguration(); + + logging.Services.AddSingleton>( + new SentryHostOptionsSetup(configuration.GetSection("Sentry"))); + logging.Services.AddSingleton>( + new SentryHostLoggingOptionsSetup(configuration)); + + logging.Services.Configure(blazorOptions => { configureOptions(blazorOptions); @@ -35,16 +55,26 @@ public static WebAssemblyHostBuilder UseSentry(this WebAssemblyHostBuilder build blazorOptions.AddTransactionProcessor(new TraceIgnoreStatusCodeTransactionProcessor(blazorOptions)); }); - builder.Services.AddSingleton, BlazorWasmOptionsSetup>(); + logging.Services.AddSingleton, BlazorWasmOptionsSetup>(); - return builder; + logging.Services.AddSingleton(c => new SentryLoggerProvider( + c.GetRequiredService(), + SystemClock.Clock, + c.GetRequiredService>().Value.Logging)); + logging.Services.AddSingleton(c => new SentryStructuredLoggerProvider(c.GetRequiredService())); + logging.Services.AddSentry(); + + logging.AddFilter(_ => true); + logging.AddFilter("Sentry.ISentryClient", LogLevel.None); + + return logging; } } /// /// Sentry Blazor Options /// -public class SentryBlazorOptions : SentryLoggingOptions +public class SentryBlazorOptions : SentryHostOptions { // Awesome Blazor specific options go here } diff --git a/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs b/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs index e98a485d9f..c0e48fdc5a 100644 --- a/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs +++ b/src/Sentry.AspNetCore/BindableSentryAspNetCoreOptions.cs @@ -8,7 +8,7 @@ namespace Sentry.AspNetCore; /// -internal class BindableSentryAspNetCoreOptions : BindableSentryLoggingOptions +internal class BindableSentryAspNetCoreOptions : BindableSentryHostOptions { public bool? IncludeActivityData { get; set; } public RequestSize? MaxRequestBodySize { get; set; } diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs b/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs index 5ad52c8e9b..dd5955c425 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreLoggerProvider.cs @@ -15,12 +15,12 @@ internal sealed class SentryAspNetCoreLoggerProvider : SentryLoggerProvider /// Creates a new instance of /// public SentryAspNetCoreLoggerProvider(IOptions options, IHub hub) - : base(options, hub) + : base(hub, SystemClock.Clock, options.Value.Logging) { } internal SentryAspNetCoreLoggerProvider(SentryAspNetCoreOptions options, IHub hub, ISystemClock clock) - : base(hub, clock, options) + : base(hub, clock, options.Logging) { } } diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs index f25ebc11ba..e2bd238f9f 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptions.cs @@ -11,7 +11,7 @@ namespace Sentry.AspNetCore; /// An options class for the ASP.NET Core Sentry integration /// /// -public class SentryAspNetCoreOptions : SentryLoggingOptions +public class SentryAspNetCoreOptions : SentryHostOptions { /// /// Gets or sets a value indicating whether [include System.Diagnostic.Activity data] to events. diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs index 60f364e5e4..eb8eb9a18d 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreOptionsSetup.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; using Sentry.Internal; @@ -15,9 +14,8 @@ internal sealed class SentryAspNetCoreOptionsSetup : ConfigureFromConfigurationO /// /// Creates a new instance of . /// - public SentryAspNetCoreOptionsSetup( - ILoggerProviderConfiguration providerConfiguration) - : base(providerConfiguration.Configuration) + public SentryAspNetCoreOptionsSetup(IConfiguration config) + : base(config) { } @@ -41,15 +39,7 @@ internal sealed class SentryAspNetCoreOptionsSetup : IConfigureOptions /// Creates a new instance of . /// - public SentryAspNetCoreOptionsSetup(ILoggerProviderConfiguration providerConfiguration) - : this(providerConfiguration.Configuration) - { - } - - /// - /// Creates a new instance of . - /// - internal SentryAspNetCoreOptionsSetup(IConfiguration config) + public SentryAspNetCoreOptionsSetup(IConfiguration config) { ArgumentNullException.ThrowIfNull(config); _config = config; diff --git a/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs b/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs index 42f7cd8cc3..efd242270f 100644 --- a/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs +++ b/src/Sentry.AspNetCore/SentryAspNetCoreStructuredLoggerProvider.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; using Sentry.Infrastructure; @@ -11,13 +10,13 @@ namespace Sentry.AspNetCore; [ProviderAlias("Sentry")] internal sealed class SentryAspNetCoreStructuredLoggerProvider : SentryStructuredLoggerProvider { - public SentryAspNetCoreStructuredLoggerProvider(IOptions options, IHub hub) - : this(options.Value, hub, SystemClock.Clock, CreateSdkVersion()) + public SentryAspNetCoreStructuredLoggerProvider(IHub hub) + : this(hub, SystemClock.Clock, CreateSdkVersion()) { } - internal SentryAspNetCoreStructuredLoggerProvider(SentryAspNetCoreOptions options, IHub hub, ISystemClock clock, SdkVersion sdk) - : base(options, hub, clock, sdk) + internal SentryAspNetCoreStructuredLoggerProvider(IHub hub, ISystemClock clock, SdkVersion sdk) + : base(hub, clock, sdk) { } diff --git a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs index 63545d07f9..42ae9780da 100644 --- a/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs +++ b/src/Sentry.AspNetCore/SentryWebHostBuilderExtensions.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Sentry.AspNetCore; using Sentry.Ben.BlockingDetector; +using Sentry.Extensions.Logging; // ReSharper disable once CheckNamespace namespace Microsoft.AspNetCore.Hosting; @@ -84,15 +85,12 @@ public static IWebHostBuilder UseSentry( logging.AddConfiguration(); var section = context.Configuration.GetSection("Sentry"); -#if NETSTANDARD2_0 - _ = logging.Services.Configure(section); -#else _ = logging.Services.AddSingleton>(_ => new SentryAspNetCoreOptionsSetup(section) ); -#endif - _ = logging.Services - .AddSingleton, SentryAspNetCoreOptionsSetup>(); + _ = logging.Services.AddSingleton>(_ => + new SentryHostLoggingOptionsSetup(context.Configuration) + ); _ = logging.Services.AddSingleton(); _ = logging.Services.AddSingleton(); diff --git a/src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs b/src/Sentry.Extensions.Logging/BindableSentryHostOptions.cs similarity index 66% rename from src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs rename to src/Sentry.Extensions.Logging/BindableSentryHostOptions.cs index 299e61a21c..c0c3482476 100644 --- a/src/Sentry.Extensions.Logging/BindableSentryLoggingOptions.cs +++ b/src/Sentry.Extensions.Logging/BindableSentryHostOptions.cs @@ -3,17 +3,15 @@ namespace Sentry.Extensions.Logging; /// -internal class BindableSentryLoggingOptions : BindableSentryOptions +internal class BindableSentryHostOptions : BindableSentryOptions { public LogLevel? MinimumBreadcrumbLevel { get; set; } public LogLevel? MinimumEventLevel { get; set; } - public bool? InitializeSdk { get; set; } - public void ApplyTo(SentryLoggingOptions options) + public void ApplyTo(SentryHostOptions options) { base.ApplyTo(options); options.MinimumBreadcrumbLevel = MinimumBreadcrumbLevel ?? options.MinimumBreadcrumbLevel; options.MinimumEventLevel = MinimumEventLevel ?? options.MinimumEventLevel; - options.InitializeSdk = InitializeSdk ?? options.InitializeSdk; } } diff --git a/src/Sentry.Extensions.Logging/Constants.cs b/src/Sentry.Extensions.Logging/Constants.cs deleted file mode 100644 index 9c409f46b0..0000000000 --- a/src/Sentry.Extensions.Logging/Constants.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Sentry.Extensions.Logging; - -internal static class Constants -{ - // See: https://github.com/getsentry/sentry-release-registry - public const string SdkName = "sentry.dotnet.extensions.logging"; -} diff --git a/src/Sentry.Extensions.Logging/Extensions/DependencyInjection/ServiceCollectionExtensions.cs b/src/Sentry.Extensions.Logging/Extensions/DependencyInjection/ServiceCollectionExtensions.cs index 2c19f126a0..9de982c687 100644 --- a/src/Sentry.Extensions.Logging/Extensions/DependencyInjection/ServiceCollectionExtensions.cs +++ b/src/Sentry.Extensions.Logging/Extensions/DependencyInjection/ServiceCollectionExtensions.cs @@ -13,30 +13,44 @@ namespace Sentry.Extensions.Logging.Extensions.DependencyInjection; public static class ServiceCollectionExtensions { /// - /// Adds Sentry's services to the + /// Adds Sentry's services to the , initializing Sentry with + /// when the hub is first resolved. /// /// The services. public static IServiceCollection AddSentry(this IServiceCollection services) - where TOptions : SentryLoggingOptions, new() + where TOptions : SentryHostOptions, new() + => services.AddSentry(initializeSdk: true); + + internal static IServiceCollection AddSentry(this IServiceCollection services, bool initializeSdk) + where TOptions : SentryHostOptions, new() { services.TryAddSingleton( c => c.GetRequiredService>().Value); - services.TryAddTransient(c => c.GetRequiredService()); - services.TryAddTransient(c => c.GetRequiredService>()()); - - services.TryAddSingleton>(c => + if (initializeSdk) { - var options = c.GetRequiredService>().Value; - - if (options.InitializeSdk) + // Replaces rather than TryAdd: the logging integration registers a non-initializing accessor, and + // whichever is registered first would otherwise win. Initializing has to take precedence. + services.RemoveAll>(); + services.AddSingleton>(c => { + var options = c.GetRequiredService>().Value; var hub = SentrySdk.InitHub(options); SentrySdk.UseHub(hub); - } + options.ApplyConfigureScopeCallbacks(hub); + + return () => HubAdapter.Instance; + }); + } - return () => HubAdapter.Instance; - }); + return services.AddSentryHub(); + } + + internal static IServiceCollection AddSentryHub(this IServiceCollection services) + { + services.TryAddTransient(c => c.GetRequiredService()); + services.TryAddTransient(c => c.GetRequiredService>()()); + services.TryAddSingleton>(_ => () => HubAdapter.Instance); // Custom handler for HttpClientFactory. // Must be singleton: https://github.com/getsentry/sentry-dotnet/issues/785 diff --git a/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs b/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs index 7731288078..bbdb7dd90e 100644 --- a/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs +++ b/src/Sentry.Extensions.Logging/LoggingBuilderExtensions.cs @@ -22,12 +22,15 @@ public static ILoggingBuilder AddSentry(this ILoggingBuilder builder) => builder.AddSentry((Action?)null); /// - /// Adds the Sentry logging integration. + /// Not supported. The logging integration no longer initializes the SDK. /// /// The builder. - /// The DSN. + /// No longer supported. + /// Never returns. + /// Always. + [Obsolete(SentryLoggingOptions.ObsoleteSdkInitialization, error: true)] public static ILoggingBuilder AddSentry(this ILoggingBuilder builder, string dsn) - => builder.AddSentry(o => o.Dsn = dsn); + => throw new NotSupportedException(SentryLoggingOptions.ObsoleteSdkInitialization); /// /// Adds the Sentry logging integration. @@ -35,12 +38,6 @@ public static ILoggingBuilder AddSentry(this ILoggingBuilder builder, string dsn /// The builder. /// The options configuration. public static ILoggingBuilder AddSentry(this ILoggingBuilder builder, Action? optionsConfiguration) - => builder.AddSentry(optionsConfiguration); - - internal static ILoggingBuilder AddSentry( - this ILoggingBuilder builder, - Action? optionsConfiguration) - where TOptions : SentryLoggingOptions, new() { builder.AddConfiguration(); @@ -49,14 +46,14 @@ internal static ILoggingBuilder AddSentry( builder.Services.Configure(optionsConfiguration); } - builder.Services.AddSingleton, SentryLoggingOptionsSetup>(); + builder.Services.AddSingleton, SentryLoggingOptionsSetup>(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); - builder.Services.AddSentry(); + builder.Services.AddSentryHub(); // All logs should flow to the SentryLogger, regardless of level. - // Filtering of events is handled in SentryLogger, using SentryOptions.MinimumEventLevel - // Filtering of breadcrumbs is handled in SentryLogger, using SentryOptions.MinimumBreadcrumbLevel + // Filtering of events is handled in SentryLogger, using SentryLoggingOptions.MinimumEventLevel + // Filtering of breadcrumbs is handled in SentryLogger, using SentryLoggingOptions.MinimumBreadcrumbLevel builder.AddFilter(_ => true); // Logs from the SentryLogger should not flow to the SentryStructuredLogger as this may cause recursive invocations. diff --git a/src/Sentry.Extensions.Logging/SentryHostLoggingOptionsSetup.cs b/src/Sentry.Extensions.Logging/SentryHostLoggingOptionsSetup.cs new file mode 100644 index 0000000000..3e75b75da6 --- /dev/null +++ b/src/Sentry.Extensions.Logging/SentryHostLoggingOptionsSetup.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace Sentry.Extensions.Logging; + +internal sealed class SentryHostLoggingOptionsSetup : IConfigureOptions + where TOptions : SentryHostOptions +{ + private readonly IConfiguration _loggingSection; + + public SentryHostLoggingOptionsSetup(IConfiguration configuration) + { + ArgumentNullException.ThrowIfNull(configuration); + _loggingSection = configuration.GetSection("Logging:Sentry"); + } + + public void Configure(TOptions options) + { + ArgumentNullException.ThrowIfNull(options); + SentryLoggingConfiguration.ApplyTo(_loggingSection, options.Logging); + } +} diff --git a/src/Sentry.Extensions.Logging/SentryHostOptions.cs b/src/Sentry.Extensions.Logging/SentryHostOptions.cs new file mode 100644 index 0000000000..b6b86266e4 --- /dev/null +++ b/src/Sentry.Extensions.Logging/SentryHostOptions.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.Logging; + +namespace Sentry.Extensions.Logging; + +/// +/// Options for integrations that initialize Sentry and also send log entries to it, such as ASP.NET Core and MAUI. +/// +/// +public abstract class SentryHostOptions : SentryOptions +{ + internal SentryLoggingOptions Logging { get; } = new(); + + /// + public LogLevel MinimumBreadcrumbLevel + { + get => Logging.MinimumBreadcrumbLevel; + set => Logging.MinimumBreadcrumbLevel = value; + } + + /// + public LogLevel MinimumEventLevel + { + get => Logging.MinimumEventLevel; + set => Logging.MinimumEventLevel = value; + } + + /// + /// Add a callback to configure the scope upon SDK initialization + /// + /// The function to invoke when initializing the SDK + public void ConfigureScope(Action action) => ConfigureScopeCallbacks = ConfigureScopeCallbacks.Concat(new[] { action }).ToArray(); + + /// + /// List of callbacks to be invoked when initializing the SDK + /// + internal Action[] ConfigureScopeCallbacks { get; set; } = Array.Empty>(); + + internal void ApplyConfigureScopeCallbacks(IHub hub) + { + foreach (var callback in ConfigureScopeCallbacks) + { + hub.ConfigureScope(callback); + } + } +} diff --git a/src/Sentry.Extensions.Logging/SentryHostOptionsSetup.cs b/src/Sentry.Extensions.Logging/SentryHostOptionsSetup.cs new file mode 100644 index 0000000000..93bd0c616d --- /dev/null +++ b/src/Sentry.Extensions.Logging/SentryHostOptionsSetup.cs @@ -0,0 +1,27 @@ +#if NET6_0_OR_GREATER +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; + +namespace Sentry.Extensions.Logging; + +internal sealed class SentryHostOptionsSetup : IConfigureOptions + where TOptions : SentryHostOptions +{ + private readonly IConfiguration _config; + + public SentryHostOptionsSetup(IConfiguration section) + { + ArgumentNullException.ThrowIfNull(section); + _config = section; + } + + public void Configure(TOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + var bindable = new BindableSentryHostOptions(); + _config.Bind(bindable); + bindable.ApplyTo(options); + } +} +#endif diff --git a/src/Sentry.Extensions.Logging/SentryLogger.cs b/src/Sentry.Extensions.Logging/SentryLogger.cs index 9a25cfc0b5..513c3c2e2b 100644 --- a/src/Sentry.Extensions.Logging/SentryLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryLogger.cs @@ -10,19 +10,28 @@ internal sealed class SentryLogger : ILogger private readonly IHub _hub; private readonly ISystemClock _clock; private readonly SentryLoggingOptions _options; + private readonly UninitializedSdkWarning _uninitializedSdkWarning; internal string CategoryName { get; } + internal const string UninitializedSdkMessage = + "Sentry: the Microsoft.Extensions.Logging integration dropped a log event because Sentry is not " + + "initialized, but a DSN was found in the environment or in an assembly attribute. The logging " + + "integration no longer initializes the SDK: call SentrySdk.Init (or UseSentry via one of the " + + "integrations) at startup. See https://docs.sentry.io/platforms/dotnet/guides/extensions-logging/"; + internal SentryLogger( string categoryName, SentryLoggingOptions options, ISystemClock clock, - IHub hub) + IHub hub, + UninitializedSdkWarning? uninitializedSdkWarning = null) { CategoryName = categoryName; _options = options; _clock = clock; _hub = hub; + _uninitializedSdkWarning = uninitializedSdkWarning ?? new UninitializedSdkWarning(); } #if NET8_0_OR_GREATER @@ -32,9 +41,10 @@ public IDisposable BeginScope(TState state) where TState : notnull public IDisposable BeginScope(TState state) => _hub.PushScope(state); #endif - public bool IsEnabled(LogLevel logLevel) - => _hub.IsEnabled - && logLevel != LogLevel.None + public bool IsEnabled(LogLevel logLevel) => _hub.IsEnabled && IsEnabledForLevel(logLevel); + + private bool IsEnabledForLevel(LogLevel logLevel) + => logLevel != LogLevel.None && (logLevel >= _options.MinimumBreadcrumbLevel || logLevel >= _options.MinimumEventLevel); @@ -45,7 +55,17 @@ public void Log( Exception? exception, Func? formatter) { - if (!IsEnabled(logLevel)) + if (!_hub.IsEnabled) + { + if (WouldCaptureEvent(logLevel, eventId, exception)) + { + _uninitializedSdkWarning.WarnOnce(UninitializedSdkMessage); + } + + return; + } + + if (!IsEnabledForLevel(logLevel)) { return; } @@ -163,6 +183,13 @@ private bool ShouldCaptureEvent(LogLevel logLevel) => _options.MinimumEventLevel != LogLevel.None && logLevel >= _options.MinimumEventLevel; + private bool WouldCaptureEvent(LogLevel logLevel, EventId eventId, Exception? exception) + => logLevel != LogLevel.None + && ShouldCaptureEvent(logLevel) + && !IsFromSentry() + && !IsEfExceptionMessage(eventId) + && !IsFiltered(logLevel, eventId, exception); + private bool ShouldAddBreadcrumb(LogLevel logLevel) => _options.MinimumBreadcrumbLevel != LogLevel.None && logLevel >= _options.MinimumBreadcrumbLevel; @@ -185,7 +212,7 @@ private bool IsFiltered( } catch (Exception e) { - _options.LogError(e, "The {0} log filter callback failed. The log entry will be filtered out.", filter.GetType().Name); + _hub.GetSentryOptions()?.LogError(e, "The {0} log filter callback failed. The log entry will be filtered out.", filter.GetType().Name); return true; } } diff --git a/src/Sentry.Extensions.Logging/SentryLoggerFactoryExtensions.cs b/src/Sentry.Extensions.Logging/SentryLoggerFactoryExtensions.cs index c2f2fc1e9e..f9fd8fae8d 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggerFactoryExtensions.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggerFactoryExtensions.cs @@ -17,8 +17,8 @@ public static class SentryLoggerFactoryExtensions /// Adds the Sentry logging integration. /// /// - /// This method does not need to be called when calling `UseSentry` with ASP.NET Core - /// since that integrates with the logging framework automatically. + /// This method does not initialize Sentry. Initialize it separately, with + /// or a framework integration such as UseSentry. /// /// The factory. /// The options configuration. @@ -30,34 +30,7 @@ public static ILoggerFactory AddSentry( optionsConfiguration?.Invoke(options); - if (options.DiagnosticLogger == null) - { - var logger = factory.CreateLogger(); - options.DiagnosticLogger = new MelDiagnosticLogger(logger, options.DiagnosticLevel); - } - - IHub hub; - if (options.InitializeSdk) - { - if (SentrySdk.IsEnabled && options.Dsn is null) - { - options.LogWarning("Not calling Init from {0} because SDK is already enabled and no DSN was provided to the integration", nameof(SentryLoggerFactoryExtensions)); - hub = HubAdapter.Instance; - } - else - { - options.LogDebug("Initializing from {0} and swapping current Hub.", nameof(SentryLoggerFactoryExtensions)); - hub = SentrySdk.InitHub(options); - SentrySdk.UseHub(hub); - } - } - else - { - // Access to whatever the SentrySdk points to (disabled or initialized via SentrySdk.Init) - hub = HubAdapter.Instance; - } - - factory.AddProvider(new SentryLoggerProvider(hub, SystemClock.Clock, options)); + factory.AddProvider(new SentryLoggerProvider(HubAdapter.Instance, SystemClock.Clock, options)); return factory; } } diff --git a/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs b/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs index 034e7b4694..32e484ba02 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggerProvider.cs @@ -1,7 +1,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Sentry.Infrastructure; -using Sentry.Reflection; +using Sentry.Internal; namespace Sentry.Extensions.Logging; @@ -13,16 +13,10 @@ internal class SentryLoggerProvider : ILoggerProvider { private readonly ISystemClock _clock; private readonly SentryLoggingOptions _options; - private readonly IDisposable? _scope; - private readonly IDisposable? _disposableHub; + private readonly UninitializedSdkWarning _uninitializedSdkWarning; internal IHub Hub { get; } - internal static readonly SdkVersion NameAndVersion - = typeof(SentryLogger).Assembly.GetNameAndVersion(); - - private static readonly string ProtocolPackageName = "nuget:" + NameAndVersion.Name; - /// /// Creates a new instance of . /// @@ -37,37 +31,13 @@ public SentryLoggerProvider(IOptions options, IHub hub) internal SentryLoggerProvider( IHub hub, ISystemClock clock, - SentryLoggingOptions options) + SentryLoggingOptions options, + UninitializedSdkWarning? uninitializedSdkWarning = null) { - _disposableHub = hub as IDisposable; - Hub = hub; _clock = clock; _options = options; - - if (hub.IsEnabled) - { - _scope = hub.PushScope(); - hub.ConfigureScope(static s => - { - if (s.Sdk is { } sdk) - { - sdk.Name = Constants.SdkName; - sdk.Version = NameAndVersion.Version; - - if (NameAndVersion.Version is { } version) - { - sdk.AddPackage(ProtocolPackageName, version); - } - } - }); - - // Add scope configuration to hub from options - foreach (var callback in options.ConfigureScopeCallbacks) - { - hub.ConfigureScope(callback); - } - } + _uninitializedSdkWarning = uninitializedSdkWarning ?? new UninitializedSdkWarning(); } /// @@ -75,14 +45,10 @@ internal SentryLoggerProvider( /// /// Category name. /// A logger. - public ILogger CreateLogger(string categoryName) => new SentryLogger(categoryName, _options, _clock, Hub); + public ILogger CreateLogger(string categoryName) + => new SentryLogger(categoryName, _options, _clock, Hub, _uninitializedSdkWarning); - /// - /// Dispose. - /// public void Dispose() { - _scope?.Dispose(); - _disposableHub?.Dispose(); } } diff --git a/src/Sentry.Extensions.Logging/SentryLoggingConfiguration.cs b/src/Sentry.Extensions.Logging/SentryLoggingConfiguration.cs new file mode 100644 index 0000000000..0e27672efe --- /dev/null +++ b/src/Sentry.Extensions.Logging/SentryLoggingConfiguration.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Sentry.Extensions.Logging; + +internal static class SentryLoggingConfiguration +{ + private static readonly string[] LoggingKeys = + [ + "LogLevel", + nameof(SentryLoggingOptions.MinimumEventLevel), + nameof(SentryLoggingOptions.MinimumBreadcrumbLevel), + ]; + + private static readonly string[] SdkInitializationKeys = ["Dsn", "InitializeSdk"]; + + internal static void ApplyTo(IConfiguration section, SentryLoggingOptions options) + { + RejectSdkSettings(section); + + if (section[nameof(options.MinimumEventLevel)] is { } eventLevel + && Enum.TryParse(eventLevel, ignoreCase: true, out var minimumEventLevel)) + { + options.MinimumEventLevel = minimumEventLevel; + } + + if (section[nameof(options.MinimumBreadcrumbLevel)] is { } breadcrumbLevel + && Enum.TryParse(breadcrumbLevel, ignoreCase: true, out var minimumBreadcrumbLevel)) + { + options.MinimumBreadcrumbLevel = minimumBreadcrumbLevel; + } + } + + private static void RejectSdkSettings(IConfiguration section) + { + if (section["Dsn"] is not null + || (bool.TryParse(section["InitializeSdk"], out var initializeSdk) && initializeSdk)) + { + throw new NotSupportedException(SentryLoggingOptions.ObsoleteSdkInitialization); + } + + var sdkSettings = section.GetChildren() + .Select(child => child.Key) + .Where(key => !LoggingKeys.Contains(key, StringComparer.OrdinalIgnoreCase) + && !SdkInitializationKeys.Contains(key, StringComparer.OrdinalIgnoreCase)) + .ToArray(); + + if (sdkSettings.Length > 0) + { + throw new NotSupportedException( + "Sentry's logging configuration section no longer configures the SDK, so these settings have no " + + $"effect there: {string.Join(", ", sdkSettings)}. Configure them where Sentry is initialized " + + "instead: the 'Sentry' configuration section when using an integration such as UseSentry, or the " + + "options passed to SentrySdk.Init."); + } + } +} diff --git a/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs b/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs index 3014f7cd0e..df03d63c1f 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggingOptions.cs @@ -5,9 +5,52 @@ namespace Sentry.Extensions.Logging; /// /// Sentry logging integration options /// -/// -public class SentryLoggingOptions : SentryOptions +/// +/// These only configure which log entries are sent to Sentry. Sentry itself is initialized separately, with +/// or a framework integration such as UseSentry. +/// +public class SentryLoggingOptions { + internal const string ObsoleteSdkInitialization = + "The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be " + + "supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and " + + "remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration."; + + /// + /// Not supported. The logging integration no longer initializes the SDK. + /// + /// When set. + [Obsolete(ObsoleteSdkInitialization, error: true)] + public string? Dsn + { + get => null; + // Only a supplied value is an error: the configuration binder writes the getter's value back. + set + { + if (value is not null) + { + throw new NotSupportedException(ObsoleteSdkInitialization); + } + } + } + + /// + /// Not supported. The logging integration no longer initializes the SDK. + /// + /// When set. + [Obsolete(ObsoleteSdkInitialization, error: true)] + public bool InitializeSdk + { + get => false; + set + { + if (value) + { + throw new NotSupportedException(ObsoleteSdkInitialization); + } + } + } + /// /// Gets or sets the minimum breadcrumb level. /// @@ -30,24 +73,8 @@ public class SentryLoggingOptions : SentryOptions /// public LogLevel MinimumEventLevel { get; set; } = LogLevel.Error; - /// - /// Whether to initialize this SDK through this integration - /// - public bool InitializeSdk { get; set; } = true; - - /// - /// Add a callback to configure the scope upon SDK initialization - /// - /// The function to invoke when initializing the SDK - public void ConfigureScope(Action action) => ConfigureScopeCallbacks = ConfigureScopeCallbacks.Concat(new[] { action }).ToArray(); - /// /// Log entry filters /// internal ILogEntryFilter[] Filters { get; set; } = Array.Empty(); - - /// - /// List of callbacks to be invoked when initializing the SDK - /// - internal Action[] ConfigureScopeCallbacks { get; set; } = Array.Empty>(); } diff --git a/src/Sentry.Extensions.Logging/SentryLoggingOptionsExtensions.cs b/src/Sentry.Extensions.Logging/SentryLoggingOptionsExtensions.cs index a536de7cb8..a89390b4c7 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggingOptionsExtensions.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggingOptionsExtensions.cs @@ -35,4 +35,14 @@ public static void AddLogEntryFilter( this SentryLoggingOptions options, Func filter) => options.AddLogEntryFilter(new DelegateLogEntryFilter(filter)); + + /// + public static void AddLogEntryFilter(this SentryHostOptions options, ILogEntryFilter filter) + => options.Logging.AddLogEntryFilter(filter); + + /// + public static void AddLogEntryFilter( + this SentryHostOptions options, + Func filter) + => options.Logging.AddLogEntryFilter(filter); } diff --git a/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs b/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs index 401d3a864a..0c0032f09b 100644 --- a/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs +++ b/src/Sentry.Extensions.Logging/SentryLoggingOptionsSetup.cs @@ -1,4 +1,3 @@ -#if NET6_0_OR_GREATER using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options; @@ -18,23 +17,6 @@ public SentryLoggingOptionsSetup(ILoggerProviderConfiguration -{ - public SentryLoggingOptionsSetup( - ILoggerProviderConfiguration providerConfiguration) - : base(providerConfiguration.Configuration) - { } -} -#endif diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs index 2b7c2c1aed..907670a332 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLogger.cs @@ -7,15 +7,13 @@ namespace Sentry.Extensions.Logging; internal sealed class SentryStructuredLogger : ILogger { private readonly string? _categoryName; - private readonly SentryLoggingOptions _options; private readonly IHub _hub; private readonly ISystemClock _clock; - private readonly SdkVersion _sdk; + private readonly SdkVersion? _sdk; - internal SentryStructuredLogger(string categoryName, SentryLoggingOptions options, IHub hub, ISystemClock clock, SdkVersion sdk) + internal SentryStructuredLogger(string categoryName, IHub hub, ISystemClock clock, SdkVersion? sdk) { _categoryName = categoryName; - _options = options; _clock = clock; _hub = hub; _sdk = sdk; @@ -34,7 +32,7 @@ public bool IsEnabled(LogLevel logLevel) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - if (!IsEnabled(logLevel)) + if (!IsEnabled(logLevel) || _hub.GetSentryOptions() is not { } options) { return; } @@ -52,7 +50,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except } catch (FormatException e) { - _options.DiagnosticLogger?.LogError(e, "Template string does not match the provided argument. The Log will be dropped."); + options.DiagnosticLogger?.LogError(e, "Template string does not match the provided argument. The Log will be dropped."); return; } @@ -87,7 +85,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except }; var scope = _hub.GetScope(); - log.SetDefaultAttributes(_options, scope, _sdk); + log.SetDefaultAttributes(options, scope, _sdk); log.SetOrigin("auto.log.extensions_logging"); if (_categoryName is not null) diff --git a/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs b/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs index bf74a36da0..4345f16710 100644 --- a/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs +++ b/src/Sentry.Extensions.Logging/SentryStructuredLoggerProvider.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Sentry.Infrastructure; namespace Sentry.Extensions.Logging; @@ -10,24 +9,17 @@ namespace Sentry.Extensions.Logging; [ProviderAlias("Sentry")] internal class SentryStructuredLoggerProvider : ILoggerProvider { - private readonly SentryLoggingOptions _options; private readonly IHub _hub; private readonly ISystemClock _clock; - private readonly SdkVersion _sdk; + private readonly SdkVersion? _sdk; - public SentryStructuredLoggerProvider(IOptions options, IHub hub) - : this(options.Value, hub, SystemClock.Clock, CreateSdkVersion()) + public SentryStructuredLoggerProvider(IHub hub) + : this(hub, SystemClock.Clock, sdk: null) { } - internal SentryStructuredLoggerProvider(IHub hub, ISystemClock clock, SentryLoggingOptions options) - : this(options, hub, clock, CreateSdkVersion()) + internal SentryStructuredLoggerProvider(IHub hub, ISystemClock clock, SdkVersion? sdk) { - } - - internal SentryStructuredLoggerProvider(SentryLoggingOptions options, IHub hub, ISystemClock clock, SdkVersion sdk) - { - _options = options; _hub = hub; _clock = clock; _sdk = sdk; @@ -35,19 +27,10 @@ internal SentryStructuredLoggerProvider(SentryLoggingOptions options, IHub hub, public ILogger CreateLogger(string categoryName) { - return new SentryStructuredLogger(categoryName, _options, _hub, _clock, _sdk); + return new SentryStructuredLogger(categoryName, _hub, _clock, _sdk); } public void Dispose() { } - - private static SdkVersion CreateSdkVersion() - { - return new SdkVersion - { - Name = Constants.SdkName, - Version = SentryLoggerProvider.NameAndVersion.Version, - }; - } } diff --git a/src/Sentry.Google.Cloud.Functions/SentryStartup.cs b/src/Sentry.Google.Cloud.Functions/SentryStartup.cs index ad7e485043..2a3c69f1c1 100644 --- a/src/Sentry.Google.Cloud.Functions/SentryStartup.cs +++ b/src/Sentry.Google.Cloud.Functions/SentryStartup.cs @@ -8,6 +8,7 @@ using Sentry; using Sentry.AspNetCore; using Sentry.Extensibility; +using Sentry.Extensions.Logging; using Sentry.Reflection; namespace Google.Cloud.Functions.Framework; @@ -32,6 +33,9 @@ public override void ConfigureLogging(WebHostBuilderContext context, ILoggingBui logging.Services.AddSingleton>( _ => new SentryAspNetCoreOptionsSetup(section) ); + logging.Services.AddSingleton>( + _ => new SentryHostLoggingOptionsSetup(context.Configuration) + ); logging.Services.Configure(options => { @@ -55,7 +59,6 @@ public override void ConfigureLogging(WebHostBuilderContext context, ILoggingBui } }); - logging.Services.AddSingleton, SentryAspNetCoreOptionsSetup>(); logging.Services.AddSingleton(); logging.Services.AddSingleton(); diff --git a/src/Sentry.Log4Net/Sentry.Log4Net.csproj b/src/Sentry.Log4Net/Sentry.Log4Net.csproj index 061bfb9a3c..26c184319c 100644 --- a/src/Sentry.Log4Net/Sentry.Log4Net.csproj +++ b/src/Sentry.Log4Net/Sentry.Log4Net.csproj @@ -18,7 +18,9 @@ + + diff --git a/src/Sentry.Log4Net/SentryAppender.Structured.cs b/src/Sentry.Log4Net/SentryAppender.Structured.cs index eaa75742d4..08403c3b78 100644 --- a/src/Sentry.Log4Net/SentryAppender.Structured.cs +++ b/src/Sentry.Log4Net/SentryAppender.Structured.cs @@ -2,7 +2,7 @@ namespace Sentry.Log4Net; public partial class SentryAppender { - private static void CaptureStructuredLog(IHub hub, SentryOptions options, LoggingEvent loggingEvent, string? environment, bool sendIdentity) + private static void CaptureStructuredLog(IHub hub, SentryOptions options, LoggingEvent loggingEvent, bool sendIdentity) { if (loggingEvent.ToSentryLogLevel() is not { } level) { @@ -17,15 +17,9 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, Loggin var log = SentryLog.Create(hub, timestamp, level, message, template, parameters); var scope = hub.GetScope(); - log.SetDefaultAttributes(options, scope, Sdk); + log.SetDefaultAttributes(options, scope); log.SetOrigin("auto.log.log4net"); - // Honor the appender-level settings, overriding the scope/options defaults, to match the SentryEvent path. - if (!string.IsNullOrWhiteSpace(environment)) - { - log.SetAttribute("sentry.environment", environment!); - } - if (sendIdentity && !string.IsNullOrEmpty(loggingEvent.Identity)) { log.SetAttribute("user.id", loggingEvent.Identity); diff --git a/src/Sentry.Log4Net/SentryAppender.cs b/src/Sentry.Log4Net/SentryAppender.cs index 55c9fd53dd..9f3ba2124d 100644 --- a/src/Sentry.Log4Net/SentryAppender.cs +++ b/src/Sentry.Log4Net/SentryAppender.cs @@ -7,36 +7,35 @@ namespace Sentry.Log4Net; /// public partial class SentryAppender : AppenderSkeleton { - private readonly Func _initAction; - private volatile IDisposable? _sdkHandle; - - private readonly Lock _initSync = new(); - - internal static readonly SdkVersion NameAndVersion - = typeof(SentryAppender).Assembly.GetNameAndVersion(); - - private static readonly SdkVersion Sdk = new() - { - Name = SdkName, - Version = NameAndVersion.Version, - }; + private readonly IHub _hub; + private readonly UninitializedSdkWarning _uninitializedSdkWarning; - private static readonly string ProtocolPackageName = "nuget:" + NameAndVersion.Name; + internal const string UninitializedSdkMessage = + "Sentry: the Sentry appender for log4net dropped a log event because Sentry is not initialized, but a " + + "DSN was found in the environment or in an assembly attribute. The appender no longer initializes the " + + "SDK: call SentrySdk.Init (or UseSentry via one of the integrations) at startup. " + + "See https://docs.sentry.io/platforms/dotnet/guides/log4net/"; - private readonly IHub _hub; + internal const string ObsoleteDsn = + "The Sentry appender no longer initializes the SDK, so a DSN can no longer be supplied to it. " + + "Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn' " + + "and any other core SDK settings from the appender configuration."; /// - /// Sentry DSN. + /// Not supported. The Sentry appender no longer initializes the SDK. /// - public string? Dsn { get; set; } + /// When set. + [Obsolete(ObsoleteDsn, error: true)] + public string? Dsn + { + get => null; + set => throw new NotSupportedException(ObsoleteDsn); + } + /// /// Whether to send the Identity or not. /// public bool SendIdentity { get; set; } - /// - /// Environment to send in the event. - /// - public string? Environment { get; set; } /// /// Lowest level required for a log message to become an event. @@ -44,24 +43,18 @@ internal static readonly SdkVersion NameAndVersion /// public Level? MinimumEventLevel { get; set; } - /// - /// log4net SDK name. - /// - /// - internal const string SdkName = "sentry.dotnet.log4net"; - /// /// Creates a new instance of the . /// - public SentryAppender() : this(SentrySdk.Init, HubAdapter.Instance) + public SentryAppender() : this(HubAdapter.Instance) { } - internal SentryAppender( - Func initAction, - IHub hubGetter) + internal SentryAppender(IHub hub, UninitializedSdkWarning? uninitializedSdkWarning = null) { - _initAction = initAction; - _hub = hubGetter; + _hub = hub; + _uninitializedSdkWarning = uninitializedSdkWarning + ?? new UninitializedSdkWarning( + message => LogLog.Warn(typeof(SentryAppender), message)); } /// @@ -76,18 +69,14 @@ protected override void Append(LoggingEvent loggingEvent) return; } - if (!_hub.IsEnabled && _sdkHandle == null) + if (!_hub.IsEnabled) { - if (Dsn == null) + if (MinimumEventLevel is null || loggingEvent.Level >= MinimumEventLevel) { - return; + _uninitializedSdkWarning.WarnOnce(UninitializedSdkMessage); } - lock (_initSync) - { - // ReSharper disable once NonAtomicCompoundOperator Double init guarded by the lock - _sdkHandle ??= _initAction(Dsn); - } + return; } CaptureStructuredLog(loggingEvent); @@ -113,7 +102,7 @@ private void CaptureStructuredLog(LoggingEvent loggingEvent) try { - CaptureStructuredLog(_hub, options, loggingEvent, Environment, SendIdentity); + CaptureStructuredLog(_hub, options, loggingEvent, SendIdentity); } catch (Exception ex) { @@ -129,17 +118,6 @@ private void CreateSentryEvent(LoggingEvent loggingEvent, Exception? exception) Level = loggingEvent.ToSentryLevel() }; - if (evt.Sdk is { } sdk) - { - sdk.Name = SdkName; - sdk.Version = NameAndVersion.Version; - - if (NameAndVersion.Version is { } version) - { - sdk.AddPackage(ProtocolPackageName, version); - } - } - if (!string.IsNullOrWhiteSpace(loggingEvent.RenderedMessage)) { evt.Message = loggingEvent.RenderedMessage; @@ -155,11 +133,6 @@ private void CreateSentryEvent(LoggingEvent loggingEvent, Exception? exception) }; } - if (!string.IsNullOrWhiteSpace(Environment)) - { - evt.Environment = Environment; - } - _hub.CaptureEvent(evt); } @@ -244,14 +217,4 @@ private void AddBreadcrumbFromLoggingEvent(LoggingEvent loggingEvent, Exception? yield return new KeyValuePair("log4net-level", loggingEvent.Level.Name); } } - - /// - /// Disposes the SDK if initialized. - /// - protected override void OnClose() - { - base.OnClose(); - - _sdkHandle?.Dispose(); - } } diff --git a/src/Sentry.Maui/BindableSentryMauiOptions.cs b/src/Sentry.Maui/BindableSentryMauiOptions.cs index f4f98e1ad0..3a79156eec 100644 --- a/src/Sentry.Maui/BindableSentryMauiOptions.cs +++ b/src/Sentry.Maui/BindableSentryMauiOptions.cs @@ -3,7 +3,7 @@ namespace Sentry.Maui; /// -internal class BindableSentryMauiOptions : BindableSentryLoggingOptions +internal class BindableSentryMauiOptions : BindableSentryHostOptions { public bool? IncludeTextInBreadcrumbs { get; set; } public bool? IncludeTitleInBreadcrumbs { get; set; } diff --git a/src/Sentry.Maui/Internal/SentryMauiInitializer.cs b/src/Sentry.Maui/Internal/SentryMauiInitializer.cs index 3a59dc4c81..209f3d6948 100644 --- a/src/Sentry.Maui/Internal/SentryMauiInitializer.cs +++ b/src/Sentry.Maui/Internal/SentryMauiInitializer.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Options; +using Sentry.Extensibility; namespace Sentry.Maui.Internal; @@ -14,6 +15,7 @@ public void Initialize(IServiceProvider services) // Initialize the Sentry SDK. var disposable = SentrySdk.Init(options); + options.ApplyConfigureScopeCallbacks(HubAdapter.Instance); // Register the return value from initializing the SDK with the disposer. // This will ensure that it gets disposed when the service provider is disposed. diff --git a/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs b/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs index dddd59bae2..98f0764438 100644 --- a/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs +++ b/src/Sentry.Maui/Internal/SentryMauiLoggerProvider.cs @@ -12,12 +12,12 @@ namespace Sentry.Maui.Internal; internal sealed class SentryMauiLoggerProvider : SentryLoggerProvider { public SentryMauiLoggerProvider(IOptions options, IHub hub) - : base(options, hub) + : base(hub, SystemClock.Clock, options.Value.Logging) { } internal SentryMauiLoggerProvider(SentryMauiOptions options, IHub hub, ISystemClock clock) - : base(hub, clock, options) + : base(hub, clock, options.Logging) { } } diff --git a/src/Sentry.Maui/Internal/SentryMauiOptionsSetup.cs b/src/Sentry.Maui/Internal/SentryMauiOptionsSetup.cs index 7436d0720e..f759e7861d 100644 --- a/src/Sentry.Maui/Internal/SentryMauiOptionsSetup.cs +++ b/src/Sentry.Maui/Internal/SentryMauiOptionsSetup.cs @@ -33,9 +33,6 @@ public void Configure(SentryMauiOptions options) // NOTE: Anything set here will overwrite options set by the user. // For option defaults that can be changed, use the constructor in SentryMauiOptions instead. - // We'll initialize the SDK in SentryMauiInitializer - options.InitializeSdk = false; - // Global Mode makes sense for client apps options.IsGlobalModeEnabled = true; diff --git a/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs b/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs index a72ffafc46..79087cddd5 100644 --- a/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs +++ b/src/Sentry.Maui/Internal/SentryMauiStructuredLoggerProvider.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using Sentry.Extensions.Logging; using Sentry.Infrastructure; @@ -11,13 +10,13 @@ namespace Sentry.Maui.Internal; [ProviderAlias("Sentry")] internal sealed class SentryMauiStructuredLoggerProvider : SentryStructuredLoggerProvider { - public SentryMauiStructuredLoggerProvider(IOptions options, IHub hub) - : this(options.Value, hub, SystemClock.Clock, CreateSdkVersion()) + public SentryMauiStructuredLoggerProvider(IHub hub) + : this(hub, SystemClock.Clock, CreateSdkVersion()) { } - internal SentryMauiStructuredLoggerProvider(SentryMauiOptions options, IHub hub, ISystemClock clock, SdkVersion sdk) - : base(options, hub, clock, sdk) + internal SentryMauiStructuredLoggerProvider(IHub hub, ISystemClock clock, SdkVersion sdk) + : base(hub, clock, sdk) { } diff --git a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs index 579c2b63e6..647ae61756 100644 --- a/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs +++ b/src/Sentry.Maui/SentryMauiAppBuilderExtensions.cs @@ -78,7 +78,7 @@ public static MauiAppBuilder UseSentry(this MauiAppBuilder builder, // This is ultimately the class that enables all the MauiElementEventBinders above services.TryAddSingleton(); - services.AddSentry(); + services.AddSentry(initializeSdk: false); builder.RegisterMauiEventsBinder(); diff --git a/src/Sentry.Maui/SentryMauiOptions.cs b/src/Sentry.Maui/SentryMauiOptions.cs index 6a7ecccb8c..680c960fe0 100644 --- a/src/Sentry.Maui/SentryMauiOptions.cs +++ b/src/Sentry.Maui/SentryMauiOptions.cs @@ -7,7 +7,7 @@ namespace Sentry.Maui; /// /// Sentry MAUI integration options /// -public class SentryMauiOptions : SentryLoggingOptions +public class SentryMauiOptions : SentryHostOptions { /// /// Creates a new instance of . diff --git a/src/Sentry.NLog/ConfigurationExtensions.cs b/src/Sentry.NLog/ConfigurationExtensions.cs index 596fb9bca0..5fb8e6a3ad 100644 --- a/src/Sentry.NLog/ConfigurationExtensions.cs +++ b/src/Sentry.NLog/ConfigurationExtensions.cs @@ -12,66 +12,58 @@ public static class ConfigurationExtensions // Internal for testability internal const string DefaultTargetName = "sentry"; + internal const string ObsoleteDsnOverload = + "The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. " + + "Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', " + + "'initializeSdk' and any other core SDK settings from the target configuration."; + /// - /// Adds a target for Sentry to the NLog configuration. + /// Not supported. The Sentry target no longer initializes the SDK. /// - /// - /// If DSN is not set, the SDK will look for an environment variable called SENTRY_DSN. If nothing is - /// found, SDK is disabled. - /// /// The NLog configuration. + /// No longer supported. /// An optional action for configuring the Sentry target options. - /// The configuration. + /// Never returns. + /// Always. + [Obsolete(ObsoleteDsnOverload, error: true)] public static LoggingConfiguration AddSentry( this LoggingConfiguration configuration, + string? dsn, Action? optionsConfig = null) - { - // Not to throw on code that ignores nullability warnings. - if (configuration.IsNull()) - { - return configuration!; - } - - return configuration.AddSentry(null, DefaultTargetName, optionsConfig); - } + => throw new NotSupportedException(ObsoleteDsnOverload); /// - /// Adds a target for Sentry to the NLog configuration. + /// Not supported. The Sentry target no longer initializes the SDK. /// /// The NLog configuration. - /// - /// The sentry DSN. If DSN is not set, the SDK will look for an environment variable called SENTRY_DSN. - /// If nothing is found, SDK is disabled. - /// + /// No longer supported. + /// The name to give the new target. /// An optional action for configuring the Sentry target options. - /// The configuration. + /// Never returns. + /// Always. + [Obsolete(ObsoleteDsnOverload, error: true)] public static LoggingConfiguration AddSentry( this LoggingConfiguration configuration, string? dsn, + string targetName, Action? optionsConfig = null) - { - // Not to throw on code that ignores nullability warnings. - if (configuration.IsNull()) - { - return configuration!; - } - - return configuration.AddSentry(dsn, DefaultTargetName, optionsConfig); - } + => throw new NotSupportedException(ObsoleteDsnOverload); /// /// Adds a target for Sentry to the NLog configuration. /// + /// + /// This doesn't initialise Sentry. Initialise Sentry separately, using SentrySdk.Init or another Sentry + /// integration (such as ASP.NET Core or MAUI). + /// /// The NLog configuration. - /// The sentry DSN. - /// The name to give the new target. /// An optional action for configuring the Sentry target options. + /// The name to give the new target. /// The configuration. public static LoggingConfiguration AddSentry( this LoggingConfiguration configuration, - string? dsn, - string targetName, - Action? optionsConfig = null) + Action? optionsConfig = null, + string targetName = DefaultTargetName) { // Not to throw on code that ignores nullability warnings. if (configuration.IsNull()) @@ -95,11 +87,6 @@ public static LoggingConfiguration AddSentry( Layout = "${message}", }; - if (dsn != null && string.IsNullOrWhiteSpace(options.Dsn)) - { - options.Dsn = dsn; - } - configuration.AddTarget(targetName, target); configuration.AddRuleForAllLevels(targetName); diff --git a/src/Sentry.NLog/Constants.cs b/src/Sentry.NLog/Constants.cs deleted file mode 100644 index 65df808c29..0000000000 --- a/src/Sentry.NLog/Constants.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Sentry.NLog; - -/// -/// NLog Constant Values. -/// -internal static class Constants -{ - /// - /// NLog SDK name - /// - public const string SdkName = "sentry.dotnet.nlog"; -} diff --git a/src/Sentry.NLog/NLogDiagnosticLogger.cs b/src/Sentry.NLog/NLogDiagnosticLogger.cs deleted file mode 100644 index 493b8ff6a7..0000000000 --- a/src/Sentry.NLog/NLogDiagnosticLogger.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace Sentry.NLog; - -internal class NLogDiagnosticLogger : IDiagnosticLogger -{ - private readonly IDiagnosticLogger? _extraLogger; - - public NLogDiagnosticLogger(IDiagnosticLogger? extraLogger = null) - { - if (!InternalLogger.LogToConsole || extraLogger is not ConsoleDiagnosticLogger) - { - _extraLogger = extraLogger; - } - } - - public bool IsEnabled(SentryLevel level) - { - if (_extraLogger?.IsEnabled(level) == true) - { - return true; - } - - return level switch - { - SentryLevel.Fatal => InternalLogger.IsFatalEnabled, - SentryLevel.Error => InternalLogger.IsErrorEnabled, - SentryLevel.Warning => InternalLogger.IsWarnEnabled, - SentryLevel.Info => InternalLogger.IsInfoEnabled, - _ => InternalLogger.IsDebugEnabled - }; - } - - public void Log(SentryLevel logLevel, string message, Exception? exception = null, params object?[] args) - { - switch (logLevel) - { - case SentryLevel.Fatal: - InternalLogger.Fatal(exception, message, args); - break; - case SentryLevel.Error: - InternalLogger.Error(exception, message, args); - break; - case SentryLevel.Warning: - InternalLogger.Warn(exception, message, args); - break; - case SentryLevel.Info: - InternalLogger.Info(exception, message, args); - break; - default: - InternalLogger.Debug(exception, message, args); - break; - } - - if (_extraLogger?.IsEnabled(logLevel) == true) - { - _extraLogger.Log(logLevel, message, exception, args); - } - } -} diff --git a/src/Sentry.NLog/Sentry.NLog.csproj b/src/Sentry.NLog/Sentry.NLog.csproj index 66aa9ef4b2..c1834bcaac 100644 --- a/src/Sentry.NLog/Sentry.NLog.csproj +++ b/src/Sentry.NLog/Sentry.NLog.csproj @@ -28,6 +28,7 @@ + diff --git a/src/Sentry.NLog/SentryNLogOptions.cs b/src/Sentry.NLog/SentryNLogOptions.cs index e273fb3ddf..99ec14a79b 100644 --- a/src/Sentry.NLog/SentryNLogOptions.cs +++ b/src/Sentry.NLog/SentryNLogOptions.cs @@ -1,29 +1,15 @@ namespace Sentry.NLog; /// -/// Sentry Options for NLog logging. All properties can be configured via code or in NLog.config xml file. +/// Options for the Sentry target for NLog. All properties can be configured via code or the NLog.config file. /// -/// +/// +/// These options only configure the target. The Sentry SDK itself is configured and initialised separately, using +/// SentrySdk.Init or another Sentry integration (such as ASP.NET Core or MAUI). +/// [NLogConfigurationItem] -public class SentryNLogOptions : SentryOptions +public class SentryNLogOptions { - /// - public SentryNLogOptions() - { - // NLog's default flush timeout is 15 seconds, so we will do the same. - FlushTimeout = TimeSpan.FromSeconds(15); - } - - /// - /// How many seconds to wait after triggering before just shutting down the - /// Sentry sdk. - /// - public int ShutdownTimeoutSeconds - { - get => (int)ShutdownTimeout.TotalSeconds; - set => ShutdownTimeout = TimeSpan.FromSeconds(value); - } - /// /// Minimum log level for events to trigger a send to Sentry. Defaults to . /// @@ -68,39 +54,12 @@ public int ShutdownTimeoutSeconds [NLogConfigurationIgnoreProperty] // Configure this directly on the target in XML config. public Layout? Layout { get; set; } - /// - /// Configured layout for Dsn-Address to Sentry - /// - [NLogConfigurationIgnoreProperty] // Configure this directly on the target in XML config. - public Layout? DsnLayout { get; set; } - - /// - /// Configured layout for application Release version to Sentry - /// - [NLogConfigurationIgnoreProperty] // Configure this directly on the target in XML config. - public Layout? ReleaseLayout { get; set; } - - /// - /// Configured layout for application Environment to Sentry - /// - [NLogConfigurationIgnoreProperty] // Configure this directly on the target in XML config. - public Layout? EnvironmentLayout { get; set; } - /// /// Any additional tags to apply to each logged message. /// [NLogConfigurationIgnoreProperty] // Configure this directly on the target in XML config. public IList Tags { get; } = new List(); - /// - /// Whether the NLog integration should initialize the SDK. - /// - /// - /// By default, if a DSN is provided to the NLog integration it will initialize the SDK. - /// This might be not ideal when using multiple integrations in case you want another one doing the Init. - /// - public bool InitializeSdk { get; set; } = true; - /// /// Optionally configure one or more parts of the user information to be rendered dynamically from an NLog layout /// diff --git a/src/Sentry.NLog/SentryTarget.Structured.cs b/src/Sentry.NLog/SentryTarget.Structured.cs index 66de2c3b4d..d8f7c96e98 100644 --- a/src/Sentry.NLog/SentryTarget.Structured.cs +++ b/src/Sentry.NLog/SentryTarget.Structured.cs @@ -15,7 +15,7 @@ private static void CaptureStructuredLog(IHub hub, SentryOptions options, LogEve var log = SentryLog.Create(hub, timestamp, level, logEvent.FormattedMessage, logEvent.Message, parameters); var scope = hub.GetScope(); - log.SetDefaultAttributes(options, scope, Sdk); + log.SetDefaultAttributes(options, scope); log.SetOrigin("auto.log.nlog"); if (logEvent.LoggerName is not null) diff --git a/src/Sentry.NLog/SentryTarget.cs b/src/Sentry.NLog/SentryTarget.cs index 666db15264..d2a2eaf6c8 100644 --- a/src/Sentry.NLog/SentryTarget.cs +++ b/src/Sentry.NLog/SentryTarget.cs @@ -10,19 +10,16 @@ public sealed partial class SentryTarget : TargetWithContext internal Func HubAccessor { get; } private readonly ISystemClock _clock; - private IDisposable? _sdkDisposable; - - internal static readonly SdkVersion NameAndVersion = typeof(SentryTarget).Assembly.GetNameAndVersion(); - - private static readonly SdkVersion Sdk = new() - { - Name = Constants.SdkName, - Version = NameAndVersion.Version, - }; + private readonly UninitializedSdkWarning _uninitializedSdkWarning; + private bool _reportedUnsupportedSdkSetting; internal static readonly string AdditionalGroupingKeyProperty = "AdditionalGroupingKey"; - private static readonly string ProtocolPackageName = "nuget:" + NameAndVersion.Name; + internal const string UninitializedSdkMessage = + "Sentry: the Sentry target for NLog dropped a log event because Sentry is not initialized, but a DSN " + + "was found in the environment or in an assembly attribute. The target no longer initializes the SDK: " + + "call SentrySdk.Init (or UseSentry via one of the integrations) at startup. " + + "See https://docs.sentry.io/platforms/dotnet/guides/nlog/"; /// /// Creates a new instance of . @@ -38,31 +35,31 @@ public SentryTarget(SentryNLogOptions options) : this( options, () => HubAdapter.Instance, - null, SystemClock.Clock) { } - internal SentryTarget(SentryNLogOptions options, Func hubAccessor, IDisposable? sdkInstance, ISystemClock clock) + internal SentryTarget( + SentryNLogOptions options, + Func hubAccessor, + ISystemClock clock, + UninitializedSdkWarning? uninitializedSdkWarning = null) { Options = options; HubAccessor = hubAccessor; _clock = clock; + _uninitializedSdkWarning = uninitializedSdkWarning + ?? new UninitializedSdkWarning(message => InternalLogger.Warn("{0}", message)); // Overrides default layout. Still will be explicitly overwritten if manually configured in the // NLog.config file. Layout = "${message}"; BreadcrumbCategory = Options.BreadcrumbCategoryLayout ?? "${logger}"; IncludeEventProperties = true; - - if (sdkInstance != null) - { - _sdkDisposable = sdkInstance; - } } /// - /// Options for both the and the sentry sdk itself. + /// Options for the . /// public SentryNLogOptions Options { get; } @@ -73,30 +70,31 @@ internal SentryTarget(SentryNLogOptions options, Func hubAccessor, IDispos public IList Tags => Options.Tags; /// - /// Configured layout for Data Source Name of a given project in Sentry + /// Not supported. The Sentry target no longer initializes the SDK. /// + /// When set. + [Obsolete(ConfigurationExtensions.ObsoleteDsnOverload, error: true)] public Layout? Dsn { - get => Options.DsnLayout; - set => Options.DsnLayout = value; + get => null; + set => throw ReportUnsupportedSdkSetting(); } /// - /// Configured layout for application Release version to Sentry + /// Not supported. The Sentry target no longer initializes the SDK. /// - public Layout? Release - { - get => Options.ReleaseLayout; - set => Options.ReleaseLayout = value; - } - - /// - /// Configured layout for application Environment to Sentry - /// - public Layout? Environment + /// When set. + [Obsolete(ConfigurationExtensions.ObsoleteDsnOverload, error: true)] + public bool InitializeSdk { - get => Options.EnvironmentLayout; - set => Options.EnvironmentLayout = value; + get => false; + set + { + if (value) + { + throw ReportUnsupportedSdkSetting(); + } + } } /// @@ -152,19 +150,6 @@ public bool EnableLogs } } - /// - /// Whether the NLog integration should initialize the SDK. - /// - /// - /// By default, if a DSN is provided to the NLog integration it will initialize the SDK. - /// This might be not ideal when using multiple integrations in case you want another one doing the Init. - /// - public bool InitializeSdk - { - get => Options.InitializeSdk; - set => Options.InitializeSdk = value; - } - /// /// Set this to to ignore log messages that don't contain an exception. /// @@ -194,25 +179,6 @@ public bool IncludeEventDataOnBreadcrumbs set => Options.IncludeEventDataOnBreadcrumbs = value; } - /// - /// How many seconds to wait after triggering before just shutting down the - /// Sentry sdk. - /// - public int ShutdownTimeoutSeconds - { - get => Options.ShutdownTimeoutSeconds; - set => Options.ShutdownTimeoutSeconds = value; - } - - /// - /// How long to wait for the flush to finish, in seconds. Defaults to 2 seconds. - /// - public int FlushTimeoutSeconds - { - get => (int)Options.FlushTimeout.TotalSeconds; - set => Options.FlushTimeout = TimeSpan.FromSeconds(value); - } - /// /// Optionally configure one or more parts of the user information to be rendered dynamically from an NLog layout /// @@ -222,11 +188,16 @@ public SentryNLogUser? User set => Options.User = value; } - /// - protected override void CloseTarget() + // NLog discards the exception unless throwConfigExceptions is on. Report once: a v6 config may set both. + private NotSupportedException ReportUnsupportedSdkSetting() { - _sdkDisposable?.Dispose(); - base.CloseTarget(); + if (!_reportedUnsupportedSdkSetting) + { + _reportedUnsupportedSdkSetting = true; + _uninitializedSdkWarning.WriteToStandardError("Sentry: " + ConfigurationExtensions.ObsoleteDsnOverload); + } + + return new NotSupportedException(ConfigurationExtensions.ObsoleteDsnOverload); } /// @@ -240,40 +211,6 @@ protected override void InitializeTarget() base.InitializeTarget(); - if (InternalLogger.IsDebugEnabled || InternalLogger.IsInfoEnabled || InternalLogger.IsWarnEnabled || InternalLogger.IsErrorEnabled || InternalLogger.IsFatalEnabled) - { - var existingLogger = Options.DiagnosticLogger; - if (existingLogger is not NLogDiagnosticLogger) - { - Options.DiagnosticLogger = new NLogDiagnosticLogger(existingLogger); - } - Options.Debug = true; - } - - var customDsn = Dsn?.Render(LogEventInfo.CreateNullEvent()); - if (!string.IsNullOrEmpty(customDsn)) - { - Options.Dsn = customDsn; - } - - var customRelease = Release?.Render(LogEventInfo.CreateNullEvent()); - if (!string.IsNullOrEmpty(customRelease)) - { - Options.Release = customRelease; - } - - var customEnvironment = Environment?.Render(LogEventInfo.CreateNullEvent()); - if (!string.IsNullOrEmpty(customEnvironment)) - { - Options.Environment = customEnvironment; - } - - // If the sdk is not there, set it on up. - if (InitializeSdk && _sdkDisposable == null) - { - _sdkDisposable = SentrySdk.Init(Options); - } - if (!HubAccessor().IsEnabled) { InternalLogger.Info("Sentry(Name={0}): Hub not enabled", Name); @@ -284,7 +221,7 @@ protected override void InitializeTarget() protected override void FlushAsync(AsyncContinuation asyncContinuation) { _ = HubAccessor() - .FlushAsync(Options.FlushTimeout) + .FlushAsync() .ContinueWith(t => asyncContinuation(t.Exception)); } @@ -315,7 +252,7 @@ protected override void Write(LogEventInfo logEvent) if (isReentrant.Value) { - Options.DiagnosticLogger?.LogError($"Reentrant log event detected. Logging when inside the scope of another log event can cause a StackOverflowException. LogEventInfo.Message:{logEvent.Message}"); + HubAccessor()?.GetSentryOptions()?.DiagnosticLogger?.LogError($"Reentrant log event detected. Logging when inside the scope of another log event can cause a StackOverflowException. LogEventInfo.Message:{logEvent.Message}"); return; } @@ -327,7 +264,7 @@ protected override void Write(LogEventInfo logEvent) } catch (Exception exception) { - Options.DiagnosticLogger?.LogError(exception, "Failed to write log event"); + HubAccessor()?.GetSentryOptions()?.DiagnosticLogger?.LogError(exception, "Failed to write log event"); throw; } finally @@ -346,6 +283,11 @@ private void InnerWrite(LogEventInfo logEvent) var hub = HubAccessor(); if (!hub.IsEnabled) { + if (logEvent.Level >= Options.MinimumEventLevel) + { + _uninitializedSdkWarning.WarnOnce(UninitializedSdkMessage); + } + return; } @@ -371,8 +313,6 @@ private void InnerWrite(LogEventInfo logEvent) CreateBreadcrumb(logEvent, exception, shouldIncludeProperties, hub); } - // Read the options from the Hub rather than the Target's NLog-Options. If the NLog-Target is added without a - // DSN (i.e. without initialising the SDK), then base options will only be initialised in the Hub options. var sentryOptions = hub.GetSentryOptions(); if (sentryOptions is not null) { @@ -452,22 +392,9 @@ private void CreateSentryEvent(LogEventInfo logEvent, Exception? exception, bool }, Logger = logEvent.LoggerName, Level = logEvent.Level.ToSentryLevel(), - Release = Options.Release, - Environment = Options.Environment, User = GetUser(logEvent) ?? new SentryUser(), }; - if (evt.Sdk is { } sdk) - { - sdk.Name = Constants.SdkName; - sdk.Version = NameAndVersion.Version; - - if (NameAndVersion.Version is { } version) - { - sdk.AddPackage(ProtocolPackageName, version); - } - } - if (Tags.Count > 0 || IncludeEventPropertiesAsTags && logEvent.HasProperties) { evt.SetTags(GetTagsFromLogEvent(logEvent)); diff --git a/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/WebAssemblyHostBuilderExtensionsTests.cs b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/WebAssemblyHostBuilderExtensionsTests.cs new file mode 100644 index 0000000000..b8f486bed2 --- /dev/null +++ b/test/Sentry.AspNetCore.Blazor.WebAssembly.Tests/WebAssemblyHostBuilderExtensionsTests.cs @@ -0,0 +1,83 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Sentry.AspNetCore.Blazor.WebAssembly.Tests; + +public class WebAssemblyHostBuilderExtensionsTests : IDisposable +{ + private readonly List _events = new(); + + public void Dispose() => SentrySdk.Close(); + + private ServiceProvider GetSut( + Action configureOptions, + params (string Key, string Value)[] settings) + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(settings.ToDictionary(s => s.Key, s => s.Value)) + .Build(); + var services = new ServiceCollection(); + services.AddSingleton(new FakeNavigationManager()); + services.AddLogging(logging => logging.AddSentryBlazor(configuration, o => + { + o.Dsn = ValidDsn; + o.BackgroundWorker = Substitute.For(); + o.AutoSessionTracking = false; + o.SetBeforeSend((e, _) => + { + _events.Add(e); + return null; + }); + configureOptions(o); + })); + return services.BuildServiceProvider(); + } + + private SentryBlazorOptions GetOptions( + Action configureOptions, + params (string Key, string Value)[] settings) + { + using var provider = GetSut(configureOptions, settings); + return provider.GetRequiredService>().Value; + } + + [Fact] + public void AddSentryBlazor_Configuration_DoesNotOverridePlatformDefaults() + { + var options = GetOptions(_ => { }, + ("Sentry:DetectStartupTime", nameof(StartupTimeDetectionMode.Best)), + ("Sentry:RequestBodyCompressionLevel", nameof(CompressionLevel.Optimal)), + ("Sentry:IsGlobalModeEnabled", "false")); + + Assert.Equal(StartupTimeDetectionMode.Fast, options.DetectStartupTime); + Assert.Equal(CompressionLevel.NoCompression, options.RequestBodyCompressionLevel); + Assert.True(options.IsGlobalModeEnabled); + } + + [Fact] + public void AddSentryBlazor_Configuration_AppliedAndOverriddenByCallback() + { + var options = GetOptions(o => o.Release = "from-code", + ("Sentry:Release", "from-configuration"), + ("Sentry:Environment", "from-configuration")); + + Assert.Equal("from-code", options.Release); + Assert.Equal("from-configuration", options.Environment); + } + + [Fact] + public void UseSentry_MinimumEventLevel_AppliesToLogger() + { + using var provider = GetSut(o => o.MinimumEventLevel = LogLevel.Critical); + var logger = provider.GetRequiredService().CreateLogger("test_category"); + + logger.LogError("below the configured level"); + logger.LogCritical("at the configured level"); + + _events.Should().ContainSingle().Which.Message!.Message.Should().Be("at the configured level"); + } +} diff --git a/test/Sentry.AspNetCore.Grpc.Tests/allsettings.json b/test/Sentry.AspNetCore.Grpc.Tests/allsettings.json index 4be2835072..880d7ce1e5 100644 --- a/test/Sentry.AspNetCore.Grpc.Tests/allsettings.json +++ b/test/Sentry.AspNetCore.Grpc.Tests/allsettings.json @@ -6,7 +6,6 @@ "IncludeActivityData": true, "MinimumBreadcrumbLevel": "Error", "MinimumEventLevel": "Critical", - "InitializeSdk": "false", "MaxBreadcrumbs": "999", "SampleRate": "1", "Release": "7f5d9a1", 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 f6ceb2a3c3..463241570b 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -39,7 +39,7 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions + public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryHostOptions { public SentryAspNetCoreOptions() { } public bool AdjustStandardEnvironmentNameCasing { get; set; } diff --git a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt index f6ceb2a3c3..463241570b 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt @@ -39,7 +39,7 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions + public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryHostOptions { public SentryAspNetCoreOptions() { } public bool AdjustStandardEnvironmentNameCasing { get; set; } 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 f6ceb2a3c3..463241570b 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -39,7 +39,7 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions + public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryHostOptions { public SentryAspNetCoreOptions() { } public bool AdjustStandardEnvironmentNameCasing { get; set; } 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 f6ceb2a3c3..463241570b 100644 --- a/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.AspNetCore.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -39,7 +39,7 @@ namespace Sentry.AspNetCore public static string? TryGetHttpPath(this Sentry.TransactionSamplingContext samplingContext) { } public static string? TryGetHttpRoute(this Sentry.TransactionSamplingContext samplingContext) { } } - public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryLoggingOptions + public class SentryAspNetCoreOptions : Sentry.Extensions.Logging.SentryHostOptions { public SentryAspNetCoreOptions() { } public bool AdjustStandardEnvironmentNameCasing { get; set; } diff --git a/test/Sentry.AspNetCore.Tests/AspNetCoreSentryWebHostBuilder.IntegrationTests.cs b/test/Sentry.AspNetCore.Tests/AspNetCoreSentryWebHostBuilder.IntegrationTests.cs index 455bf8c0a2..3c38c2471c 100644 --- a/test/Sentry.AspNetCore.Tests/AspNetCoreSentryWebHostBuilder.IntegrationTests.cs +++ b/test/Sentry.AspNetCore.Tests/AspNetCoreSentryWebHostBuilder.IntegrationTests.cs @@ -46,13 +46,4 @@ public void UseSentry_DisableDsnString_DisabledSdk() Assert.False(SentrySdk.IsEnabled); } - - [Fact] - public void UseSentry_OptionsNotInitializeSdk_DisabledSdk() - { - _ = _webHostBuilder.UseSentry(o => o.InitializeSdk = false) - .Build(); - - Assert.False(SentrySdk.IsEnabled); - } } diff --git a/test/Sentry.AspNetCore.Tests/IntegrationMockedBackgroundWorker.cs b/test/Sentry.AspNetCore.Tests/IntegrationMockedBackgroundWorker.cs index 4869455bf4..a438fbf741 100644 --- a/test/Sentry.AspNetCore.Tests/IntegrationMockedBackgroundWorker.cs +++ b/test/Sentry.AspNetCore.Tests/IntegrationMockedBackgroundWorker.cs @@ -37,7 +37,11 @@ public IntegrationMockedBackgroundWorker(ITestOutputHelper output) [Fact] public async Task DisabledSdk_UnhandledException_NoEventCaptured() { - Configure = o => o.InitializeSdk = false; + Configure = o => + { + o.FakeSettings(); + o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; + }; Build(); _ = await HttpClient.GetAsync("/throw"); @@ -49,7 +53,11 @@ public async Task DisabledSdk_UnhandledException_NoEventCaptured() [Fact] public void DisabledSdk_WithLogger_NoEventCaptured() { - Configure = o => o.InitializeSdk = false; + Configure = o => + { + o.FakeSettings(); + o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; + }; Build(); var logger = ServiceProvider.GetRequiredService>(); @@ -262,7 +270,6 @@ public void AllSettingsViaJson() Assert.True(options.IncludeActivityData); Assert.Equal(LogLevel.Error, options.MinimumBreadcrumbLevel); Assert.Equal(LogLevel.Critical, options.MinimumEventLevel); - Assert.False(options.InitializeSdk); Assert.Equal(999, options.MaxBreadcrumbs); Assert.Equal(1, options.SampleRate); Assert.Equal("7f5d9a1", options.Release); diff --git a/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs b/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs index d869be9ff9..f379adfe22 100644 --- a/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs +++ b/test/Sentry.AspNetCore.Tests/MiddlewareLoggerIntegration.cs @@ -35,11 +35,7 @@ private class Fixture : IDisposable public Fixture() { HubAccessor = () => Hub; - var loggingOptions = new SentryLoggingOptions - { - InitializeSdk = false, - }; - loggingOptions.InitializeSdk = false; + var loggingOptions = new SentryLoggingOptions(); Client.When(client => client.CaptureEvent(Arg.Any(), Arg.Any(), Arg.Any())) .Do(callback => callback.Arg().Evaluate()); @@ -48,7 +44,7 @@ public Fixture() hub.BindClient(Client); Hub = hub; var provider = new SentryLoggerProvider(hub, new MockClock(), loggingOptions); - _disposable = provider; + _disposable = hub; SentryLogger = provider.CreateLogger(nameof(SentryLogger)); _ = HttpContext.Features.Returns(FeatureCollection); } diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs index 916b2a615d..886396d63a 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreOptionsSetupTests.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Configuration; using Sentry.Internal; #if NETCOREAPP3_1_OR_GREATER @@ -18,14 +17,7 @@ private class Fixture public Dictionary Configuration { get; set; } = new(); public SentryAspNetCoreOptionsSetup GetSut() - { - var config = new ConfigurationBuilder() - .AddInMemoryCollection(Configuration) - .Build(); - var loggingConfig = Substitute.For>(); - loggingConfig.Configuration.Returns(config); - return new(loggingConfig); - } + => new(new ConfigurationBuilder().AddInMemoryCollection(Configuration).Build()); } private readonly Fixture _fixture = new(); @@ -49,7 +41,7 @@ public void Filters_KestrelApplicationEvent_NoException_Filtered() sut.Configure(_target); //Assert - Assert.Contains(_target.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Critical, 13, null)); + Assert.Contains(_target.Logging.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Critical, 13, null)); } [Fact] @@ -62,7 +54,7 @@ public void Filters_KestrelApplicationEvent_WithException_Filtered() sut.Configure(_target); // Assert - Assert.Contains(_target.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Critical, 13, new Exception())); + Assert.Contains(_target.Logging.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Critical, 13, new Exception())); } [Fact] @@ -75,7 +67,7 @@ public void Filters_KestrelEventId1_WithException_NotFiltered() sut.Configure(_target); // Assert - Assert.DoesNotContain(_target.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Trace, 1, null)); + Assert.DoesNotContain(_target.Logging.Filters, f => f.Filter("Microsoft.AspNetCore.Server.Kestrel", LogLevel.Trace, 1, null)); } [Theory] diff --git a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs index 18d3092333..5ce57761c3 100644 --- a/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryAspNetCoreStructuredLoggerProviderTests.cs @@ -6,21 +6,18 @@ namespace Sentry.AspNetCore.Tests; -public class SentryAspNetCoreStructuredLoggerProviderTests +public class SentryAspNetCoreStructuredLoggerProviderTests : IDisposable { private class Fixture { - public IOptions Options { get; } public IHub Hub { get; } public MockClock Clock { get; } public SdkVersion Sdk { get; } public Fixture() { - var loggingOptions = new SentryAspNetCoreOptions(); - - Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); Hub = Substitute.For(); + SentryClientExtensions.SentryOptionsForTestingOnly = new SentryOptions(); Clock = new MockClock(); Sdk = new SdkVersion { @@ -33,12 +30,14 @@ public Fixture() public SentryAspNetCoreStructuredLoggerProvider GetSut() { - return new SentryAspNetCoreStructuredLoggerProvider(Options.Value, Hub, Clock, Sdk); + return new SentryAspNetCoreStructuredLoggerProvider(Hub, Clock, Sdk); } } private readonly Fixture _fixture = new(); + public void Dispose() => SentryClientExtensions.SentryOptionsForTestingOnly = null; + [Fact] public void Type_CustomAttributes_HasProviderAliasAttribute() { @@ -55,7 +54,6 @@ public void Ctor_DependencyInjection_CanCreate() using var services = new ServiceCollection() .AddLogging() .AddSingleton() - .AddSingleton(_fixture.Options) .AddSingleton(_fixture.Hub) .BuildServiceProvider(); @@ -84,7 +82,6 @@ public void CreateLogger_DependencyInjection_CanLog() using var services = new ServiceCollection() .AddLogging() .AddSingleton() - .AddSingleton(_fixture.Options) .AddSingleton(_fixture.Hub) .BuildServiceProvider(); diff --git a/test/Sentry.AspNetCore.Tests/SentryConfigurationSectionTests.cs b/test/Sentry.AspNetCore.Tests/SentryConfigurationSectionTests.cs new file mode 100644 index 0000000000..fd0fbb4814 --- /dev/null +++ b/test/Sentry.AspNetCore.Tests/SentryConfigurationSectionTests.cs @@ -0,0 +1,93 @@ +#if NET6_0_OR_GREATER +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Configuration; +using Microsoft.Extensions.Options; +using Sentry.Internal; + +namespace Sentry.AspNetCore.Tests; + +[Collection(nameof(SentrySdkCollection))] +public class SentryConfigurationSectionTests : IDisposable +{ + public void Dispose() => SentrySdk.Close(); + + private static SentryAspNetCoreOptions BuildOptions(params (string Key, string Value)[] settings) + => BuildOptions(null, settings); + + private static SentryAspNetCoreOptions BuildOptions( + Action configure, + params (string Key, string Value)[] settings) + { + var builder = WebApplication.CreateBuilder(); + ((IConfigurationBuilder)builder.Configuration).AddInMemoryCollection( + settings.ToDictionary(s => s.Key, s => s.Value)); + configure?.Invoke(builder); + builder.WebHost.UseSentry((SentryAspNetCoreOptions options) => + { + options.Dsn = ValidDsn; + options.BackgroundWorker = Substitute.For(); + options.AutoSessionTracking = false; + options.InitNativeSdks = false; + }); + + using var app = builder.Build(); + return app.Services.GetRequiredService>().Value; + } + + [Fact] + public void SentrySection_ConfiguresSdkAndLoggingSettings() + { + var options = BuildOptions( + ("Sentry:Release", "1.0.0"), + ("Sentry:MinimumEventLevel", "Critical")); + + Assert.Equal("1.0.0", options.Release); + Assert.Equal(LogLevel.Critical, options.MinimumEventLevel); + } + + [Fact] + public void LoggingSection_ConfiguresLoggingSettings() + { + var options = BuildOptions( + ("Logging:Sentry:MinimumEventLevel", "Critical"), + ("Logging:Sentry:MinimumBreadcrumbLevel", "Warning")); + + Assert.Equal(LogLevel.Critical, options.MinimumEventLevel); + Assert.Equal(LogLevel.Warning, options.MinimumBreadcrumbLevel); + } + + [Fact] + public void LoggingSection_SdkSetting_Throws() + { + var exception = Assert.Throws( + () => BuildOptions(("Logging:Sentry:Dsn", ValidDsn))); + + Assert.Contains("Dsn", exception.Message); + } + + [Fact] + public void LoggingAddConfiguration_WholeConfiguration_SentrySectionNotRejected() + { + var options = BuildOptions( + builder => builder.Logging.AddConfiguration(builder.Configuration), + ("Sentry:Release", "1.0.0"), + ("Logging:Sentry:MinimumEventLevel", "Critical")); + + Assert.Equal("1.0.0", options.Release); + Assert.Equal(LogLevel.Critical, options.MinimumEventLevel); + } + + [Fact] + public void SentrySection_AppliedOnce() + { + var options = BuildOptions(("Sentry:Release", "1.0.0")); + + Assert.Single(options.GetAllTransactionProcessors().OfType()); + Assert.Single(options.Logging.Filters); + } +} +#endif diff --git a/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs b/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs index 6ae6378710..1302043972 100644 --- a/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs +++ b/test/Sentry.AspNetCore.Tests/SentryWebHostBuilderExtensionsTests.cs @@ -22,6 +22,11 @@ public class SentryWebHostBuilderExtensionsTests public SentryWebHostBuilderExtensionsTests() { + // Substitutes return an empty string for every key, where configuration returns null for an absent one. + var emptySection = Substitute.For(); + emptySection[Arg.Any()].Returns((string)null); + Configuration.GetSection(Arg.Any()).Returns(emptySection); + var context = new WebHostBuilderContext { Configuration = Configuration, @@ -61,7 +66,7 @@ public void UseSentry_DisableDsnString_ServicesRegistered(Action assert) { - _ = WebHostBuilder.UseSentry(o => o.InitializeSdk = false); + _ = WebHostBuilder.UseSentry(o => o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue); assert(Services); } @@ -81,14 +86,9 @@ public static IEnumerable ExpectedServices() [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.InitializeSdk = false; + options.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; }); using var serviceProvider = Services.BuildServiceProvider(); @@ -104,7 +104,7 @@ public void UseSentry_Logging_AddLoggerFilterRules() { WebHostBuilder.UseSentry((SentryAspNetCoreOptions options) => { - options.InitializeSdk = false; + options.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; }); using var serviceProvider = Services.BuildServiceProvider(); diff --git a/test/Sentry.AspNetCore.Tests/UseSentryWithLoggingAddSentryTests.cs b/test/Sentry.AspNetCore.Tests/UseSentryWithLoggingAddSentryTests.cs new file mode 100644 index 0000000000..7006862862 --- /dev/null +++ b/test/Sentry.AspNetCore.Tests/UseSentryWithLoggingAddSentryTests.cs @@ -0,0 +1,49 @@ +#if NET6_0_OR_GREATER +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Sentry.AspNetCore.Tests; + +// Calling both UseSentry and Logging.AddSentry is redundant, but reachable. Whichever registered the hub +// accessor first used to win, so calling AddSentry first left the SDK disabled. +[Collection(nameof(SentrySdkCollection))] +public class UseSentryWithLoggingAddSentryTests : IDisposable +{ + public void Dispose() => SentrySdk.Close(); + + private static IHub BuildHub(bool useSentryFirst) + { + var builder = WebApplication.CreateBuilder(); + + void UseSentry() => builder.WebHost.UseSentry((SentryAspNetCoreOptions options) => + { + options.Dsn = ValidDsn; + options.BackgroundWorker = Substitute.For(); + options.AutoSessionTracking = false; + options.InitNativeSdks = false; + }); + + if (useSentryFirst) + { + UseSentry(); + builder.Logging.AddSentry(); + } + else + { + builder.Logging.AddSentry(); + UseSentry(); + } + + using var app = builder.Build(); + return app.Services.GetRequiredService(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void UseSentry_WithLoggingAddSentry_InitializesSdk(bool useSentryFirst) + => Assert.True(BuildHub(useSentryFirst).IsEnabled); +} +#endif diff --git a/test/Sentry.AspNetCore.Tests/allsettings.json b/test/Sentry.AspNetCore.Tests/allsettings.json index 427091798c..6edd4090f7 100644 --- a/test/Sentry.AspNetCore.Tests/allsettings.json +++ b/test/Sentry.AspNetCore.Tests/allsettings.json @@ -6,7 +6,6 @@ "IncludeActivityData": true, "MinimumBreadcrumbLevel": "Error", "MinimumEventLevel": "Critical", - "InitializeSdk": "false", "MaxBreadcrumbs": "999", "SampleRate": 1.0, "Release": "7f5d9a1", diff --git a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs index b9f4250999..20fa73ed45 100644 --- a/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs +++ b/test/Sentry.DiagnosticSource.IntegrationTests/SqlListenerTests.verify.cs @@ -62,7 +62,7 @@ public async Task LoggingAsync() Skip.If(!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)); var transport = new RecordingTransport(); - void ApplyOptions(SentryLoggingOptions sentryOptions) + void ApplyOptions(SentryOptions sentryOptions) { sentryOptions.AttachStacktrace = false; sentryOptions.TracesSampleRate = 1; @@ -72,7 +72,7 @@ void ApplyOptions(SentryLoggingOptions sentryOptions) sentryOptions.Debug = true; } - var options = new SentryLoggingOptions(); + var options = new SentryOptions(); ApplyOptions(options); await using var database = await _fixture.SqlInstance.Build(); @@ -87,7 +87,7 @@ void ApplyOptions(SentryLoggingOptions sentryOptions) await dbContext.SaveChangesAsync(); } - var loggerFactory = LoggerFactory.Create(_ => _.AddSentry(ApplyOptions)); + var loggerFactory = LoggerFactory.Create(_ => _.AddSentry()); using (var hub = new Hub(options)) { var transaction = hub.StartTransaction("my transaction", "my operation"); 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 8650a342f4..13bc331a27 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 @@ -4,6 +4,7 @@ { public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { } public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action? optionsConfiguration) { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string dsn) { } } public static class SentryLoggerFactoryExtensions @@ -28,16 +29,27 @@ 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) { } } - public class SentryLoggingOptions : Sentry.SentryOptions + public abstract class SentryHostOptions : Sentry.SentryOptions + { + protected SentryHostOptions() { } + public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } + public void ConfigureScope(System.Action action) { } + } + public class SentryLoggingOptions { public SentryLoggingOptions() { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] + public string? Dsn { get; set; } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] 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 static class SentryLoggingOptionsExtensions { + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, System.Func filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, System.Func filter) { } } @@ -47,6 +59,6 @@ namespace Sentry.Extensions.Logging.Extensions.DependencyInjection public static class ServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) - where TOptions : Sentry.Extensions.Logging.SentryLoggingOptions, new () { } + where TOptions : Sentry.Extensions.Logging.SentryHostOptions, new () { } } } \ No newline at end of file diff --git a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt index 8650a342f4..13bc331a27 100644 --- a/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt +++ b/test/Sentry.Extensions.Logging.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt @@ -4,6 +4,7 @@ { public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { } public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action? optionsConfiguration) { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string dsn) { } } public static class SentryLoggerFactoryExtensions @@ -28,16 +29,27 @@ 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) { } } - public class SentryLoggingOptions : Sentry.SentryOptions + public abstract class SentryHostOptions : Sentry.SentryOptions + { + protected SentryHostOptions() { } + public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } + public void ConfigureScope(System.Action action) { } + } + public class SentryLoggingOptions { public SentryLoggingOptions() { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] + public string? Dsn { get; set; } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] 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 static class SentryLoggingOptionsExtensions { + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, System.Func filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, System.Func filter) { } } @@ -47,6 +59,6 @@ namespace Sentry.Extensions.Logging.Extensions.DependencyInjection public static class ServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) - where TOptions : Sentry.Extensions.Logging.SentryLoggingOptions, new () { } + where TOptions : Sentry.Extensions.Logging.SentryHostOptions, new () { } } } \ No newline at end of file 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 8650a342f4..13bc331a27 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 @@ -4,6 +4,7 @@ { public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { } public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action? optionsConfiguration) { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string dsn) { } } public static class SentryLoggerFactoryExtensions @@ -28,16 +29,27 @@ 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) { } } - public class SentryLoggingOptions : Sentry.SentryOptions + public abstract class SentryHostOptions : Sentry.SentryOptions + { + protected SentryHostOptions() { } + public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } + public void ConfigureScope(System.Action action) { } + } + public class SentryLoggingOptions { public SentryLoggingOptions() { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] + public string? Dsn { get; set; } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] 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 static class SentryLoggingOptionsExtensions { + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, System.Func filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, System.Func filter) { } } @@ -47,6 +59,6 @@ namespace Sentry.Extensions.Logging.Extensions.DependencyInjection public static class ServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) - where TOptions : Sentry.Extensions.Logging.SentryLoggingOptions, new () { } + where TOptions : Sentry.Extensions.Logging.SentryHostOptions, new () { } } } \ No newline at end of file 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 8650a342f4..13bc331a27 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 @@ -4,6 +4,7 @@ { public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { } public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action? optionsConfiguration) { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string dsn) { } } public static class SentryLoggerFactoryExtensions @@ -28,16 +29,27 @@ 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) { } } - public class SentryLoggingOptions : Sentry.SentryOptions + public abstract class SentryHostOptions : Sentry.SentryOptions + { + protected SentryHostOptions() { } + public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } + public void ConfigureScope(System.Action action) { } + } + public class SentryLoggingOptions { public SentryLoggingOptions() { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] + public string? Dsn { get; set; } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] 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 static class SentryLoggingOptionsExtensions { + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, System.Func filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, System.Func filter) { } } @@ -47,6 +59,6 @@ namespace Sentry.Extensions.Logging.Extensions.DependencyInjection public static class ServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) - where TOptions : Sentry.Extensions.Logging.SentryLoggingOptions, new () { } + where TOptions : Sentry.Extensions.Logging.SentryHostOptions, new () { } } } \ No newline at end of file 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 8650a342f4..13bc331a27 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 @@ -4,6 +4,7 @@ { public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder) { } public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, System.Action? optionsConfiguration) { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] public static Microsoft.Extensions.Logging.ILoggingBuilder AddSentry(this Microsoft.Extensions.Logging.ILoggingBuilder builder, string dsn) { } } public static class SentryLoggerFactoryExtensions @@ -28,16 +29,27 @@ 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) { } } - public class SentryLoggingOptions : Sentry.SentryOptions + public abstract class SentryHostOptions : Sentry.SentryOptions + { + protected SentryHostOptions() { } + public Microsoft.Extensions.Logging.LogLevel MinimumBreadcrumbLevel { get; set; } + public Microsoft.Extensions.Logging.LogLevel MinimumEventLevel { get; set; } + public void ConfigureScope(System.Action action) { } + } + public class SentryLoggingOptions { public SentryLoggingOptions() { } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] + public string? Dsn { get; set; } + [System.Obsolete(@"The Microsoft.Extensions.Logging integration no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn', 'InitializeSdk' and any other core SDK settings from the logging configuration.", true)] 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 static class SentryLoggingOptionsExtensions { + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } + public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryHostOptions options, System.Func filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, Sentry.Extensions.Logging.ILogEntryFilter filter) { } public static void AddLogEntryFilter(this Sentry.Extensions.Logging.SentryLoggingOptions options, System.Func filter) { } } @@ -47,6 +59,6 @@ namespace Sentry.Extensions.Logging.Extensions.DependencyInjection public static class ServiceCollectionExtensions { public static Microsoft.Extensions.DependencyInjection.IServiceCollection AddSentry(this Microsoft.Extensions.DependencyInjection.IServiceCollection services) - where TOptions : Sentry.Extensions.Logging.SentryLoggingOptions, new () { } + where TOptions : Sentry.Extensions.Logging.SentryHostOptions, new () { } } } \ No newline at end of file diff --git a/test/Sentry.Extensions.Logging.Tests/ConfigurationOptionsTests.cs b/test/Sentry.Extensions.Logging.Tests/ConfigurationOptionsTests.cs index 2c66765548..4a6fcc815e 100644 --- a/test/Sentry.Extensions.Logging.Tests/ConfigurationOptionsTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/ConfigurationOptionsTests.cs @@ -29,12 +29,7 @@ public IServiceProvider GetSut() { var configuration = Builder.Build(); var services = new ServiceCollection(); - _ = services.AddLogging(builder => builder.AddConfiguration(configuration).AddSentry(o => - { - o.BackgroundWorker = Substitute.For(); - o.InitNativeSdks = false; - o.AutoSessionTracking = false; - })); + _ = services.AddLogging(builder => builder.AddConfiguration(configuration).AddSentry()); return services.BuildServiceProvider(); } } @@ -49,49 +44,11 @@ public void SentryLoggingOptions_ValuesFromAppSettings() using (new AssertionScope()) { - sentryLoggingOptions.InitializeSdk.Should().BeFalse(); sentryLoggingOptions.MinimumBreadcrumbLevel.Should().Be(LogLevel.Warning); sentryLoggingOptions.MinimumEventLevel.Should().Be(LogLevel.Critical); } } - [Fact] - public void SentryOptions_InitializeTrue_ValuesAppliedFromLoggingOptions() - { - var dict = new Dictionary - { - {"Sentry:InitializeSdk", "true"}, - }; - - _ = _fixture.Builder.AddInMemoryCollection(dict); - - var provider = _fixture.GetSut(); - var sentryLoggingOptions = provider.GetRequiredService>().Value; - - Assert.Equal(150, sentryLoggingOptions.MaxBreadcrumbs); - Assert.Equal("e386dfd", sentryLoggingOptions.Release); - Assert.Equal(ValidDsn, sentryLoggingOptions.Dsn); - } - - [Fact] - public void SentryOptions_DefaultTags_ValuesApplied() - { - const string expectedKey = "expected_key"; - const string expectedValue = "expected value"; - var dict = new Dictionary - { - {"Sentry:DefaultTags:" + expectedKey, expectedValue}, - }; - - _ = _fixture.Builder.AddInMemoryCollection(dict); - - var provider = _fixture.GetSut(); - var sentryLoggingOptions = provider.GetRequiredService>().Value; - - sentryLoggingOptions.DefaultTags.Should().ContainKey(expectedKey); - sentryLoggingOptions.DefaultTags[expectedKey].Should().Be(expectedValue); - } - [Fact] public void SentryLoggerProvider_ResolvedFromILoggerProvider() { diff --git a/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs b/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs index 8f9546b8d8..25166aad29 100644 --- a/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/LoggingBuilderExtensionsTests.cs @@ -11,10 +11,7 @@ public void AddSentry_LoggingBuilder_AddLoggerProviders() { // Arrange var serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging((ILoggingBuilder builder) => builder.AddSentry(options => - { - options.InitializeSdk = false; - })); + serviceCollection.AddLogging((ILoggingBuilder builder) => builder.AddSentry()); using var serviceProvider = serviceCollection.BuildServiceProvider(); using var loggerFactory = serviceProvider.GetRequiredService(); @@ -32,10 +29,7 @@ public void AddSentry_LoggingBuilder_AddLoggerFilterRules() { // Arrange var serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging((ILoggingBuilder builder) => builder.AddSentry(options => - { - options.InitializeSdk = false; - })); + serviceCollection.AddLogging((ILoggingBuilder builder) => builder.AddSentry()); using var serviceProvider = serviceCollection.BuildServiceProvider(); using var loggerFactory = serviceProvider.GetRequiredService(); diff --git a/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs b/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs index 4e4dd049da..7bf40d2d33 100644 --- a/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/LoggingTests.cs @@ -24,14 +24,18 @@ public void Log_CapturesEvent(LogLevel logLevel) // Arrange var worker = Substitute.For(); + using var sentry = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.BackgroundWorker = worker; + o.InitNativeSdks = false; + }); + var serviceCollection = new ServiceCollection(); serviceCollection.AddLogging(builder => builder.AddSentry(o => { - o.Dsn = ValidDsn; o.MinimumBreadcrumbLevel = LogLevel.None; o.MinimumEventLevel = logLevel; - o.BackgroundWorker = worker; - o.InitNativeSdks = false; })); serviceCollection.Configure(options => options.AddFilter(CategoryName, LogLevel.None)); using var serviceProvider = serviceCollection.BuildServiceProvider(); @@ -70,14 +74,18 @@ public void Log_AddsBreadcrumb(LogLevel logLevel) // Arrange var worker = Substitute.For(); + using var sentry = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.BackgroundWorker = worker; + o.InitNativeSdks = false; + }); + var serviceCollection = new ServiceCollection(); serviceCollection.AddLogging(builder => builder.AddSentry(o => { - o.Dsn = ValidDsn; o.MinimumBreadcrumbLevel = logLevel; o.MinimumEventLevel = LogLevel.None; - o.BackgroundWorker = worker; - o.InitNativeSdks = false; })); serviceCollection.Configure(options => options.AddFilter(CategoryName, LogLevel.None)); using var serviceProvider = serviceCollection.BuildServiceProvider(); @@ -121,14 +129,18 @@ public void Log_CapturesStructuredLog(LogLevel logLevel) // Arrange var worker = Substitute.For(); + using var sentry = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.BackgroundWorker = worker; + o.InitNativeSdks = false; + }); + 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; })); serviceCollection.Configure(options => options.AddFilter(CategoryName, logLevel)); using var serviceProvider = serviceCollection.BuildServiceProvider(); @@ -164,14 +176,18 @@ public void Log_EventsAndBreadcrumbsIgnoreConfiguration_StructuredLogsRespectCon var envelopes = new List(2); worker.EnqueueEnvelope(Arg.Do(envelope => envelopes.Add(envelope))); + using var sentry = SentrySdk.Init(o => + { + o.Dsn = ValidDsn; + o.BackgroundWorker = worker; + o.InitNativeSdks = false; + }); + var serviceCollection = new ServiceCollection(); serviceCollection.AddLogging(builder => builder.AddSentry(o => { - o.Dsn = ValidDsn; o.MinimumBreadcrumbLevel = LogLevel.Information; o.MinimumEventLevel = LogLevel.Warning; - o.BackgroundWorker = worker; - o.InitNativeSdks = false; })); serviceCollection.Configure(options => options.AddFilter(CategoryName, LogLevel.Error)); using var serviceProvider = serviceCollection.BuildServiceProvider(); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerFactoryExtensionsTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerFactoryExtensionsTests.cs index 8937cf76e3..09191fd0e3 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggerFactoryExtensionsTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerFactoryExtensionsTests.cs @@ -5,91 +5,16 @@ namespace Sentry.Extensions.Logging.Tests; public class SentryLoggerFactoryExtensionsTests { [Fact] - public void AddSentry_ConfigureScope_HubEnabledTrue_InvokesCallback() - { - const SentryLevel expected = SentryLevel.Debug; - var sut = Substitute.For(); - var hub = Substitute.For(); - _ = hub.IsEnabled.Returns(true); - var scope = new Scope(new SentryOptions()); - hub.When(w => w.ConfigureScope(Arg.Any>())) - .Do(info => info.Arg>()(scope)); - _ = SentrySdk.UseHub(hub); - - _ = sut.AddSentry(o => - { - o.InitializeSdk = false; // use the mock above - o.ConfigureScope(s => s.Level = expected); - }); - - Assert.Equal(expected, scope.Level); - } - - [Fact] - public void AddSentry_ConfigureScope_HubEnabledFalse_DoesNotInvokesCallback() - { - const SentryLevel expected = SentryLevel.Debug; - var sut = Substitute.For(); - var hub = Substitute.For(); - _ = hub.IsEnabled.Returns(false); - var scope = new Scope(new SentryOptions()); - hub.When(w => w.ConfigureScope(Arg.Any>())) - .Do(info => info.Arg>()(scope)); - _ = SentrySdk.UseHub(hub); - - _ = sut.AddSentry(o => - { - o.InitializeSdk = false; // use the mock above - o.ConfigureScope(s => s.Level = expected); - }); - - Assert.NotEqual(expected, scope.Level); - } - - [Fact] - public void AddSentry_InitializeSdkFalse_HubAdapter() + public void AddSentry_ProviderUsesHubAdapter() { var sut = Substitute.For(); - _ = sut.AddSentry(o => o.InitializeSdk = false); + _ = sut.AddSentry(); sut.Received(1) .AddProvider(Arg.Is(p => p.Hub == HubAdapter.Instance)); } - [Fact] - public void AddSentry_NoDiagnosticSet_MelSet() - { - SentryLoggingOptions options = null; - var sut = Substitute.For(); - _ = sut.AddSentry(o => - { - o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; - o.Debug = true; - options = o; - }); - - _ = Assert.IsType(options.DiagnosticLogger); - } - - [Fact] - public void AddSentry_DiagnosticSet_NoOverriden() - { - SentryLoggingOptions options = null; - var sut = Substitute.For(); - var diagnosticLogger = Substitute.For(); - _ = sut.AddSentry(o => - { - o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; - o.Debug = true; - Assert.Null(o.DiagnosticLogger); - o.DiagnosticLogger = diagnosticLogger; - options = o; - }); - - Assert.Same(diagnosticLogger, options.DiagnosticLogger); - } - [Fact] public void AddSentry_WithOptionsCallback_CallbackInvoked() { @@ -97,7 +22,7 @@ public void AddSentry_WithOptionsCallback_CallbackInvoked() var expected = Substitute.For(); _ = expected.AddSentry(o => { - o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; + o.MinimumEventLevel = LogLevel.Critical; callbackInvoked = true; }); @@ -118,7 +43,7 @@ public void AddSentry_NoOptionsDelegate_ProviderAdded() public void AddSentry_ReturnsSameFactory() { var expected = Substitute.For(); - var actual = expected.AddSentry(o => o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue); + var actual = expected.AddSentry(o => o.MinimumEventLevel = LogLevel.Critical); Assert.Same(expected, actual); } @@ -127,7 +52,7 @@ public void AddSentry_ReturnsSameFactory() public void AddSentry_ConfigureOptionsOverload_ReturnsSameFactory() { var expected = Substitute.For(); - var actual = expected.AddSentry(o => o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue); + var actual = expected.AddSentry(o => o.MinimumEventLevel = LogLevel.Critical); Assert.Same(expected, actual); } @@ -140,7 +65,7 @@ public void AddSentry_ConfigureOptionsOverload_InvokesCallback() var invoked = false; _ = expected.AddSentry(o => { - o.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; + o.MinimumEventLevel = LogLevel.Critical; Assert.NotNull(o); invoked = true; }); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs index db4bb02e73..99122fe37c 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerProviderTests.cs @@ -42,55 +42,4 @@ public void CreateLogger_Category_AsProvided() Assert.Equal(expectedCategory, actual.CategoryName); } - - [Fact] - public void Ctor_DisabledHub_DoesNotCreatesScope() - { - _ = _fixture.Hub.IsEnabled.Returns(false); - _ = _fixture.GetSut(); - _ = _fixture.Hub.DidNotReceive().PushScope(); - } - - [Fact] - public void Ctor_EnabledHub_CreatesScope() - { - _ = _fixture.Hub.IsEnabled.Returns(true); - _ = _fixture.GetSut(); - _ = _fixture.Hub.Received(1).PushScope(); - } - - [Fact] - public void Dispose_DisposesNewScope() - { - _ = _fixture.Hub.IsEnabled.Returns(true); - var disposable = Substitute.For(); - _ = _fixture.Hub.PushScope().Returns(disposable); - - var sut = _fixture.GetSut(); - - sut.Dispose(); - - disposable.Received(1).Dispose(); - } - - [Fact] - public void NameAndVersion_Name_NotNull() => Assert.NotNull(SentryLoggerProvider.NameAndVersion.Name); - - [Fact] - public void NameAndVersion_Version_NotNull() => Assert.NotNull(SentryLoggerProvider.NameAndVersion.Version); - - [Fact] - public void Ctor_ScopeSdk_ContainNameAndVersion() - { - _ = _fixture.Hub.IsEnabled.Returns(true); - var scope = new Scope(new SentryOptions()); - - _fixture.Hub.When(w => w.ConfigureScope(Arg.Any>())) - .Do(info => info.Arg>()(scope)); - - _ = _fixture.GetSut(); - - Assert.Equal(Constants.SdkName, scope.Sdk.Name); - Assert.Equal(SentryLoggerProvider.NameAndVersion.Version, scope.Sdk.Version); - } } diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs index 58fd48ab14..6fb19dfc04 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerTests.cs @@ -9,12 +9,14 @@ private class Fixture public string CategoryName { get; set; } = "SomeApp"; public IHub Hub { get; set; } = Substitute.For(); public SentryLoggingOptions Options { get; set; } = new(); + public SentryOptions SentryOptions { get; } = new(); public Scope Scope { get; } = new(new SentryOptions()); public Fixture() { _ = Hub.IsEnabled.Returns(true); Hub.SubstituteConfigureScope(Scope); + SentryClientExtensions.SentryOptionsForTestingOnly = SentryOptions; } public SentryLogger GetSut() => new(CategoryName, Options, new MockClock(), Hub); @@ -238,14 +240,14 @@ public void LogCritical_FilterThrows_DoesNotCaptureEventAndLogsError() { var exception = new InvalidOperationException("filter failed"); _fixture.Options.AddLogEntryFilter((_, _, _, _) => throw exception); - _fixture.Options.AddDiagnosticLoggerSubstitute(); + _fixture.SentryOptions.AddDiagnosticLoggerSubstitute(); var sut = _fixture.GetSut(); sut.LogCritical("message"); _ = _fixture.Hub.DidNotReceive().CaptureEvent(Arg.Any()); - _fixture.Options.ReceivedLogError(exception, + _fixture.SentryOptions.ReceivedLogError(exception, "The {0} log filter callback failed. The log entry will be filtered out.", nameof(DelegateLogEntryFilter)); } @@ -254,7 +256,7 @@ public void LogCritical_FilterThrows_DoesNotCaptureEventAndLogsError() public void LogCritical_FilterThrows_DoesNotAddBreadcrumb() { _fixture.Options.AddLogEntryFilter((_, _, _, _) => throw new InvalidOperationException("filter failed")); - _fixture.Options.AddDiagnosticLoggerSubstitute(); + _fixture.SentryOptions.AddDiagnosticLoggerSubstitute(); var sut = _fixture.GetSut(); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggerUninitializedSdkTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggerUninitializedSdkTests.cs new file mode 100644 index 0000000000..8261693b48 --- /dev/null +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggerUninitializedSdkTests.cs @@ -0,0 +1,94 @@ +using Microsoft.Extensions.Logging; + +namespace Sentry.Extensions.Logging.Tests; + +public class SentryLoggerUninitializedSdkTests +{ + private readonly List _standardError = []; + + private SentryLogger GetSut(bool sdkEnabled, string dsn = ValidDsn, SentryLoggingOptions options = null) + { + var hub = Substitute.For(); + hub.IsEnabled.Returns(sdkEnabled); + hub.Logger.Returns(new InMemorySentryStructuredLogger()); + + var warning = new UninitializedSdkWarning + { + DsnLocator = () => dsn, + WriteToStandardError = _standardError.Add + }; + + return new SentryLogger("SomeApp", options ?? new SentryLoggingOptions(), new MockClock(), hub, warning); + } + + [Fact] + public void Log_SdkNotInitialized_WarnsToStandardError() + { + GetSut(sdkEnabled: false).LogError("message"); + + Assert.Equal(new[] { SentryLogger.UninitializedSdkMessage }, _standardError); + } + + [Fact] + public void Log_SdkNotInitialized_WarnsOnceForManyEvents() + { + var sut = GetSut(sdkEnabled: false); + + sut.LogError("first"); + sut.LogCritical("second"); + + Assert.Single(_standardError); + } + + [Fact] + public void Log_SdkNotInitializedAndBelowMinimumEventLevel_DoesNotWarn() + { + GetSut(sdkEnabled: false).LogWarning("message"); + + Assert.Empty(_standardError); + } + + [Fact] + public void Log_SdkNotInitializedAndEventsDisabled_DoesNotWarn() + { + var options = new SentryLoggingOptions { MinimumEventLevel = LogLevel.None }; + + GetSut(sdkEnabled: false, options: options).LogCritical("message"); + + Assert.Empty(_standardError); + } + + [Fact] + public void Log_SdkNotInitializedAndNoDsnFound_DoesNotWarn() + { + GetSut(sdkEnabled: false, dsn: null).LogError("message"); + + Assert.Empty(_standardError); + } + + [Fact] + public void Log_SdkInitialized_DoesNotWarn() + { + GetSut(sdkEnabled: true).LogError("message"); + + Assert.Empty(_standardError); + } + + [Fact] + public void CreateLogger_SdkNotInitialized_WarnsOnceAcrossCategories() + { + var hub = Substitute.For(); + hub.IsEnabled.Returns(false); + var warning = new UninitializedSdkWarning + { + DsnLocator = () => ValidDsn, + WriteToStandardError = _standardError.Add + }; + var provider = new SentryLoggerProvider(hub, new MockClock(), new SentryLoggingOptions(), warning); + + provider.CreateLogger("FirstCategory").LogError("first"); + provider.CreateLogger("SecondCategory").LogError("second"); + + Assert.Single(_standardError); + } +} diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggingConfigurationTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggingConfigurationTests.cs new file mode 100644 index 0000000000..cce19d9b2b --- /dev/null +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggingConfigurationTests.cs @@ -0,0 +1,83 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace Sentry.Extensions.Logging.Tests; + +public class SentryLoggingConfigurationTests +{ + private static IConfiguration Section(params (string Key, string Value)[] settings) + => new ConfigurationBuilder() + .AddInMemoryCollection(settings.ToDictionary(s => s.Key, s => s.Value)) + .Build(); + + [Fact] + public void ApplyTo_MinimumLevels_Applied() + { + var options = new SentryLoggingOptions(); + + SentryLoggingConfiguration.ApplyTo( + Section(("MinimumEventLevel", "Critical"), ("MinimumBreadcrumbLevel", "Warning")), + options); + + Assert.Equal(LogLevel.Critical, options.MinimumEventLevel); + Assert.Equal(LogLevel.Warning, options.MinimumBreadcrumbLevel); + } + + [Fact] + public void ApplyTo_LogLevelRules_Ignored() + { + var options = new SentryLoggingOptions(); + + SentryLoggingConfiguration.ApplyTo(Section(("LogLevel:Default", "Warning")), options); + + Assert.Equal(LogLevel.Error, options.MinimumEventLevel); + } + + [Fact] + public void ApplyTo_EveryLoggingOption_IsRecognised() + { + var settings = typeof(SentryLoggingOptions).GetProperties() + .Where(p => p.SetMethod?.IsPublic == true && p.GetCustomAttribute() is null) + .Select(p => (p.Name, nameof(LogLevel.Warning))) + .ToArray(); + + SentryLoggingConfiguration.ApplyTo(Section(settings), new SentryLoggingOptions()); + } + + [Fact] + public void ApplyTo_SdkSetting_ThrowsNamingIt() + { + var exception = Assert.Throws( + () => SentryLoggingConfiguration.ApplyTo(Section(("Release", "1.0.0")), new SentryLoggingOptions())); + + Assert.Contains("Release", exception.Message); + Assert.Contains("'Sentry' configuration section", exception.Message); + } + + [Fact] + public void ApplyTo_SdkSettings_ThrowsNamingThemAll() + { + var exception = Assert.Throws( + () => SentryLoggingConfiguration.ApplyTo( + Section(("Release", "1.0.0"), ("TracesSampleRate", "1.0")), + new SentryLoggingOptions())); + + Assert.Contains("Release", exception.Message); + Assert.Contains("TracesSampleRate", exception.Message); + } + + [Theory] + [InlineData("Dsn", "https://key@sentry.io/1")] + [InlineData("InitializeSdk", "true")] + public void ApplyTo_SdkInitializationSetting_ThrowsPointingAtInit(string key, string value) + { + var exception = Assert.Throws( + () => SentryLoggingConfiguration.ApplyTo(Section((key, value)), new SentryLoggingOptions())); + + Assert.Contains("SentrySdk.Init", exception.Message); + } + + [Fact] + public void ApplyTo_InitializeSdkFalse_Accepted() + => SentryLoggingConfiguration.ApplyTo(Section(("InitializeSdk", "false")), new SentryLoggingOptions()); +} diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsConfigurationBindingTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsConfigurationBindingTests.cs new file mode 100644 index 0000000000..44513f9844 --- /dev/null +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsConfigurationBindingTests.cs @@ -0,0 +1,105 @@ +#nullable enable + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Sentry.Extensions.Logging.Tests; + +public class SentryLoggingOptionsConfigurationBindingTests +{ + private static Exception? BindConfiguration(string key, string value) + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { [key] = value }) + .Build(); + + var services = new ServiceCollection(); + services.AddLogging(builder => builder.AddConfiguration(config).AddSentry()); + using var provider = services.BuildServiceProvider(); + + return Record.Exception(() => provider.GetRequiredService>().Value); + } + + [Theory] + [InlineData("Sentry:Dsn", "https://key@sentry.io/1")] + [InlineData("Sentry:InitializeSdk", "true")] + [InlineData("Sentry:Release", "1.0.0")] + public void BindConfiguration_WithSdkSetting_Throws(string key, string value) + { + var exception = BindConfiguration(key, value); + + Assert.NotNull(exception); + Assert.Contains("SentrySdk.Init", exception.ToString()); + } + + [Theory] + [InlineData("Sentry:MinimumEventLevel", nameof(LogLevel.Warning))] + // Asking not to initialize is what now always happens, so it is not an error. + [InlineData("Sentry:InitializeSdk", "false")] + public void BindConfiguration_WithoutSdkSetting_DoesNotThrow(string key, string value) + => Assert.Null(BindConfiguration(key, value)); + + [Fact] + public void BindOptionsDirectly_WithLoggingSetting_DoesNotThrow() + { + // The configuration binder writes each property's value back, so the tombstones must tolerate their own. + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["MinimumEventLevel"] = nameof(LogLevel.Warning) }) + .Build(); + var options = new SentryLoggingOptions(); + + Assert.Null(Record.Exception(() => config.Bind(options))); + Assert.Equal(LogLevel.Warning, options.MinimumEventLevel); + } + + [Theory] + [InlineData("Dsn", "https://key@sentry.io/1")] + [InlineData("InitializeSdk", true)] + public void SetProperty_SdkSetting_Throws(string propertyName, object value) + { + var property = typeof(SentryLoggingOptions).GetProperty(propertyName)!; + + var exception = Assert.Throws( + () => property.SetValue(new SentryLoggingOptions(), value)); + + Assert.IsType(exception.InnerException); + Assert.Contains("SentrySdk.Init", exception.InnerException!.Message); + } + + private static MethodInfo DsnOverload => typeof(SentryLoggingOptions).Assembly + .GetType("Microsoft.Extensions.Logging.LoggingBuilderExtensions")! + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == "AddSentry" && m.GetParameters().Any(p => p.Name == "dsn")); + + [Fact] + public void AddSentry_DsnOverload_InvokedByName_Throws() + { + var exception = Assert.Throws( + () => { DsnOverload.Invoke(null, [Substitute.For(), "https://key@sentry.io/1"]); }); + + Assert.IsType(exception.InnerException); + Assert.Contains("SentrySdk.Init", exception.InnerException!.Message); + } + + [Theory] + [InlineData("Dsn")] + [InlineData("InitializeSdk")] + public void SdkSettings_AreObsoleteAsError(string propertyName) + { + var obsolete = typeof(SentryLoggingOptions).GetProperty(propertyName)!.GetCustomAttribute(); + + Assert.NotNull(obsolete); + Assert.True(obsolete!.IsError); + } + + [Fact] + public void AddSentry_DsnOverload_IsObsoleteAsError() + { + var obsolete = DsnOverload.GetCustomAttribute(); + + Assert.NotNull(obsolete); + Assert.True(obsolete!.IsError); + } +} diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsExtensionsTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsExtensionsTests.cs index 9671860170..d4cb540b6b 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsExtensionsTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsExtensionsTests.cs @@ -1,33 +1,53 @@ +using Microsoft.Extensions.Logging; + namespace Sentry.Extensions.Logging.Tests; public class SentryLoggingOptionsExtensionsTests { - private readonly SentryLoggingOptions _sut = new(); + private class TestHostOptions : SentryHostOptions; + + [Fact] + public void AddLogEntryFilter_LoggingOptions_AddsFilter() + { + var sut = new SentryLoggingOptions(); + var filter = Substitute.For(); + + sut.AddLogEntryFilter(filter); + + sut.Filters.Should().ContainSingle().Which.Should().BeSameAs(filter); + } [Fact] - public void ApplyDefaultTags_TagInEvent_DoesNotOverrideTag() + public void AddLogEntryFilter_HostOptions_AddsFilterToLoggingOptions() { - const string key = "key"; - const string expected = "event tag value"; - var target = new SentryEvent(); - target.SetTag(key, expected); - _sut.DefaultTags[key] = "default value"; + var sut = new TestHostOptions(); + var filter = Substitute.For(); - _sut.ApplyDefaultTags(target); + sut.AddLogEntryFilter(filter); - Assert.Equal(expected, target.Tags[key]); + sut.Logging.Filters.Should().ContainSingle().Which.Should().BeSameAs(filter); } [Fact] - public void ApplyDefaultTags_TagNotInEvent_AppliesTag() + public void AddLogEntryFilter_HostOptionsDelegate_AddsFilterToLoggingOptions() { - const string key = "key"; - const string expected = "default tag value"; - var target = new SentryEvent(); - _sut.DefaultTags[key] = expected; + var sut = new TestHostOptions(); + + sut.AddLogEntryFilter((_, _, _, _) => true); + + sut.Logging.Filters.Should().ContainSingle().Which.Should().BeOfType(); + } - _sut.ApplyDefaultTags(target); + [Fact] + public void MinimumLevels_HostOptions_PassThroughToLoggingOptions() + { + var sut = new TestHostOptions + { + MinimumBreadcrumbLevel = LogLevel.Debug, + MinimumEventLevel = LogLevel.Critical, + }; - Assert.Equal(expected, target.Tags[key]); + sut.Logging.MinimumBreadcrumbLevel.Should().Be(LogLevel.Debug); + sut.Logging.MinimumEventLevel.Should().Be(LogLevel.Critical); } } diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs index 461789e12f..1c7df8cf07 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsSetupTests.cs @@ -10,100 +10,11 @@ public class SentryLoggingOptionsSetupTests public void Configure_BindsConfigurationToOptions() { // Arrange - var expected = new SentryLoggingOptions - { - IsGlobalModeEnabled = true, - TagFilters = new List { "tag1", "tag2" }, - SendDefaultPii = true, - IsEnvironmentUser = true, - ServerName = "FakeServerName", - AttachStacktrace = true, - MaxBreadcrumbs = 7, - SampleRate = 0.7f, - Release = "FakeRelease", - Distribution = "FakeDistribution", - Environment = "Test", - Dsn = "https://d4d82fc1c2c4032a83f3a29aa3a3aff@fake-sentry.io:65535/2147483647", - MaxQueueItems = 8, - MaxCacheItems = 9, - ShutdownTimeout = TimeSpan.FromSeconds(13), - FlushTimeout = TimeSpan.FromSeconds(17), - DecompressionMethods = DecompressionMethods.GZip | DecompressionMethods.Deflate, - RequestBodyCompressionLevel = CompressionLevel.Fastest, - RequestBodyCompressionBuffered = true, - SendClientReports = true, - Debug = true, - DiagnosticLevel = SentryLevel.Warning, - ReportAssembliesMode = ReportAssembliesMode.InformationalVersion, - DeduplicateMode = DeduplicateMode.AggregateException, - CacheDirectoryPath = "~/test", - CaptureFailedRequests = true, - // FailedRequestStatusCodes = IList, - FailedRequestTargets = ["target1", "target2"], - InitCacheFlushTimeout = TimeSpan.FromSeconds(27), - // DefaultTags = Dictionary, - TracesSampleRate = 0.8f, - TracePropagationTargets = new List { "target3", "target4" }, - StackTraceMode = StackTraceMode.Enhanced, - MaxAttachmentSize = 21478, - DetectStartupTime = StartupTimeDetectionMode.Fast, - AutoSessionTrackingInterval = TimeSpan.FromHours(3), - AutoSessionTracking = true, - UseAsyncFileIO = true, - JsonPreserveReferences = true, - - MinimumBreadcrumbLevel = LogLevel.Debug, - MinimumEventLevel = LogLevel.Error, - InitializeSdk = true - }; var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { - ["IsGlobalModeEnabled"] = expected.IsGlobalModeEnabled.ToString(), - ["TagFilters:0"] = expected.TagFilters.First().ToString(), - ["TagFilters:1"] = expected.TagFilters.Last().ToString(), - ["SendDefaultPii"] = expected.SendDefaultPii.ToString(), - ["IsEnvironmentUser"] = expected.IsEnvironmentUser.ToString(), - ["ServerName"] = expected.ServerName, - ["AttachStacktrace"] = expected.AttachStacktrace.ToString(), - ["MaxBreadcrumbs"] = expected.MaxBreadcrumbs.ToString(), - ["SampleRate"] = expected.SampleRate.Value.ToString(CultureInfo.InvariantCulture), - ["Release"] = expected.Release, - ["Distribution"] = expected.Distribution, - ["Environment"] = expected.Environment, - ["Dsn"] = expected.Dsn, - ["MaxQueueItems"] = expected.MaxQueueItems.ToString(), - ["MaxCacheItems"] = expected.MaxCacheItems.ToString(), - ["ShutdownTimeout"] = expected.ShutdownTimeout.ToString(), - ["FlushTimeout"] = expected.FlushTimeout.ToString(), - ["DecompressionMethods"] = expected.DecompressionMethods.ToString(), - ["RequestBodyCompressionLevel"] = expected.RequestBodyCompressionLevel.ToString(), - ["RequestBodyCompressionBuffered"] = expected.RequestBodyCompressionBuffered.ToString(), - ["SendClientReports"] = expected.SendClientReports.ToString(), - ["Debug"] = expected.Debug.ToString(), - ["DiagnosticLevel"] = expected.DiagnosticLevel.ToString(), - ["ReportAssembliesMode"] = expected.ReportAssembliesMode.ToString(), - ["DeduplicateMode"] = expected.DeduplicateMode.ToString(), - ["CacheDirectoryPath"] = expected.CacheDirectoryPath.ToString(), - ["CaptureFailedRequests"] = expected.CaptureFailedRequests.ToString(), - ["FailedRequestStatusCodes"] = expected.FailedRequestStatusCodes.ToString(), - ["FailedRequestTargets:0"] = expected.FailedRequestTargets.First().ToString(), - ["FailedRequestTargets:1"] = expected.FailedRequestTargets.Last().ToString(), - ["InitCacheFlushTimeout"] = expected.InitCacheFlushTimeout.ToString(), - ["DefaultTags"] = expected.DefaultTags.ToString(), - ["TracesSampleRate"] = expected.TracesSampleRate.Value.ToString(CultureInfo.InvariantCulture), - ["TracePropagationTargets:0"] = expected.TracePropagationTargets.First().ToString(), - ["TracePropagationTargets:1"] = expected.TracePropagationTargets.Last().ToString(), - ["StackTraceMode"] = expected.StackTraceMode.ToString(), - ["MaxAttachmentSize"] = expected.MaxAttachmentSize.ToString(), - ["DetectStartupTime"] = expected.DetectStartupTime.ToString(), - ["AutoSessionTrackingInterval"] = expected.AutoSessionTrackingInterval.ToString(), - ["AutoSessionTracking"] = expected.AutoSessionTracking.ToString(), - ["UseAsyncFileIO"] = expected.UseAsyncFileIO.ToString(), - ["JsonPreserveReferences"] = expected.JsonPreserveReferences.ToString(), - ["MinimumBreadcrumbLevel"] = expected.MinimumBreadcrumbLevel.ToString(), - ["MinimumEventLevel"] = expected.MinimumEventLevel.ToString(), - ["InitializeSdk"] = expected.InitializeSdk.ToString(), + ["MinimumBreadcrumbLevel"] = nameof(LogLevel.Debug), + ["MinimumEventLevel"] = nameof(LogLevel.Critical), }) .Build(); @@ -119,47 +30,8 @@ public void Configure_BindsConfigurationToOptions() // Assert using (new AssertionScope()) { - actual.IsGlobalModeEnabled.Should().Be(expected.IsGlobalModeEnabled); - actual.TagFilters.Should().BeEquivalentTo(expected.TagFilters); - actual.SendDefaultPii.Should().Be(expected.SendDefaultPii); - actual.IsEnvironmentUser.Should().Be(expected.IsEnvironmentUser); - actual.ServerName.Should().Be(expected.ServerName); - actual.AttachStacktrace.Should().Be(expected.AttachStacktrace); - actual.MaxBreadcrumbs.Should().Be(expected.MaxBreadcrumbs); - actual.SampleRate.Should().Be(expected.SampleRate); - actual.Release.Should().Be(expected.Release); - actual.Distribution.Should().Be(expected.Distribution); - actual.Environment.Should().Be(expected.Environment); - actual.Dsn.Should().Be(expected.Dsn); - actual.MaxQueueItems.Should().Be(expected.MaxQueueItems); - actual.MaxCacheItems.Should().Be(expected.MaxCacheItems); - actual.ShutdownTimeout.Should().Be(expected.ShutdownTimeout); - actual.FlushTimeout.Should().Be(expected.FlushTimeout); - actual.DecompressionMethods.Should().Be(expected.DecompressionMethods); - actual.RequestBodyCompressionLevel.Should().Be(expected.RequestBodyCompressionLevel); - actual.RequestBodyCompressionBuffered.Should().Be(expected.RequestBodyCompressionBuffered); - actual.SendClientReports.Should().Be(expected.SendClientReports); - actual.Debug.Should().Be(expected.Debug); - actual.DiagnosticLevel.Should().Be(expected.DiagnosticLevel); - actual.ReportAssembliesMode.Should().Be(expected.ReportAssembliesMode); - actual.DeduplicateMode.Should().Be(expected.DeduplicateMode); - actual.CacheDirectoryPath.Should().Be(expected.CacheDirectoryPath); - actual.CaptureFailedRequests.Should().Be(expected.CaptureFailedRequests); - actual.FailedRequestTargets.Should().BeEquivalentTo(expected.FailedRequestTargets); - actual.InitCacheFlushTimeout.Should().Be(expected.InitCacheFlushTimeout); - actual.TracesSampleRate.Should().Be(expected.TracesSampleRate); - actual.TracePropagationTargets.Should().BeEquivalentTo(expected.TracePropagationTargets); - actual.StackTraceMode.Should().Be(expected.StackTraceMode); - actual.MaxAttachmentSize.Should().Be(expected.MaxAttachmentSize); - actual.DetectStartupTime.Should().Be(expected.DetectStartupTime); - actual.AutoSessionTrackingInterval.Should().Be(expected.AutoSessionTrackingInterval); - actual.AutoSessionTracking.Should().Be(expected.AutoSessionTracking); - actual.UseAsyncFileIO.Should().Be(expected.UseAsyncFileIO); - actual.JsonPreserveReferences.Should().Be(expected.JsonPreserveReferences); - - actual.MinimumBreadcrumbLevel.Should().Be(expected.MinimumBreadcrumbLevel); - actual.MinimumEventLevel.Should().Be(expected.MinimumEventLevel); - actual.InitializeSdk.Should().Be(expected.InitializeSdk); + actual.MinimumBreadcrumbLevel.Should().Be(LogLevel.Debug); + actual.MinimumEventLevel.Should().Be(LogLevel.Critical); } } } diff --git a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsTests.cs deleted file mode 100644 index c6ce409a01..0000000000 --- a/test/Sentry.Extensions.Logging.Tests/SentryLoggingOptionsTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -#if !NETFRAMEWORK -using Microsoft.Extensions.Configuration; - -namespace Sentry.Extensions.Logging.Tests; - -public class SentryLoggingOptionsTests : BindableTests -{ - [Fact] - public void BindableProperties_MatchOptionsProperties() - { - var propertyNames = GetPropertyNames(); - AssertPropertiesMatchOptions(propertyNames); - } - - [Fact] - public void ApplyTo_SetsOptionsFromConfig() - { - // Arrange - var actual = new SentryLoggingOptions(); - var bindable = new BindableSentryLoggingOptions(); - - // Act - Fixture.Config.Bind(bindable); - bindable.ApplyTo(actual); - - // Assert - AssertContainsExpectedPropertyValues(actual); - } -} -#endif - diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs index c8d797244e..1c63a1ff2a 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerProviderTests.cs @@ -6,22 +6,19 @@ namespace Sentry.Extensions.Logging.Tests; -public class SentryStructuredLoggerProviderTests +public class SentryStructuredLoggerProviderTests : IDisposable { private class Fixture { - public IOptions Options { get; } public IHub Hub { get; } public MockClock Clock { get; } public SdkVersion Sdk { get; } public Fixture() { - var loggingOptions = new SentryLoggingOptions(); - - Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); Hub = Substitute.For(); - Hub.SubstituteConfigureScope(new Scope(loggingOptions)); + SentryClientExtensions.SentryOptionsForTestingOnly = new SentryOptions(); + Hub.SubstituteConfigureScope(new Scope(new SentryOptions())); Clock = new MockClock(); Sdk = new SdkVersion { @@ -34,12 +31,14 @@ public Fixture() public SentryStructuredLoggerProvider GetSut() { - return new SentryStructuredLoggerProvider(Options.Value, Hub, Clock, Sdk); + return new SentryStructuredLoggerProvider(Hub, Clock, Sdk); } } private readonly Fixture _fixture = new(); + public void Dispose() => SentryClientExtensions.SentryOptionsForTestingOnly = null; + [Fact] public void Type_CustomAttributes_HasProviderAliasAttribute() { @@ -56,7 +55,6 @@ public void Ctor_DependencyInjection_CanCreate() using var services = new ServiceCollection() .AddLogging() .AddSingleton() - .AddSingleton(_fixture.Options) .AddSingleton(_fixture.Hub) .BuildServiceProvider(); @@ -85,7 +83,6 @@ public void CreateLogger_DependencyInjection_CanLog() using var services = new ServiceCollection() .AddLogging() .AddSingleton() - .AddSingleton(_fixture.Options) .AddSingleton(_fixture.Hub) .BuildServiceProvider(); @@ -97,10 +94,10 @@ public void CreateLogger_DependencyInjection_CanLog() categoryName.Should().Be(typeof(SentryStructuredLoggerProviderTests).FullName); capturedLog.TryGetAttribute("sentry.sdk.name", out object? name).Should().BeTrue(); - name.Should().Be(Constants.SdkName); + name.Should().Be(SdkVersion.Instance.Name); capturedLog.TryGetAttribute("sentry.sdk.version", out object? version).Should().BeTrue(); - version.Should().Be(SentryLoggerProvider.NameAndVersion.Version); + version.Should().Be(SdkVersion.Instance.Version); capturedLog.TryGetAttribute("sentry.origin", out object? origin).Should().BeTrue(); origin.Should().Be("auto.log.extensions_logging"); diff --git a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs index 0b11148cb8..c6a84e8ad6 100644 --- a/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs +++ b/test/Sentry.Extensions.Logging.Tests/SentryStructuredLoggerTests.cs @@ -10,7 +10,7 @@ public class SentryStructuredLoggerTests : IDisposable private class Fixture { public string CategoryName { get; internal set; } - public IOptions Options { get; } + public SentryOptions Options { get; } public IHub Hub { get; } public MockClock Clock { get; } public SdkVersion Sdk { get; } @@ -20,18 +20,18 @@ private class Fixture public Fixture() { - var loggingOptions = new SentryLoggingOptions + Options = new SentryOptions { Debug = true, DiagnosticLogger = DiagnosticLogger, Environment = "my-environment", Release = "my-release", }; + SentryClientExtensions.SentryOptionsForTestingOnly = Options; CategoryName = nameof(CategoryName); - Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); Hub = Substitute.For(); - Hub.SubstituteConfigureScope(new Scope(loggingOptions)); + Hub.SubstituteConfigureScope(new Scope(Options)); Clock = new MockClock(new DateTimeOffset(2025, 04, 22, 14, 51, 00, 789, TimeSpan.FromHours(2))); Sdk = new SdkVersion { @@ -58,7 +58,7 @@ public void WithActiveSpan(SentryId traceId, SpanId spanId) public SentryStructuredLogger GetSut() { - return new SentryStructuredLogger(CategoryName, Options.Value, Hub, Clock, Sdk); + return new SentryStructuredLogger(CategoryName, Hub, Clock, Sdk); } } @@ -66,6 +66,7 @@ public SentryStructuredLogger GetSut() public void Dispose() { + SentryClientExtensions.SentryOptionsForTestingOnly = null; _fixture.CapturedLogs.Should().BeEmpty(); _fixture.DiagnosticLogger.Entries.Should().BeEmpty(); } @@ -129,7 +130,7 @@ public void Log_LogLevelNone_DoesNotCaptureLog() [Fact] public void Log_WithoutActiveSpan_CaptureLog() { - var scope = new Scope(_fixture.Options.Value); + var scope = new Scope(_fixture.Options); _fixture.Hub.GetSpan().Returns((ISpan?)null); _fixture.Hub.SubstituteConfigureScope(scope); var logger = _fixture.GetSut(); diff --git a/test/Sentry.Extensions.Logging.Tests/ServiceCollectionExtensionsTests.cs b/test/Sentry.Extensions.Logging.Tests/ServiceCollectionExtensionsTests.cs new file mode 100644 index 0000000000..5b80c1d115 --- /dev/null +++ b/test/Sentry.Extensions.Logging.Tests/ServiceCollectionExtensionsTests.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.DependencyInjection; +using Sentry.Extensions.Logging.Extensions.DependencyInjection; + +namespace Sentry.Extensions.Logging.Tests; + +public class ServiceCollectionExtensionsTests : IDisposable +{ + private class TestHostOptions : SentryHostOptions; + + public void Dispose() => SentrySdk.Close(); + + private static ServiceProvider GetSut(bool initializeSdk, Action configureScope) + { + var services = new ServiceCollection(); + services.Configure(o => + { + o.Dsn = ValidDsn; + o.BackgroundWorker = Substitute.For(); + o.AutoSessionTracking = false; + o.InitNativeSdks = false; + o.ConfigureScope(configureScope); + }); + services.AddSentry(initializeSdk); + return services.BuildServiceProvider(); + } + + [Fact] + public void AddSentry_HubResolved_InitializesSdkAndAppliesConfigureScope() + { + var configured = false; + using var provider = GetSut(initializeSdk: true, _ => configured = true); + + _ = provider.GetRequiredService(); + + SentrySdk.IsEnabled.Should().BeTrue(); + configured.Should().BeTrue(); + } + + [Fact] + public void AddSentry_WithoutInitializeSdk_HubResolved_LeavesSdkAlone() + { + SentrySdk.UseHub(DisabledHub.Instance); + var configured = false; + using var provider = GetSut(initializeSdk: false, _ => configured = true); + + _ = provider.GetRequiredService(); + + SentrySdk.IsEnabled.Should().BeFalse(); + configured.Should().BeFalse(); + } +} diff --git a/test/Sentry.Extensions.Logging.Tests/appsettings.json b/test/Sentry.Extensions.Logging.Tests/appsettings.json index dc995ef4f2..42dd09734e 100644 --- a/test/Sentry.Extensions.Logging.Tests/appsettings.json +++ b/test/Sentry.Extensions.Logging.Tests/appsettings.json @@ -1,10 +1,6 @@ { "Sentry": { - "Dsn": "https://d4d82fc1c2c4032a83f3a29aa3a3aff@fake-sentry.io:65535/2147483647", - "MaxBreadcrumbs": 150, - "Release": "e386dfd", "MinimumBreadcrumbLevel": "Warning", - "MinimumEventLevel": "Critical", - "InitializeSdk": false + "MinimumEventLevel": "Critical" } } diff --git a/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs b/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs index 39c14fa641..8ac15a9b50 100644 --- a/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs +++ b/test/Sentry.Google.Cloud.Functions.Tests/SentryStartupTests.cs @@ -114,20 +114,52 @@ public static IEnumerable ExpectedServices() yield return new object[] { new Action(c => Assert.Single(c, d => d.ImplementationType == typeof(SentryAspNetCoreLoggerProvider)))}; - yield return new object[] { - new Action(c => - Assert.Single(c, d => d.ImplementationType == typeof(SentryAspNetCoreOptionsSetup)))}; yield return new object[] { new Action(c => Assert.Single(c, d => d.ImplementationType == typeof(AspNetCoreEventProcessor)))}; } + [Fact] + public void ConfigureLogging_SentrySection_BindsSdkSettings() + { + WebHostBuilderContext.Configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary { ["Sentry:Environment"] = "production" }) + .Build(); + + var sut = new SentryStartup(); + sut.ConfigureLogging(WebHostBuilderContext, LoggingBuilder); + + using var provider = LoggingBuilder.Services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + Assert.Equal("production", options.Environment); + } + + [Fact] + public void ConfigureLogging_LoggingSection_BindsLoggingSettings() + { + WebHostBuilderContext.Configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Logging:Sentry:MinimumEventLevel"] = "Critical", + ["Logging:Sentry:MinimumBreadcrumbLevel"] = "Warning", + }) + .Build(); + + var sut = new SentryStartup(); + sut.ConfigureLogging(WebHostBuilderContext, LoggingBuilder); + + using var provider = LoggingBuilder.Services.BuildServiceProvider(); + var options = provider.GetRequiredService>().Value; + Assert.Equal(LogLevel.Critical, options.MinimumEventLevel); + Assert.Equal(LogLevel.Warning, options.MinimumBreadcrumbLevel); + } + [Fact] public void ConfigureLogging_Logging_AddLoggerProviders() { LoggingBuilder.Services.Configure(options => { - options.InitializeSdk = false; + options.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; }); var sut = new SentryStartup(); @@ -146,7 +178,7 @@ public void ConfigureLogging_Logging_AddLoggerFilterRules() { LoggingBuilder.Services.Configure(options => { - options.InitializeSdk = false; + options.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; }); var sut = new SentryStartup(); diff --git a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 117659776a..269b5fe6f0 100644 --- a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -4,11 +4,10 @@ namespace Sentry.Log4Net public class SentryAppender : log4net.Appender.AppenderSkeleton { public SentryAppender() { } + [System.Obsolete(@"The Sentry appender no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn' and any other core SDK settings from the appender configuration.", true)] public string? Dsn { get; set; } - public string? Environment { get; set; } public log4net.Core.Level? MinimumEventLevel { get; set; } public bool SendIdentity { get; set; } protected override void Append(log4net.Core.LoggingEvent loggingEvent) { } - protected override void OnClose() { } } } \ No newline at end of file diff --git a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt index 117659776a..269b5fe6f0 100644 --- a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt +++ b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt @@ -4,11 +4,10 @@ namespace Sentry.Log4Net public class SentryAppender : log4net.Appender.AppenderSkeleton { public SentryAppender() { } + [System.Obsolete(@"The Sentry appender no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn' and any other core SDK settings from the appender configuration.", true)] public string? Dsn { get; set; } - public string? Environment { get; set; } public log4net.Core.Level? MinimumEventLevel { get; set; } public bool SendIdentity { get; set; } protected override void Append(log4net.Core.LoggingEvent loggingEvent) { } - protected override void OnClose() { } } } \ No newline at end of file diff --git a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 117659776a..269b5fe6f0 100644 --- a/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.Log4Net.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -4,11 +4,10 @@ namespace Sentry.Log4Net public class SentryAppender : log4net.Appender.AppenderSkeleton { public SentryAppender() { } + [System.Obsolete(@"The Sentry appender no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'Dsn' and any other core SDK settings from the appender configuration.", true)] public string? Dsn { get; set; } - public string? Environment { get; set; } public log4net.Core.Level? MinimumEventLevel { get; set; } public bool SendIdentity { get; set; } protected override void Append(log4net.Core.LoggingEvent loggingEvent) { } - protected override void OnClose() { } } } \ No newline at end of file diff --git a/test/Sentry.Log4Net.Tests/IntegrationTests.verify.cs b/test/Sentry.Log4Net.Tests/IntegrationTests.verify.cs index 6ba7959933..9675389741 100644 --- a/test/Sentry.Log4Net.Tests/IntegrationTests.verify.cs +++ b/test/Sentry.Log4Net.Tests/IntegrationTests.verify.cs @@ -104,12 +104,9 @@ private static Hierarchy SetupLogging(IHub hub) tracer.ActivateOptions(); hierarchy.Root.AddAppender(tracer); - var appender = new SentryAppender( - _ => Substitute.For(), - hub) + var appender = new SentryAppender(hub) { Layout = layout, - Dsn = ValidDsn, SendIdentity = true }; appender.ActivateOptions(); diff --git a/test/Sentry.Log4Net.Tests/SentryAppenderConfigurationBindingTests.cs b/test/Sentry.Log4Net.Tests/SentryAppenderConfigurationBindingTests.cs new file mode 100644 index 0000000000..658c3d176b --- /dev/null +++ b/test/Sentry.Log4Net.Tests/SentryAppenderConfigurationBindingTests.cs @@ -0,0 +1,93 @@ +#nullable enable + +using System.Xml; +using log4net.Config; +using log4net.Util; + +namespace Sentry.Log4Net.Tests; + +public class SentryAppenderConfigurationBindingTests +{ + private static (Exception? Exception, int AppenderCount, string Errors) LoadConfiguration(string appenderElements) + { + var configXml = $""" + + + {appenderElements} + + + + """; + + var document = new XmlDocument(); + document.LoadXml(configXml); + + var errors = new List(); + void OnLogReceived(object? sender, LogReceivedEventArgs e) + { + if (e.LogLog.Exception is { } exception) + { + errors.Add($"{e.LogLog.Message} {exception.Message}"); + } + else + { + errors.Add(e.LogLog.Message); + } + } + + LogLog.LogReceived += OnLogReceived; + try + { + var repository = LogManager.CreateRepository(Guid.NewGuid().ToString()); + var exception = Record.Exception(() => XmlConfigurator.Configure(repository, document.DocumentElement!)); + return (exception, repository.GetAppenders().Length, string.Join(" | ", errors)); + } + finally + { + LogLog.LogReceived -= OnLogReceived; + } + } + + [Fact] + public void LoadConfiguration_WithDsn_ReportsMigrationError() + { + var (exception, appenderCount, errors) = LoadConfiguration(""""""); + + // log4net catches exceptions thrown while setting a parameter, so the config still loads and the appender attaches. + Assert.Null(exception); + Assert.Equal(1, appenderCount); + Assert.Contains("SentrySdk.Init", errors); + } + + [Fact] + public void LoadConfiguration_WithAppenderSettings_ReportsNoErrors() + { + var (exception, appenderCount, errors) = LoadConfiguration( + """"""); + + Assert.Null(exception); + Assert.Equal(1, appenderCount); + Assert.Empty(errors); + } + + [Fact] + public void Dsn_WhenSet_Throws() + { + var exception = Assert.Throws( + () => DsnProperty.SetValue(new SentryAppender(), "https://key@sentry.io/1")); + + Assert.IsType(exception.InnerException); + Assert.Contains("SentrySdk.Init", exception.InnerException!.Message); + } + + [Fact] + public void Dsn_IsObsoleteAsError() + { + var obsolete = DsnProperty.GetCustomAttribute(); + + Assert.NotNull(obsolete); + Assert.True(obsolete!.IsError); + } + + private static PropertyInfo DsnProperty => typeof(SentryAppender).GetProperty("Dsn")!; +} diff --git a/test/Sentry.Log4Net.Tests/SentryAppenderTests.Structured.cs b/test/Sentry.Log4Net.Tests/SentryAppenderTests.Structured.cs index abbc4f9a2a..31c3ea0441 100644 --- a/test/Sentry.Log4Net.Tests/SentryAppenderTests.Structured.cs +++ b/test/Sentry.Log4Net.Tests/SentryAppenderTests.Structured.cs @@ -99,8 +99,8 @@ public void DoAppend_StructuredLogging_LogEvent(bool withActiveSpan) log.Attributes.ShouldContain("sentry.environment", "test-environment"); log.Attributes.ShouldContain("sentry.release", "test-release"); log.Attributes.ShouldContain("sentry.origin", "auto.log.log4net"); - log.Attributes.ShouldContain("sentry.sdk.name", SentryAppender.SdkName); - log.Attributes.ShouldContain("sentry.sdk.version", SentryAppender.NameAndVersion.Version); + log.Attributes.ShouldContain("sentry.sdk.name", SdkVersion.Instance.Name!); + log.Attributes.ShouldContain("sentry.sdk.version", SdkVersion.Instance.Version!); log.Attributes.ShouldContain("category.name", "TestLogger"); log.Attributes.ShouldContain("property.Text-Property", "4"); @@ -209,22 +209,6 @@ public void DoAppend_StructuredLoggingWithoutException_LeavesBreadcrumb() _ = _fixture.Hub.Received(0).CaptureEvent(Arg.Any()); } - [Fact] - public void DoAppend_StructuredLogging_ConfiguredEnvironment_OverridesOptions() - { - InMemorySentryStructuredLogger capturer = new(); - _fixture.Hub.Logger.Returns(capturer); - _fixture.Options.Environment = "options-environment"; - - var sut = _fixture.GetSut(); - sut.Environment = "appender-environment"; - - sut.DoAppend(CreateLoggingEvent(Level.Info, "Message")); - - var log = capturer.Logs.Should().ContainSingle().Which; - log.Attributes.ShouldContain("sentry.environment", "appender-environment"); - } - [Fact] public void DoAppend_StructuredLogging_SendIdentity_SetsUser() { diff --git a/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs b/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs index ef07207701..b64cf902ef 100644 --- a/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs +++ b/test/Sentry.Log4Net.Tests/SentryAppenderTests.cs @@ -4,36 +4,21 @@ public partial class SentryAppenderTests : IDisposable { private class Fixture { - public bool InitInvoked { get; set; } - public string DsnReceivedOnInit { get; set; } - public IDisposable SdkDisposeHandle { get; set; } = Substitute.For(); - public Func InitAction { get; set; } public IHub Hub { get; set; } = Substitute.For(); - public Func HubAccessor { get; set; } public Scope Scope { get; } = new(new SentryOptions()); - public string Dsn { get; set; } = "dsn"; public SentryOptions Options { get; } = new(); public Fixture() { - HubAccessor = () => Hub; + Hub.IsEnabled.Returns(true); Hub.SubstituteConfigureScope(Scope); - InitAction = s => - { - DsnReceivedOnInit = s; - InitInvoked = true; - return SdkDisposeHandle; - }; } public SentryAppender GetSut() { SentryClientExtensions.SentryOptionsForTestingOnly = Options; - var sut = new SentryAppender(InitAction, Hub) - { - Dsn = Dsn - }; + var sut = new SentryAppender(Hub); sut.ActivateOptions(); return sut; } @@ -63,42 +48,6 @@ public void Append_WithException_CreatesEventWithException() .CaptureEvent(Arg.Is(e => e.Exception == expected)); } - [Fact] - public void Append_Log4NetSdk_Name() - { - var sut = _fixture.GetSut(); - - var evt = new LoggingEvent(new LoggingEventData()); - - sut.DoAppend(evt); - - var expected = typeof(SentryAppender).Assembly.GetNameAndVersion(); - _ = _fixture.Hub.Received(1) - .CaptureEvent(Arg.Is(e => e.Sdk.Name == SentryAppender.SdkName - && e.Sdk.Version == expected.Version)); - } - - [Fact] - public void Append_Log4NetSdk_Packages() - { - var sut = _fixture.GetSut(); - - var evt = new LoggingEvent(new LoggingEventData()); - - SentryEvent actual = null; - _fixture.Hub.When(h => h.CaptureEvent(Arg.Any())) - .Do(c => actual = c.Arg()); - - sut.DoAppend(evt); - - var expected = typeof(SentryAppender).Assembly.GetNameAndVersion(); - - Assert.NotNull(actual); - var package = Assert.Single(actual.Sdk.Packages); - Assert.Equal("nuget:" + expected.Name, package.Name); - Assert.Equal(expected.Version, package.Version); - } - [Fact] public void Append_LoggerNameAndLevel_Set() { @@ -185,66 +134,19 @@ public void Append_ConfiguredSendUser_UserInEvent() } [Fact] - public void Append_NoDsn_InitNotCalled() + public void Append_DisabledHub_HubNotCalled() { - _fixture.Dsn = null; - var sut = _fixture.GetSut(); - - var evt = new LoggingEvent(new LoggingEventData()); - sut.DoAppend(evt); - - Assert.False(_fixture.InitInvoked); - } - - [Fact] - public void Append_WithEnabledHub_InitNotCalled() - { - _ = _fixture.Hub.IsEnabled.Returns(true); - var sut = _fixture.GetSut(); - - var evt = new LoggingEvent(new LoggingEventData()); - sut.DoAppend(evt); - - Assert.False(_fixture.InitInvoked); - } - - [Fact] - public void Append_WithDsn_InitCalled() - { - var sut = _fixture.GetSut(); - - var evt = new LoggingEvent(new LoggingEventData()); - sut.DoAppend(evt); - - Assert.True(_fixture.InitInvoked); - Assert.Same(_fixture.Dsn, _fixture.DsnReceivedOnInit); - } - - [Fact] - public void Append_NoDsn_HubNotCalled() - { - _fixture.Dsn = null; - var sut = _fixture.GetSut(); - - var evt = new LoggingEvent(new LoggingEventData()); - sut.DoAppend(evt); - - Assert.False(_fixture.InitInvoked); - _ = _fixture.Hub.DidNotReceiveWithAnyArgs().CaptureEvent(null); - } - - [Fact] - public void Append_NoDsnAndDisabledHub_HubNotCalled() - { - _fixture.Dsn = null; _ = _fixture.Hub.IsEnabled.Returns(false); + InMemorySentryStructuredLogger capturer = new(); + _fixture.Hub.Logger.Returns(capturer); var sut = _fixture.GetSut(); var evt = new LoggingEvent(new LoggingEventData()); sut.DoAppend(evt); - Assert.False(_fixture.InitInvoked); _ = _fixture.Hub.DidNotReceiveWithAnyArgs().CaptureEvent(null); + capturer.Logs.Should().BeEmpty(); + _fixture.Scope.Breadcrumbs.Should().BeEmpty(); } [Fact] @@ -284,7 +186,6 @@ public void Append_ThreadContext_AsExtra() ThreadContext.Properties[id] = expected; var sut = _fixture.GetSut(); - sut.Dsn = "dsn"; var evt = new LoggingEvent(new LoggingEventData()); sut.DoAppend(evt); @@ -293,32 +194,6 @@ public void Append_ThreadContext_AsExtra() .CaptureEvent(Arg.Is(e => e.Extra[id] == expected)); } - [Fact] - public void Append_ByDefault_DoesNotSetEnvironment() - { - var sut = _fixture.GetSut(); - var evt = new LoggingEvent(new LoggingEventData()); - - sut.DoAppend(evt); - - _ = _fixture.Hub.Received(1) - .CaptureEvent(Arg.Is(e => e.Environment == null)); - } - - [Fact] - public void Append_ConfiguredEnvironment() - { - const string expected = "dev"; - var sut = _fixture.GetSut(); - sut.Environment = expected; - var evt = new LoggingEvent(new LoggingEventData()); - - sut.DoAppend(evt); - - _ = _fixture.Hub.Received(1) - .CaptureEvent(Arg.Is(e => e.Environment == expected)); - } - [Fact] public void MinimumEventLevel_DefaultsToNull() { @@ -415,21 +290,4 @@ public void DoAppend_AboveMinimumEventLevel_AddsEvent() _ = _fixture.Hub.Received(1) .CaptureEvent(Arg.Is(e => e.Message.Message == expectedMessage)); } - - [Fact] - public void Close_DisposesSdk() - { - const string expectedDsn = "dsn"; - var sut = _fixture.GetSut(); - sut.Dsn = expectedDsn; - - var evt = new LoggingEvent(new LoggingEventData()); - sut.DoAppend(evt); - - _fixture.SdkDisposeHandle.DidNotReceive().Dispose(); - - sut.Close(); - - _fixture.SdkDisposeHandle.Received(1).Dispose(); - } } diff --git a/test/Sentry.Log4Net.Tests/SentryAppenderUninitializedSdkTests.cs b/test/Sentry.Log4Net.Tests/SentryAppenderUninitializedSdkTests.cs new file mode 100644 index 0000000000..0554774f6f --- /dev/null +++ b/test/Sentry.Log4Net.Tests/SentryAppenderUninitializedSdkTests.cs @@ -0,0 +1,78 @@ +namespace Sentry.Log4Net.Tests; + +public class SentryAppenderUninitializedSdkTests : IDisposable +{ + private readonly List _internalLog = []; + private readonly List _standardError = []; + + public void Dispose() => SentryClientExtensions.SentryOptionsForTestingOnly = null; + + private SentryAppender GetSut(bool sdkEnabled, string dsn = ValidDsn, Level minimumEventLevel = null) + { + var hub = Substitute.For(); + hub.IsEnabled.Returns(sdkEnabled); + hub.Logger.Returns(new InMemorySentryStructuredLogger()); + + var warning = new UninitializedSdkWarning(_internalLog.Add) + { + DsnLocator = () => dsn, + WriteToStandardError = _standardError.Add + }; + + var sut = new SentryAppender(hub, warning) { MinimumEventLevel = minimumEventLevel }; + sut.ActivateOptions(); + return sut; + } + + private static LoggingEvent LoggingEventAt(Level level) => + new(null, null, "logger", level, "message", null); + + [Fact] + public void Append_SdkNotInitialized_WarnsToInternalLogAndStandardError() + { + GetSut(sdkEnabled: false).DoAppend(LoggingEventAt(Level.Error)); + + Assert.Equal(new[] { SentryAppender.UninitializedSdkMessage }, _internalLog); + Assert.Equal(new[] { SentryAppender.UninitializedSdkMessage }, _standardError); + } + + [Fact] + public void Append_SdkNotInitialized_WarnsOnceForManyEvents() + { + var sut = GetSut(sdkEnabled: false); + + sut.DoAppend(LoggingEventAt(Level.Error)); + sut.DoAppend(LoggingEventAt(Level.Critical)); + + Assert.Single(_standardError); + } + + [Fact] + public void Append_SdkNotInitializedAndBelowMinimumEventLevel_DoesNotWarn() + { + var sut = GetSut(sdkEnabled: false, minimumEventLevel: Level.Error); + + sut.DoAppend(LoggingEventAt(Level.Warn)); + + Assert.Empty(_internalLog); + Assert.Empty(_standardError); + } + + [Fact] + public void Append_SdkNotInitializedAndNoDsnFound_DoesNotWarn() + { + GetSut(sdkEnabled: false, dsn: null).DoAppend(LoggingEventAt(Level.Error)); + + Assert.Empty(_internalLog); + Assert.Empty(_standardError); + } + + [Fact] + public void Append_SdkInitialized_DoesNotWarn() + { + GetSut(sdkEnabled: true).DoAppend(LoggingEventAt(Level.Error)); + + Assert.Empty(_internalLog); + Assert.Empty(_standardError); + } +} diff --git a/test/Sentry.Log4Net.V3.Tests/Sentry.Log4Net.V3.Tests.csproj b/test/Sentry.Log4Net.V3.Tests/Sentry.Log4Net.V3.Tests.csproj index 32403d528d..134606440c 100644 --- a/test/Sentry.Log4Net.V3.Tests/Sentry.Log4Net.V3.Tests.csproj +++ b/test/Sentry.Log4Net.V3.Tests/Sentry.Log4Net.V3.Tests.csproj @@ -11,6 +11,8 @@ SentryAppenderTests.cs + + diff --git a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index db0645d89c..bac8f36955 100644 --- a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -34,7 +34,7 @@ namespace Sentry.Maui void Bind(Microsoft.Maui.Controls.VisualElement element, System.Action addBreadcrumb); void UnBind(Microsoft.Maui.Controls.VisualElement element); } - public class SentryMauiOptions : Sentry.Extensions.Logging.SentryLoggingOptions + public class SentryMauiOptions : Sentry.Extensions.Logging.SentryHostOptions { public SentryMauiOptions() { } public bool AttachScreenshot { get; set; } diff --git a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt index db0645d89c..bac8f36955 100644 --- a/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt +++ b/test/Sentry.Maui.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt @@ -34,7 +34,7 @@ namespace Sentry.Maui void Bind(Microsoft.Maui.Controls.VisualElement element, System.Action addBreadcrumb); void UnBind(Microsoft.Maui.Controls.VisualElement element); } - public class SentryMauiOptions : Sentry.Extensions.Logging.SentryLoggingOptions + public class SentryMauiOptions : Sentry.Extensions.Logging.SentryHostOptions { public SentryMauiOptions() { } public bool AttachScreenshot { get; set; } diff --git a/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs b/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs index a4ee5bb016..7d54cf85e8 100644 --- a/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs +++ b/test/Sentry.Maui.Tests/Internal/SentryMauiStructuredLoggerProviderTests.cs @@ -6,21 +6,18 @@ namespace Sentry.Maui.Tests.Internal; -public class SentryMauiStructuredLoggerProviderTests +public class SentryMauiStructuredLoggerProviderTests : IDisposable { private class Fixture { - public IOptions Options { get; } public IHub Hub { get; } public MockClock Clock { get; } public SdkVersion Sdk { get; } public Fixture() { - var loggingOptions = new SentryMauiOptions(); - - Options = Microsoft.Extensions.Options.Options.Create(loggingOptions); Hub = Substitute.For(); + SentryClientExtensions.SentryOptionsForTestingOnly = new SentryOptions(); Clock = new MockClock(); Sdk = new SdkVersion { @@ -33,12 +30,14 @@ public Fixture() public SentryMauiStructuredLoggerProvider GetSut() { - return new SentryMauiStructuredLoggerProvider(Options.Value, Hub, Clock, Sdk); + return new SentryMauiStructuredLoggerProvider(Hub, Clock, Sdk); } } private readonly Fixture _fixture = new(); + public void Dispose() => SentryClientExtensions.SentryOptionsForTestingOnly = null; + [Fact] public void Type_CustomAttributes_HasProviderAliasAttribute() { @@ -55,7 +54,6 @@ public void Ctor_DependencyInjection_CanCreate() using var services = new ServiceCollection() .AddLogging() .AddSingleton() - .AddSingleton(_fixture.Options) .AddSingleton(_fixture.Hub) .BuildServiceProvider(); @@ -84,7 +82,6 @@ public void CreateLogger_DependencyInjection_CanLog() using var services = new ServiceCollection() .AddLogging() .AddSingleton() - .AddSingleton(_fixture.Options) .AddSingleton(_fixture.Hub) .BuildServiceProvider(); diff --git a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs index e512cbd5ea..afbe518260 100644 --- a/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs +++ b/test/Sentry.Maui.Tests/SentryMauiAppBuilderExtensionsTests.cs @@ -190,6 +190,32 @@ public void UseSentry_SetsMauiSdkNameAndVersion() Assert.Equal(MauiConstants.SdkVersion, @event.Sdk.Version); } + [Fact] + public void UseSentry_ConfigureScope_AppliedWhenSdkInitialized() + { + // Arrange + SentryEvent @event = null; + var builder = _fixture.Builder + .UseSentry(options => + { + options.ConfigureScope(scope => scope.SetTag("configured", "at-init")); + options.SetBeforeSend((e, _) => + { + @event = e; + return null; + }); + }); + + // Act + using var app = builder.Build(); + var client = app.Services.GetRequiredService(); + client.CaptureMessage("test"); + + // Assert + Assert.NotNull(@event); + Assert.Equal("at-init", @event.Tags["configured"]); + } + [Fact] public void UseSentry_EnablesHub() { @@ -338,10 +364,7 @@ public void UseSentry_Logging_AddLoggerProviders() var builder = _fixture.Builder; // Act - builder.UseSentry((SentryMauiOptions options) => - { - options.InitializeSdk = false; - }); + builder.UseSentry(); using var serviceProvider = builder.Services.BuildServiceProvider(); var providers = serviceProvider.GetRequiredService>().ToArray(); @@ -359,10 +382,7 @@ public void UseSentry_Logging_AddLoggerFilterRules() var builder = _fixture.Builder; // Act - builder.UseSentry((SentryMauiOptions options) => - { - options.InitializeSdk = false; - }); + builder.UseSentry(); using var serviceProvider = builder.Services.BuildServiceProvider(); var loggerFilterOptions = serviceProvider.GetRequiredService>().Value; diff --git a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt index 8934fb57a7..aa4f652e7f 100644 --- a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt +++ b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -3,8 +3,10 @@ namespace NLog { public static class ConfigurationExtensions { - public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null, string targetName = "sentry") { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, System.Action? optionsConfig = null) { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, string targetName, System.Action? optionsConfig = null) { } public static void AddTag(this Sentry.NLog.SentryNLogOptions options, string name, NLog.Layouts.Layout layout) { } } @@ -12,29 +14,21 @@ namespace NLog namespace Sentry.NLog { [NLog.Config.NLogConfigurationItem] - public class SentryNLogOptions : Sentry.SentryOptions + public class SentryNLogOptions { public SentryNLogOptions() { } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbCategoryLayout { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? DsnLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? EnvironmentLayout { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } - public bool InitializeSdk { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? Layout { get; set; } public NLog.LogLevel? MinimumBreadcrumbLevel { get; set; } public NLog.LogLevel? MinimumEventLevel { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? ReleaseLayout { get; set; } - public int ShutdownTimeoutSeconds { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] public System.Collections.Generic.IList Tags { get; } [NLog.Config.NLogConfigurationIgnoreProperty] public Sentry.NLog.SentryNLogUser? User { get; set; } @@ -58,25 +52,22 @@ namespace Sentry.NLog public SentryTarget(Sentry.NLog.SentryNLogOptions options) { } public NLog.Layouts.Layout? BreadcrumbCategory { get; set; } public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public NLog.Layouts.Layout? Dsn { get; set; } [System.Obsolete("Logs are always enabled. This option is ignored and will be removed in a future m" + "ajor version. To drop logs, use SetBeforeSendLog and return null.")] public bool EnableLogs { get; set; } - public NLog.Layouts.Layout? Environment { get; set; } - public int FlushTimeoutSeconds { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public bool InitializeSdk { get; set; } public string MinimumBreadcrumbLevel { get; set; } public string MinimumEventLevel { get; set; } public Sentry.NLog.SentryNLogOptions Options { get; } - public NLog.Layouts.Layout? Release { get; set; } - public int ShutdownTimeoutSeconds { get; set; } [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext), "tag")] public System.Collections.Generic.IList Tags { get; } public Sentry.NLog.SentryNLogUser? User { get; set; } - protected override void CloseTarget() { } protected override void FlushAsync(NLog.Common.AsyncContinuation asyncContinuation) { } protected override void InitializeTarget() { } protected override void Write(NLog.LogEventInfo logEvent) { } diff --git a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt index 8934fb57a7..aa4f652e7f 100644 --- a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt +++ b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet11_0.verified.txt @@ -3,8 +3,10 @@ namespace NLog { public static class ConfigurationExtensions { - public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null, string targetName = "sentry") { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, System.Action? optionsConfig = null) { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, string targetName, System.Action? optionsConfig = null) { } public static void AddTag(this Sentry.NLog.SentryNLogOptions options, string name, NLog.Layouts.Layout layout) { } } @@ -12,29 +14,21 @@ namespace NLog namespace Sentry.NLog { [NLog.Config.NLogConfigurationItem] - public class SentryNLogOptions : Sentry.SentryOptions + public class SentryNLogOptions { public SentryNLogOptions() { } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbCategoryLayout { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? DsnLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? EnvironmentLayout { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } - public bool InitializeSdk { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? Layout { get; set; } public NLog.LogLevel? MinimumBreadcrumbLevel { get; set; } public NLog.LogLevel? MinimumEventLevel { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? ReleaseLayout { get; set; } - public int ShutdownTimeoutSeconds { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] public System.Collections.Generic.IList Tags { get; } [NLog.Config.NLogConfigurationIgnoreProperty] public Sentry.NLog.SentryNLogUser? User { get; set; } @@ -58,25 +52,22 @@ namespace Sentry.NLog public SentryTarget(Sentry.NLog.SentryNLogOptions options) { } public NLog.Layouts.Layout? BreadcrumbCategory { get; set; } public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public NLog.Layouts.Layout? Dsn { get; set; } [System.Obsolete("Logs are always enabled. This option is ignored and will be removed in a future m" + "ajor version. To drop logs, use SetBeforeSendLog and return null.")] public bool EnableLogs { get; set; } - public NLog.Layouts.Layout? Environment { get; set; } - public int FlushTimeoutSeconds { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public bool InitializeSdk { get; set; } public string MinimumBreadcrumbLevel { get; set; } public string MinimumEventLevel { get; set; } public Sentry.NLog.SentryNLogOptions Options { get; } - public NLog.Layouts.Layout? Release { get; set; } - public int ShutdownTimeoutSeconds { get; set; } [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext), "tag")] public System.Collections.Generic.IList Tags { get; } public Sentry.NLog.SentryNLogUser? User { get; set; } - protected override void CloseTarget() { } protected override void FlushAsync(NLog.Common.AsyncContinuation asyncContinuation) { } protected override void InitializeTarget() { } protected override void Write(NLog.LogEventInfo logEvent) { } diff --git a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt index 8934fb57a7..aa4f652e7f 100644 --- a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt +++ b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet8_0.verified.txt @@ -3,8 +3,10 @@ namespace NLog { public static class ConfigurationExtensions { - public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null, string targetName = "sentry") { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, System.Action? optionsConfig = null) { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, string targetName, System.Action? optionsConfig = null) { } public static void AddTag(this Sentry.NLog.SentryNLogOptions options, string name, NLog.Layouts.Layout layout) { } } @@ -12,29 +14,21 @@ namespace NLog namespace Sentry.NLog { [NLog.Config.NLogConfigurationItem] - public class SentryNLogOptions : Sentry.SentryOptions + public class SentryNLogOptions { public SentryNLogOptions() { } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbCategoryLayout { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? DsnLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? EnvironmentLayout { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } - public bool InitializeSdk { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? Layout { get; set; } public NLog.LogLevel? MinimumBreadcrumbLevel { get; set; } public NLog.LogLevel? MinimumEventLevel { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? ReleaseLayout { get; set; } - public int ShutdownTimeoutSeconds { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] public System.Collections.Generic.IList Tags { get; } [NLog.Config.NLogConfigurationIgnoreProperty] public Sentry.NLog.SentryNLogUser? User { get; set; } @@ -58,25 +52,22 @@ namespace Sentry.NLog public SentryTarget(Sentry.NLog.SentryNLogOptions options) { } public NLog.Layouts.Layout? BreadcrumbCategory { get; set; } public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public NLog.Layouts.Layout? Dsn { get; set; } [System.Obsolete("Logs are always enabled. This option is ignored and will be removed in a future m" + "ajor version. To drop logs, use SetBeforeSendLog and return null.")] public bool EnableLogs { get; set; } - public NLog.Layouts.Layout? Environment { get; set; } - public int FlushTimeoutSeconds { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public bool InitializeSdk { get; set; } public string MinimumBreadcrumbLevel { get; set; } public string MinimumEventLevel { get; set; } public Sentry.NLog.SentryNLogOptions Options { get; } - public NLog.Layouts.Layout? Release { get; set; } - public int ShutdownTimeoutSeconds { get; set; } [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext), "tag")] public System.Collections.Generic.IList Tags { get; } public Sentry.NLog.SentryNLogUser? User { get; set; } - protected override void CloseTarget() { } protected override void FlushAsync(NLog.Common.AsyncContinuation asyncContinuation) { } protected override void InitializeTarget() { } protected override void Write(NLog.LogEventInfo logEvent) { } diff --git a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt index 8934fb57a7..aa4f652e7f 100644 --- a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt +++ b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.DotNet9_0.verified.txt @@ -3,8 +3,10 @@ namespace NLog { public static class ConfigurationExtensions { - public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null, string targetName = "sentry") { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, System.Action? optionsConfig = null) { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, string targetName, System.Action? optionsConfig = null) { } public static void AddTag(this Sentry.NLog.SentryNLogOptions options, string name, NLog.Layouts.Layout layout) { } } @@ -12,29 +14,21 @@ namespace NLog namespace Sentry.NLog { [NLog.Config.NLogConfigurationItem] - public class SentryNLogOptions : Sentry.SentryOptions + public class SentryNLogOptions { public SentryNLogOptions() { } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbCategoryLayout { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? DsnLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? EnvironmentLayout { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } - public bool InitializeSdk { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? Layout { get; set; } public NLog.LogLevel? MinimumBreadcrumbLevel { get; set; } public NLog.LogLevel? MinimumEventLevel { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? ReleaseLayout { get; set; } - public int ShutdownTimeoutSeconds { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] public System.Collections.Generic.IList Tags { get; } [NLog.Config.NLogConfigurationIgnoreProperty] public Sentry.NLog.SentryNLogUser? User { get; set; } @@ -58,25 +52,22 @@ namespace Sentry.NLog public SentryTarget(Sentry.NLog.SentryNLogOptions options) { } public NLog.Layouts.Layout? BreadcrumbCategory { get; set; } public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public NLog.Layouts.Layout? Dsn { get; set; } [System.Obsolete("Logs are always enabled. This option is ignored and will be removed in a future m" + "ajor version. To drop logs, use SetBeforeSendLog and return null.")] public bool EnableLogs { get; set; } - public NLog.Layouts.Layout? Environment { get; set; } - public int FlushTimeoutSeconds { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public bool InitializeSdk { get; set; } public string MinimumBreadcrumbLevel { get; set; } public string MinimumEventLevel { get; set; } public Sentry.NLog.SentryNLogOptions Options { get; } - public NLog.Layouts.Layout? Release { get; set; } - public int ShutdownTimeoutSeconds { get; set; } [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext), "tag")] public System.Collections.Generic.IList Tags { get; } public Sentry.NLog.SentryNLogUser? User { get; set; } - protected override void CloseTarget() { } protected override void FlushAsync(NLog.Common.AsyncContinuation asyncContinuation) { } protected override void InitializeTarget() { } protected override void Write(NLog.LogEventInfo logEvent) { } diff --git a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt index 8934fb57a7..aa4f652e7f 100644 --- a/test/Sentry.NLog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt +++ b/test/Sentry.NLog.Tests/ApiApprovalTests.Run.Net4_8.verified.txt @@ -3,8 +3,10 @@ namespace NLog { public static class ConfigurationExtensions { - public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null) { } + public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, System.Action? optionsConfig = null, string targetName = "sentry") { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, System.Action? optionsConfig = null) { } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public static NLog.Config.LoggingConfiguration AddSentry(this NLog.Config.LoggingConfiguration configuration, string? dsn, string targetName, System.Action? optionsConfig = null) { } public static void AddTag(this Sentry.NLog.SentryNLogOptions options, string name, NLog.Layouts.Layout layout) { } } @@ -12,29 +14,21 @@ namespace NLog namespace Sentry.NLog { [NLog.Config.NLogConfigurationItem] - public class SentryNLogOptions : Sentry.SentryOptions + public class SentryNLogOptions { public SentryNLogOptions() { } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbCategoryLayout { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? DsnLayout { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? EnvironmentLayout { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } - public bool InitializeSdk { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] public NLog.Layouts.Layout? Layout { get; set; } public NLog.LogLevel? MinimumBreadcrumbLevel { get; set; } public NLog.LogLevel? MinimumEventLevel { get; set; } [NLog.Config.NLogConfigurationIgnoreProperty] - public NLog.Layouts.Layout? ReleaseLayout { get; set; } - public int ShutdownTimeoutSeconds { get; set; } - [NLog.Config.NLogConfigurationIgnoreProperty] public System.Collections.Generic.IList Tags { get; } [NLog.Config.NLogConfigurationIgnoreProperty] public Sentry.NLog.SentryNLogUser? User { get; set; } @@ -58,25 +52,22 @@ namespace Sentry.NLog public SentryTarget(Sentry.NLog.SentryNLogOptions options) { } public NLog.Layouts.Layout? BreadcrumbCategory { get; set; } public NLog.Layouts.Layout? BreadcrumbLayout { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public NLog.Layouts.Layout? Dsn { get; set; } [System.Obsolete("Logs are always enabled. This option is ignored and will be removed in a future m" + "ajor version. To drop logs, use SetBeforeSendLog and return null.")] public bool EnableLogs { get; set; } - public NLog.Layouts.Layout? Environment { get; set; } - public int FlushTimeoutSeconds { get; set; } public bool IgnoreEventsWithNoException { get; set; } public bool IncludeEventDataOnBreadcrumbs { get; set; } public bool IncludeEventPropertiesAsTags { get; set; } + [System.Obsolete(@"The Sentry target no longer initializes the SDK, so a DSN can no longer be supplied to it. Initialize Sentry with SentrySdk.Init (or UseSentry via one of the integrations), and remove 'dsn', 'initializeSdk' and any other core SDK settings from the target configuration.", true)] public bool InitializeSdk { get; set; } public string MinimumBreadcrumbLevel { get; set; } public string MinimumEventLevel { get; set; } public Sentry.NLog.SentryNLogOptions Options { get; } - public NLog.Layouts.Layout? Release { get; set; } - public int ShutdownTimeoutSeconds { get; set; } [NLog.Config.ArrayParameter(typeof(NLog.Targets.TargetPropertyWithContext), "tag")] public System.Collections.Generic.IList Tags { get; } public Sentry.NLog.SentryNLogUser? User { get; set; } - protected override void CloseTarget() { } protected override void FlushAsync(NLog.Common.AsyncContinuation asyncContinuation) { } protected override void InitializeTarget() { } protected override void Write(NLog.LogEventInfo logEvent) { } diff --git a/test/Sentry.NLog.Tests/ConfigurationExtensionsTest.cs b/test/Sentry.NLog.Tests/ConfigurationExtensionsTest.cs index f46f27d593..4a1efcb3a9 100644 --- a/test/Sentry.NLog.Tests/ConfigurationExtensionsTest.cs +++ b/test/Sentry.NLog.Tests/ConfigurationExtensionsTest.cs @@ -12,21 +12,16 @@ public void AddSentry_Parameterless_DefaultTargetName() [Fact] public void AddSentry_ConfigCallback_CallbackInvoked() { - var expected = TimeSpan.FromDays(1); - var actual = new LoggingConfiguration().AddSentry(o => o.FlushTimeout = expected); + var actual = new LoggingConfiguration().AddSentry(o => o.MinimumEventLevel = LogLevel.Warn); var sentryTarget = Assert.IsType(actual.AllTargets[0]); - Assert.Equal(expected.TotalSeconds, sentryTarget.FlushTimeoutSeconds); + Assert.Equal(LogLevel.Warn.ToString(), sentryTarget.MinimumEventLevel); } [Fact] - public void AddSentry_DsnAndConfigCallback_CallbackInvokedAndDsnUsed() + public void AddSentry_TargetName_TargetNamed() { - var expectedTimeout = TimeSpan.FromDays(1); - var expectedDsn = "https://a@sentry.io/1"; - var actual = new LoggingConfiguration().AddSentry(expectedDsn, o => o.FlushTimeout = expectedTimeout); - var sentryTarget = Assert.IsType(actual.AllTargets[0]); - Assert.Equal(expectedTimeout.TotalSeconds, sentryTarget.FlushTimeoutSeconds); - Assert.Equal(expectedDsn, sentryTarget.Options.Dsn); + var actual = new LoggingConfiguration().AddSentry(targetName: "custom"); + Assert.Equal("custom", actual.AllTargets[0].Name); } [Fact] diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt index 34197eb0e0..f489c6a8fc 100644 --- a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt +++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet10_0.verified.txt @@ -40,8 +40,8 @@ FileName: IntegrationTests.verify.cs, Function: Task IntegrationTests.Simple(), Module: null, - LineNumber: 52, - ColumnNumber: 17, + LineNumber: 57, + ColumnNumber: 21, AbsolutePath: {ProjectDirectory}IntegrationTests.verify.cs, ContextLine: null, InApp: false, diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet8_0.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet8_0.verified.txt index 34197eb0e0..f489c6a8fc 100644 --- a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet8_0.verified.txt +++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet8_0.verified.txt @@ -40,8 +40,8 @@ FileName: IntegrationTests.verify.cs, Function: Task IntegrationTests.Simple(), Module: null, - LineNumber: 52, - ColumnNumber: 17, + LineNumber: 57, + ColumnNumber: 21, AbsolutePath: {ProjectDirectory}IntegrationTests.verify.cs, ContextLine: null, InApp: false, diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet9_0.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet9_0.verified.txt index 34197eb0e0..f489c6a8fc 100644 --- a/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet9_0.verified.txt +++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.DotNet9_0.verified.txt @@ -40,8 +40,8 @@ FileName: IntegrationTests.verify.cs, Function: Task IntegrationTests.Simple(), Module: null, - LineNumber: 52, - ColumnNumber: 17, + LineNumber: 57, + ColumnNumber: 21, AbsolutePath: {ProjectDirectory}IntegrationTests.verify.cs, ContextLine: null, InApp: false, diff --git a/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt index 34197eb0e0..f489c6a8fc 100644 --- a/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt +++ b/test/Sentry.NLog.Tests/IntegrationTests.Simple.Net4_8.verified.txt @@ -40,8 +40,8 @@ FileName: IntegrationTests.verify.cs, Function: Task IntegrationTests.Simple(), Module: null, - LineNumber: 52, - ColumnNumber: 17, + LineNumber: 57, + ColumnNumber: 21, AbsolutePath: {ProjectDirectory}IntegrationTests.verify.cs, ContextLine: null, InApp: false, diff --git a/test/Sentry.NLog.Tests/IntegrationTests.verify.cs b/test/Sentry.NLog.Tests/IntegrationTests.verify.cs index 887897a3b4..edf97f08f8 100644 --- a/test/Sentry.NLog.Tests/IntegrationTests.verify.cs +++ b/test/Sentry.NLog.Tests/IntegrationTests.verify.cs @@ -12,52 +12,58 @@ public Task Simple() var transport = new RecordingTransport(); - var configuration = new LoggingConfiguration(); + using (SentrySdk.Init( + options => + { + options.TracesSampleRate = 1; + options.Transport = transport; + options.DiagnosticLevel = SentryLevel.Debug; + options.Dsn = ValidDsn; + options.Release = "test-release"; + })) + { + var configuration = new LoggingConfiguration(); - configuration.AddSentry( - options => - { - options.TracesSampleRate = 1; - options.Layout = "${message}"; - options.Transport = transport; - options.DiagnosticLevel = SentryLevel.Debug; - options.IncludeEventDataOnBreadcrumbs = true; - options.MinimumBreadcrumbLevel = LogLevel.Debug; - options.Dsn = ValidDsn; - options.Release = "test-release"; - options.User = new SentryNLogUser + configuration.AddSentry( + options => { - Id = "${scopeproperty:item=id}", - Username = "${scopeproperty:item=username}", - Email = "${scopeproperty:item=email}", - IpAddress = "${scopeproperty:item=ipAddress}", - Segment = "${scopeproperty:item=segment}", - Other = + options.Layout = "${message}"; + options.IncludeEventDataOnBreadcrumbs = true; + options.MinimumBreadcrumbLevel = LogLevel.Debug; + options.User = new SentryNLogUser { - new TargetPropertyWithContext("mood", "joyous") - }, - }; + Id = "${scopeproperty:item=id}", + Username = "${scopeproperty:item=username}", + Email = "${scopeproperty:item=email}", + IpAddress = "${scopeproperty:item=ipAddress}", + Segment = "${scopeproperty:item=segment}", + Other = + { + new TargetPropertyWithContext("mood", "joyous") + }, + }; - options.AddTag("logger", "${logger}"); - }); + options.AddTag("logger", "${logger}"); + }); - LogManager.Configuration = configuration; + LogManager.Configuration = configuration; - var log = LogManager.GetCurrentClassLogger(); + var log = LogManager.GetCurrentClassLogger(); - using (ScopeContext.PushProperty("id", "myId")) - { - try - { - throw new("Exception message"); - } - catch (Exception exception) + using (ScopeContext.PushProperty("id", "myId")) { - log.Error(exception, "message = {arg}", "arg value"); + try + { + throw new("Exception message"); + } + catch (Exception exception) + { + log.Error(exception, "message = {arg}", "arg value"); + } } - } - LogManager.Flush(); + LogManager.Flush(); + } return Verify(transport.Envelopes) .UniqueForRuntimeAndVersion() @@ -68,33 +74,35 @@ public Task Simple() public Task LoggingInsideTheContextOfLogging() { var transport = new RecordingTransport(); - - var configuration = new LoggingConfiguration(); - var diagnosticLogger = new InMemoryDiagnosticLogger(); - configuration.AddSentry( - options => - { - options.TracesSampleRate = 1; - options.Debug = true; - options.DiagnosticLogger = diagnosticLogger; - options.Transport = transport; - options.Dsn = ValidDsn; - options.AttachStacktrace = false; - options.Release = "test-release"; - }); - LogManager.Configuration = configuration; + using (SentrySdk.Init( + options => + { + options.TracesSampleRate = 1; + options.Debug = true; + options.DiagnosticLogger = diagnosticLogger; + options.Transport = transport; + options.Dsn = ValidDsn; + options.AttachStacktrace = false; + options.Release = "test-release"; + })) + { + var configuration = new LoggingConfiguration(); + configuration.AddSentry(); + + LogManager.Configuration = configuration; - var log = LogManager.GetCurrentClassLogger(); + var log = LogManager.GetCurrentClassLogger(); - SentrySdk.ConfigureScope( - scope => - { - scope.OnEvaluating += (_, _) => log.Error("message from OnEvaluating"); - log.Error("message"); - }); - LogManager.Flush(); + SentrySdk.ConfigureScope( + scope => + { + scope.OnEvaluating += (_, _) => log.Error("message from OnEvaluating"); + log.Error("message"); + }); + LogManager.Flush(); + } return Verify( new diff --git a/test/Sentry.NLog.Tests/NLogDiagnosticLoggerTest.cs b/test/Sentry.NLog.Tests/NLogDiagnosticLoggerTest.cs deleted file mode 100644 index d8f7e94e60..0000000000 --- a/test/Sentry.NLog.Tests/NLogDiagnosticLoggerTest.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Sentry.NLog.Tests; - -public class NLogDiagnosticLoggerTest -{ - [Fact] - public void NLogDiagnosticLogger_Levels() - { - var logger = new NLogDiagnosticLogger(); - try - { - InternalLogger.LogLevel = LogLevel.Debug; - foreach (SentryLevel level in Enum.GetValues(typeof(SentryLevel))) - { - var logWriter = new StringWriter(); - InternalLogger.LogWriter = logWriter; - if (logger.IsEnabled(level)) - { - logger.Log(level, level.ToString()); - } - Assert.Contains(level.ToString(), logWriter.ToString()); - } - } - finally - { - InternalLogger.LogWriter = null; - InternalLogger.LogLevel = LogLevel.Off; - } - } -} diff --git a/test/Sentry.NLog.Tests/SentryTargetConfigurationBindingTests.cs b/test/Sentry.NLog.Tests/SentryTargetConfigurationBindingTests.cs new file mode 100644 index 0000000000..b367f2b9e4 --- /dev/null +++ b/test/Sentry.NLog.Tests/SentryTargetConfigurationBindingTests.cs @@ -0,0 +1,130 @@ +namespace Sentry.NLog.Tests; + +public class SentryTargetConfigurationBindingTests +{ + private static Exception LoadConfiguration(string targetAttributes) + { + var configXml = $@" + + + + + "; + + var logFactory = new LogFactory(); + return Record.Exception(() => logFactory.Configuration = + new XmlLoggingConfiguration(XmlReader.Create(new StringReader(configXml)), null, logFactory)); + } + + [Theory] + [InlineData("dsn='https://key@sentry.io/1'")] + [InlineData("initializeSdk='true'")] + public void LoadConfiguration_WithSdkSetting_Throws(string targetAttributes) + { + var exception = LoadConfiguration(targetAttributes); + + Assert.NotNull(exception); + Assert.Contains("SentrySdk.Init", exception.ToString()); + } + + [Fact] + public void LoadConfiguration_WithTargetSettings_DoesNotThrow() + { + Assert.Null(LoadConfiguration("minimumEventLevel='Warn' includeEventPropertiesAsTags='true'")); + } + + [Fact] + public void LoadConfiguration_WithInitializeSdkFalse_DoesNotThrow() + { + Assert.Null(LoadConfiguration("initializeSdk='false'")); + } + + [Theory] + [InlineData(2)] + [InlineData(3)] + public void AddSentry_DsnOverload_InvokedByName_Throws(int parameterCount) + { + var method = DsnOverload(parameterCount); + + var arguments = method.GetParameters() + .Select(p => (object)(p.Name switch + { + "configuration" => new LoggingConfiguration(), + "dsn" => ValidDsn, + "targetName" => "sentry", + _ => p.HasDefaultValue ? p.DefaultValue : null + })!) + .ToArray(); + + var exception = Assert.Throws(() => method.Invoke(null, arguments)); + + Assert.IsType(exception.InnerException); + Assert.Contains("SentrySdk.Init", exception.InnerException!.Message); + } + + [Theory] + [InlineData(2)] + [InlineData(3)] + public void AddSentry_DsnOverload_IsObsoleteAsError(int parameterCount) + { + var obsolete = DsnOverload(parameterCount).GetCustomAttribute(); + + Assert.NotNull(obsolete); + Assert.True(obsolete!.IsError); + } + + [Fact] + public void Dsn_WhenSet_WritesMigrationMessageToStandardError() + { + var standardError = new List(); + + Set(GetTarget(standardError), "Dsn", (Layout)ValidDsn); + + Assert.Contains(ConfigurationExtensions.ObsoleteDsnOverload, Assert.Single(standardError)); + } + + [Fact] + public void InitializeSdk_WhenSetToTrue_WritesMigrationMessageToStandardError() + { + var standardError = new List(); + + Set(GetTarget(standardError), "InitializeSdk", true); + + Assert.Contains(ConfigurationExtensions.ObsoleteDsnOverload, Assert.Single(standardError)); + } + + [Fact] + public void InitializeSdk_WhenSetToFalse_WritesNothing() + { + var standardError = new List(); + + Set(GetTarget(standardError), "InitializeSdk", false); + + Assert.Empty(standardError); + } + + [Fact] + public void ReportUnsupportedSdkSetting_BothSettingsOnOneTarget_WritesOnce() + { + var standardError = new List(); + var target = GetTarget(standardError); + + Set(target, "Dsn", (Layout)ValidDsn); + Set(target, "InitializeSdk", true); + + Assert.Single(standardError); + } + + private static SentryTarget GetTarget(List standardError) => + new(new SentryNLogOptions(), () => Substitute.For(), new MockClock(), + new UninitializedSdkWarning { WriteToStandardError = standardError.Add }); + + private static void Set(SentryTarget target, string property, object value) => + Record.Exception(() => typeof(SentryTarget).GetProperty(property)!.SetValue(target, value)); + + private static MethodInfo DsnOverload(int parameterCount) => typeof(ConfigurationExtensions) + .GetMethods(BindingFlags.Public | BindingFlags.Static) + .Single(m => m.Name == nameof(ConfigurationExtensions.AddSentry) + && m.GetParameters().Any(p => p.Name == "dsn") + && m.GetParameters().Length == parameterCount + 1); +} diff --git a/test/Sentry.NLog.Tests/SentryTargetTests.Structured.cs b/test/Sentry.NLog.Tests/SentryTargetTests.Structured.cs index 1faf731b88..1318e01ef7 100644 --- a/test/Sentry.NLog.Tests/SentryTargetTests.Structured.cs +++ b/test/Sentry.NLog.Tests/SentryTargetTests.Structured.cs @@ -7,7 +7,7 @@ public partial class SentryTargetTests [Theory] [InlineData(false)] [InlineData(true)] - public void Write_StructuredLogging_UseHubOptionsOverTargetOptions(bool isEnabled) + public void Write_StructuredLogging_RequiresHubOptions(bool isEnabled) { InMemorySentryStructuredLogger capturer = new(); _fixture.Hub.Logger.Returns(capturer); @@ -55,8 +55,8 @@ public void Write_StructuredLogging_LogEvent(bool withActiveSpan) { InMemorySentryStructuredLogger capturer = new(); _fixture.Hub.Logger.Returns(capturer); - _fixture.Options.Environment = "test-environment"; - _fixture.Options.Release = "test-release"; + _fixture.SentryOptions.Environment = "test-environment"; + _fixture.SentryOptions.Release = "test-release"; if (withActiveSpan) { @@ -98,8 +98,8 @@ public void Write_StructuredLogging_LogEvent(bool withActiveSpan) log.Attributes.ShouldContain("sentry.environment", "test-environment"); log.Attributes.ShouldContain("sentry.release", "test-release"); log.Attributes.ShouldContain("sentry.origin", "auto.log.nlog"); - log.Attributes.ShouldContain("sentry.sdk.name", Constants.SdkName); - log.Attributes.ShouldContain("sentry.sdk.version", SentryTarget.NameAndVersion.Version); + log.Attributes.ShouldContain("sentry.sdk.name", SdkVersion.Instance.Name!); + log.Attributes.ShouldContain("sentry.sdk.version", SdkVersion.Instance.Version!); log.Attributes.ShouldContain("category.name", "sentry"); log.Attributes.ShouldContain("property.Text-Property-Key", "Text-Property-Value"); diff --git a/test/Sentry.NLog.Tests/SentryTargetTests.cs b/test/Sentry.NLog.Tests/SentryTargetTests.cs index dc456fde82..286093d9c6 100644 --- a/test/Sentry.NLog.Tests/SentryTargetTests.cs +++ b/test/Sentry.NLog.Tests/SentryTargetTests.cs @@ -8,14 +8,14 @@ public partial class SentryTargetTests private class Fixture { - public SentryNLogOptions Options { get; } = new() { Dsn = ValidDsn }; + public SentryNLogOptions Options { get; } = new(); + + public SentryOptions SentryOptions { get; } = new() { Dsn = ValidDsn }; public IHub Hub { get; } = Substitute.For(); public Func HubAccessor { get; set; } - public IDisposable SdkDisposeHandle { get; set; } = Substitute.For(); - public Scope Scope { get; } public Fixture() @@ -24,7 +24,7 @@ public Fixture() HubAccessor = () => Hub; Scope = new Scope(new SentryOptions()); Hub.SubstituteConfigureScope(Scope); - SentryClientExtensions.SentryOptionsForTestingOnly = Options; + SentryClientExtensions.SentryOptionsForTestingOnly = SentryOptions; } public Target GetTarget(bool asyncTarget = false) @@ -32,11 +32,9 @@ public Target GetTarget(bool asyncTarget = false) var target = new SentryTarget( Options, HubAccessor, - SdkDisposeHandle, new MockClock()) { Name = "sentry", - Dsn = Options.Dsn ?? Options.DsnLayout, }; if (asyncTarget) @@ -77,9 +75,9 @@ public void Can_configure_from_xml_file() - + - True + True @@ -92,14 +90,9 @@ public void Can_configure_from_xml_file() var t = logFactory.Configuration.FindTargetByName("sentry") as SentryTarget; Assert.NotNull(t); - if (t.Options.Dsn != null) - { - Assert.Equal(ValidDsn, t.Options.Dsn); - } - - Assert.Equal("test", t.Options.Environment); - Assert.Equal("1.2.3", t.Options.Release); - Assert.True(t.Options.AttachStacktrace); + Assert.Equal(LogLevel.Warn, t.Options.MinimumEventLevel); + Assert.True(t.Options.IgnoreEventsWithNoException); + Assert.True(t.Options.IncludeEventPropertiesAsTags); } [Fact] @@ -111,7 +104,7 @@ public void Can_configure_user_from_xml_file() - + @@ -126,7 +119,6 @@ public void Can_configure_user_from_xml_file() var t = logFactory.Configuration.FindTargetByName("sentry") as SentryTarget; Assert.NotNull(t); - Assert.Equal(ValidDsn, t.Options.Dsn); Assert.Equal("myUser", t.User?.Username?.ToString()); var other = t.User?.Other; @@ -137,27 +129,8 @@ public void Can_configure_user_from_xml_file() } [Fact] - public void Shutdown_DisposesSdk() + public void Shutdown_DoesNotThrow() { - _fixture.Options.InitializeSdk = false; - var target = _fixture.GetTarget(); - LogManager.Setup().LoadConfiguration(c => c.ForLogger().WriteTo(target)); - - var sut = LogManager.GetCurrentClassLogger(); - - sut.Error(DefaultMessage); - - _fixture.SdkDisposeHandle.DidNotReceive().Dispose(); - - LogManager.Shutdown(); - - _fixture.SdkDisposeHandle.Received(1).Dispose(); - } - - [Fact] - public void Shutdown_NoDisposeHandleProvided_DoesNotThrow() - { - _fixture.Options.InitializeSdk = false; var factory = _fixture.GetLoggerFactory(); var sut = factory.GetCurrentClassLogger(); @@ -249,40 +222,6 @@ public void Log_WithOnlyException_GeneratesBreadcrumbFromException() Assert.Equal(b.Data["exception_message"], expectedException.Message); } - [Fact] - public void Log_NLogSdk_Name() - { - _fixture.Options.MinimumEventLevel = LogLevel.Info; - var logger = _fixture.GetLogger(); - - var expected = typeof(SentryTarget).Assembly.GetNameAndVersion(); - logger.Info(DefaultMessage); - - _fixture.Hub.Received(1) - .CaptureEvent(Arg.Is(e => e.Sdk.Name == Constants.SdkName - && e.Sdk.Version == expected.Version)); - } - - [Fact] - public void Log_NLogSdk_Packages() - { - _fixture.Options.MinimumEventLevel = LogLevel.Info; - var logger = _fixture.GetLogger(); - - SentryEvent actual = null; - _fixture.Hub.When(h => h.CaptureEvent(Arg.Any())) - .Do(c => actual = c.Arg()); - - logger.Info(DefaultMessage); - - var expected = typeof(SentryTarget).Assembly.GetNameAndVersion(); - - Assert.NotNull(actual); - var package = Assert.Single(actual.Sdk.Packages); - Assert.Equal("nuget:" + expected.Name, package.Name); - Assert.Equal(expected.Version, package.Version); - } - [Theory] [ClassData(typeof(LogLevelData))] public void Log_LoggerLevel_Set(LogLevel nlogLevel, SentryLevel? sentryLevel) @@ -461,11 +400,12 @@ public void Log_WithCustomBreadcrumbCategory_RendersCorrectly() } [Fact] - public async Task LogManager_WhenFlushCalled_CallsSentryFlushAsync() + public async Task LogManager_WhenFlushCalled_FlushesHubWithSdkFlushTimeout() { var timeout = TimeSpan.FromSeconds(2); + var sdkFlushTimeout = TimeSpan.FromSeconds(3); - _fixture.Options.FlushTimeout = timeout; + _fixture.SentryOptions.FlushTimeout = sdkFlushTimeout; var factory = _fixture.GetLoggerFactory(asyncTarget: true); // Verify that it's asynchronous @@ -495,69 +435,7 @@ void Continuation(Exception _) Assert.True(tcs.Task.IsCompleted); testDisposable.Received().Dispose(); - await hub.Received().FlushAsync(Arg.Any()); - } - - [SkippableFact] - public void InitializeTarget_InitializesSdk() - { -#if SENTRY_DSN_DEFINED_IN_ENV - Skip.If(true, "This test only works when the DSN is not configured as an environment variable."); -#endif - _fixture.Options.Dsn = Sentry.SentryConstants.DisableSdkDsnValue; - _fixture.SdkDisposeHandle = null; - _fixture.Options.InitializeSdk = true; - - var logWriter = new StringWriter(); - - try - { - InternalLogger.LogWriter = logWriter; - InternalLogger.LogLevel = LogLevel.Debug; - - _fixture.GetLoggerFactory(); - - var logOutput = logWriter.ToString(); - Assert.Contains("Init called with an empty string as the DSN. Sentry SDK will be disabled.", logOutput); - } - finally - { - InternalLogger.LogWriter = null; - InternalLogger.LogLevel = LogLevel.Off; - } - } - - [Fact] - public void Dsn_ReturnsDsnFromOptions_Null() - { - _fixture.Options.Dsn = null; - var target = (SentryTarget)_fixture.GetTarget(); - Assert.Null(target.Dsn); - } - - [Fact] - public void Dsn_ReturnsDsnFromOptions_Instance() - { - var expectedDsn = "https://a@sentry.io/1"; - _fixture.Options.Dsn = expectedDsn; - var target = (SentryTarget)_fixture.GetTarget(); - Assert.Equal(expectedDsn, target.Options.Dsn); - } - - [Fact] - public void Dsn_SupportsNLogLayout_Lookup() - { - var expectedDsn = "https://a@sentry.io/1"; - var target = (SentryTarget)_fixture.GetTarget(); - target.Dsn = "${var:mydsn}"; - var logFactory = new LogFactory(); - var logConfig = new LoggingConfiguration(logFactory) - { - Variables = { ["mydsn"] = expectedDsn } - }; - logConfig.AddRuleForAllLevels(target); - logFactory.Configuration = logConfig; - Assert.Equal(expectedDsn, target.Options.Dsn); + await hub.Received().FlushAsync(sdkFlushTimeout); } [Fact] @@ -683,51 +561,6 @@ public void IncludeEventDataOnBreadcrumbs_SetterReplacesOptions() Assert.False(target.IncludeEventDataOnBreadcrumbs); } - [Fact] - public void ShutdownTimeoutSeconds_ValueFromOptions() - { - const int expected = 60; - _fixture.Options.ShutdownTimeoutSeconds = expected; - var target = (SentryTarget)_fixture.GetTarget(); - Assert.Equal(expected, target.ShutdownTimeoutSeconds); - } - - [Fact] - public void ShutdownTimeoutSeconds_Default_2Seconds() - { - var target = (SentryTarget)_fixture.GetTarget(); - Assert.Equal(2, target.ShutdownTimeoutSeconds); - } - - [Fact] - public void ShutdownTimeoutSeconds_SetterReplacesOptions() - { - var expected = 60; - _fixture.Options.ShutdownTimeoutSeconds = int.MinValue; - var target = (SentryTarget)_fixture.GetTarget(); - target.ShutdownTimeoutSeconds = expected; - Assert.Equal(expected, target.ShutdownTimeoutSeconds); - } - - [Fact] - public void FlushTimeoutSeconds_ValueFromOptions() - { - var expected = 10; - _fixture.Options.FlushTimeout = TimeSpan.FromSeconds(expected); - var target = (SentryTarget)_fixture.GetTarget(); - Assert.Equal(expected, target.FlushTimeoutSeconds); - } - - [Fact] - public void FlushTimeoutSeconds_SetterReplacesOptions() - { - var expected = 100; - _fixture.Options.FlushTimeout = TimeSpan.FromSeconds(expected); - var target = (SentryTarget)_fixture.GetTarget(); - target.FlushTimeoutSeconds = expected; - Assert.Equal(expected, target.FlushTimeoutSeconds); - } - [Fact] public void IgnoreEventsWithNoException_SetterReplacesOptions() { @@ -737,13 +570,6 @@ public void IgnoreEventsWithNoException_SetterReplacesOptions() Assert.True(target.IgnoreEventsWithNoException); } - [Fact] - public void FlushTimeoutSeconds_Default_15Seconds() - { - var target = (SentryTarget)_fixture.GetTarget(); - Assert.Equal(15, target.FlushTimeoutSeconds); - } - [Fact] public void BreadcrumbLayout_Null_FallsBackToLayout() { diff --git a/test/Sentry.NLog.Tests/SentryTargetUninitializedSdkTests.cs b/test/Sentry.NLog.Tests/SentryTargetUninitializedSdkTests.cs new file mode 100644 index 0000000000..9cf269f737 --- /dev/null +++ b/test/Sentry.NLog.Tests/SentryTargetUninitializedSdkTests.cs @@ -0,0 +1,80 @@ +namespace Sentry.NLog.Tests; + +public class SentryTargetUninitializedSdkTests +{ + private readonly List _internalLog = []; + private readonly List _standardError = []; + + private Logger GetLogger(bool sdkEnabled, string dsn = ValidDsn) + { + var hub = Substitute.For(); + hub.IsEnabled.Returns(sdkEnabled); + hub.Logger.Returns(new InMemorySentryStructuredLogger()); + + var warning = new UninitializedSdkWarning(_internalLog.Add) + { + DsnLocator = () => dsn, + WriteToStandardError = _standardError.Add + }; + + var target = new SentryTarget(new SentryNLogOptions(), () => hub, new MockClock(), warning) + { + Name = "sentry" + }; + + var factory = new LogFactory(); + var configuration = new LoggingConfiguration(factory); + configuration.AddTarget("sentry", target); + configuration.AddRule(LogLevel.Trace, LogLevel.Fatal, target); + factory.Configuration = configuration; + + return factory.GetLogger("sentry"); + } + + [Fact] + public void Write_SdkNotInitialized_WarnsToInternalLoggerAndStandardError() + { + GetLogger(sdkEnabled: false).Error("message"); + + Assert.Equal(new[] { SentryTarget.UninitializedSdkMessage }, _internalLog); + Assert.Equal(new[] { SentryTarget.UninitializedSdkMessage }, _standardError); + } + + [Fact] + public void Write_SdkNotInitialized_WarnsOnceForManyEvents() + { + var logger = GetLogger(sdkEnabled: false); + + logger.Error("first"); + logger.Fatal("second"); + + Assert.Single(_standardError); + } + + [Fact] + public void Write_SdkNotInitializedAndBelowMinimumEventLevel_DoesNotWarn() + { + GetLogger(sdkEnabled: false).Warn("message"); + + Assert.Empty(_internalLog); + Assert.Empty(_standardError); + } + + [Fact] + public void Write_SdkNotInitializedAndNoDsnFound_DoesNotWarn() + { + GetLogger(sdkEnabled: false, dsn: null).Error("message"); + + Assert.Empty(_internalLog); + Assert.Empty(_standardError); + } + + [Fact] + public void Write_SdkInitialized_DoesNotWarn() + { + GetLogger(sdkEnabled: true).Error("message"); + + Assert.Empty(_internalLog); + Assert.Empty(_standardError); + } +} diff --git a/test/Sentry.Tests/SentryOptionsTests.cs b/test/Sentry.Tests/SentryOptionsTests.cs index 145f01c0ab..fbd455c63a 100644 --- a/test/Sentry.Tests/SentryOptionsTests.cs +++ b/test/Sentry.Tests/SentryOptionsTests.cs @@ -719,4 +719,33 @@ public void CachesInstallationId() installationId2.Should().Be(installationId1); logger.Received(0).Log(SentryLevel.Debug, "Resolved installation ID '{0}'.", null, Arg.Any()); } + + [Fact] + public void ApplyDefaultTags_TagInEvent_DoesNotOverrideTag() + { + const string key = "key"; + const string expected = "event tag value"; + var sut = new SentryOptions(); + var target = new SentryEvent(); + target.SetTag(key, expected); + sut.DefaultTags[key] = "default value"; + + sut.ApplyDefaultTags(target); + + Assert.Equal(expected, target.Tags[key]); + } + + [Fact] + public void ApplyDefaultTags_TagNotInEvent_AppliesTag() + { + const string key = "key"; + const string expected = "default tag value"; + var sut = new SentryOptions(); + var target = new SentryEvent(); + sut.DefaultTags[key] = expected; + + sut.ApplyDefaultTags(target); + + Assert.Equal(expected, target.Tags[key]); + } }