feat: Microsoft.Extensions.Logging integration no longer initializes the SDK - #5595
jamescrosswell wants to merge 11 commits into
Conversation
…the SDK Completes the logging-integration part of #5245. The MEL integration now only wires up the logger providers; Sentry has to be initialized separately. Unlike Serilog, NLog and log4net, SentryLoggingOptions keeps deriving from SentryOptions, because SentryAspNetCoreOptions, SentryMauiOptions and SentryBlazorOptions derive from it and those integrations do initialize the SDK. InitializeSdk therefore stays as internal plumbing, now defaulting to false and opted into by the framework integrations that own it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## feat/no-init-from-logging-log4net-5245 #5595 +/- ##
==========================================================================
+ Coverage 74.60% 74.65% +0.05%
==========================================================================
Files 514 517 +3
Lines 18732 18734 +2
Branches 3640 3634 -6
==========================================================================
+ Hits 13975 13986 +11
+ Misses 3889 3876 -13
- Partials 868 872 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… framework options SentryLoggingOptions no longer derives from SentryOptions, matching the Serilog and NLog options: it carries only the log levels and entry filters. SentryAspNetCoreOptions, SentryMauiOptions and SentryBlazorOptions now derive from a new abstract SentryHostOptions, which keeps MinimumEventLevel, MinimumBreadcrumbLevel, ConfigureScope and AddLogEntryFilter by passing them through to an inner SentryLoggingOptions, so existing UseSentry callbacks and configuration keys keep working. InitializeSdk is removed. Integrations that initialise through DI call AddSentry<TOptions>; MAUI, which initialises in SentryMauiInitializer, uses an internal non-initialising overload. ConfigureScope callbacks are applied right after the SDK is initialised instead of when the MEL logger provider is built. Also fixes Blazor WebAssembly's logger ignoring the logging settings from UseSentry (it was built from a separate, default IOptions<SentryLoggingOptions>), and structured logs from plain MEL taking default attributes from options the SDK was not initialised with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Some background for reviewers on why this PR splits the options class. Initialising SentryGenerally Sentry's integrations fall into two camps:
As a rule, we provide For people not using any specific platform, they can instead initialise Sentry by calling One of the features that Sentry supports is logging and it's such a common feature that we decided it might be convenient if people could initialise both logging and the Sentry SDK in a single call (e.g. when using Sentry in a console application, where you also have logging). In theory that was fine. In practice it's caused huge headaches (see #5245). If you use Serilog and ASP.NET Core, you have to tell Serilog "send logs, but don't initialise — ASP.NET Core already did that". Users constantly get this wrong, and so did we. So in v7, we're removing the ability to initialise the SDK from the logging integrations. These will now be initialised the same as all the other 'feature' integrations we do. The old wayIn version 6 the options classes look something like this: classDiagram
SentryOptions <|-- SentryLoggingOptions
SentryLoggingOptions <|-- SentryAspNetCoreOptions
SentryLoggingOptions <|-- SentryMauiOptions
SentryLoggingOptions <|-- SentryBlazorOptions
The new wayAs such, in version 7.0 we're splitting that options class up:
classDiagram
SentryOptions <|-- SentryHostOptions
SentryHostOptions <|-- SentryAspNetCoreOptions
SentryHostOptions <|-- SentryMauiOptions
SentryHostOptions <|-- SentryBlazorOptions
SentryHostOptions *-- SentryLoggingOptions : holds internally
|
There was a problem hiding this comment.
Just a note on why SentryHostOptions.Logging is a composite property:
SentryLoggingOptionsmust be a concrete public class. It's what users configure inbuilder.Logging.AddSentry(o => …), and when using MELIOptions<SentryLoggingOptions>resolves from DI for the options binding.IOptions<T>is constrained toclass, new(), so this can't be an interface.SentryHostOptionshas to be aSentryOptions. It's the object handed toSentrySdk.InitHub... so it can't descend fromSentryLoggingOptions- that's kind of the whole point of this PR stack - removing the DSN (and options that are used for SDK initialisation) from the options that are used to initialise the logging integrations.
| @@ -61,12 +61,6 @@ internal SentryLoggerProvider( | |||
| } | |||
| } | |||
| }); | |||
There was a problem hiding this comment.
| }); |
This entire block is redundant.
Applications will call SentrySdk.Init or the UseSentry variant for integrations like AspNetCore and Maui. The Sdk name and version should be set to the name of the integration that actually initialises the Sentry Hub (which can no longer be the logging integrations, when this PR is merged).
See:
The logging integration records its origin (auto.log.*) to indicate where logs come from.
…sposes the hub Sdk.Name and Sdk.Version should identify the integration that initialised the hub, which after this PR can no longer be a logging integration. The logging integration identifies itself through the log origin (auto.log.*) instead. See #5497. With the SDK name gone, and ConfigureScope callbacks now applied at init, the scope the provider pushed has nothing left to hold, so it goes too. Disposing the hub goes as well: whoever initialises the hub owns it, and the provider is now always handed HubAdapter, which is not IDisposable. The one fixture that handed it a real Hub now disposes the Hub it created. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…it-from-logging-mel-5245
… set the SDK name Completes the change across the four logging integrations: the SDK name on a log should identify the integration that initialised the hub, and the logging integration identifies itself through the origin (auto.log.extensions_logging). See #5497. The ASP.NET Core and MAUI structured logger providers keep passing their own SDK version: those integrations do initialise the SDK, so the name is theirs to set. With no remaining callers, Constants and SentryLoggerProvider.NameAndVersion are deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t-5245' into feat/no-init-from-logging-mel-5245
…s with a migration error Mirrors the Serilog (#5611), NLog and log4net guards. The v6 AddSentry(dsn) overload and the SentryLoggingOptions.Dsn / InitializeSdk properties come back as tombstones: obsolete-as-error for code callers, throwing NotSupportedException so configuration fails loudly with migration guidance instead of being ignored. Both binding paths are covered. On .NET 6 and later the Sentry section binds through BindableSentryLoggingOptions, which now carries these keys and throws when either is present; on netstandard2.0 the configuration binder sets the properties directly and the setters throw. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rror The tombstone setters threw unconditionally, which broke every bind of SentryLoggingOptions on netstandard2.0: ConfigurationBinder reads each property and writes the value back, so InitializeSdk's own `false` tripped the guard even when the key was absent. That failed four tests on net48, three of them pre-existing. The setters now throw only for a value that asks for something the integration can no longer do, and BindableSentryLoggingOptions matches, so both binding paths behave the same: a Dsn or InitializeSdk=true is an error, InitializeSdk=false is accepted because not initializing is what now always happens. Covered by a test that binds onto the options directly, which reproduces the netstandard2.0 write-back on every target framework. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t-5245' into feat/no-init-from-logging-mel-5245
…t-5245' into feat/no-init-from-logging-mel-5245
…t-5245' into feat/no-init-from-logging-mel-5245
The
Microsoft.Extensions.Loggingportion of #5245, stacked on #5592 (log4net) and following the same design.AddSentrynow only wires up the logger providers; Sentry has to be initialized separately viaSentrySdk.Init,UseSentry, etc.This is the last of the four logging integrations, so it closes the issue.
Closes #5245
Tip
Background for reviewers on how Sentry gets initialised today, and why this PR splits
SentryLoggingOptions: #5595 (comment)Changelog Entry
Microsoft.Extensions.Loggingintegration no longer initializes the SDK. Sentry must now be initialized separately from the logging integration (usingSentrySdk.InitorUseSentry) - #5595MinimumEventLevel,MinimumBreadcrumbLevel, log entry filters andConfigureScopecallbacks set inUseSentry- #5595Breaking changes
SentryLoggingOptionsno longer derives fromSentryOptions, matching the Serilog and NLog options. It carries onlyMinimumBreadcrumbLevel,MinimumEventLeveland log entry filters, sobuilder.Logging.AddSentry(o => o.Dsn = "…")now fails with a migration error instead of silently doing nothing. Core SDK settings go on the options used to initialize Sentry.SentryLoggingOptions.ConfigureScopeis removed. CallSentrySdk.ConfigureScopeafter initializing Sentry.ILoggingBuilder.AddSentry(string dsn)no longer initializes Sentry. It is kept as an obsolete-as-error tombstone that throwsNotSupportedExceptionwith migration guidance, so both code callers and reflection-based callers fail loudly.ILoggerFactory.AddSentry(…)no longer initializes Sentry, replaces the current hub, or assigns aMelDiagnosticLoggeras the SDK'sDiagnosticLogger.InitializeSdkno longer has any effect.SentryLoggingOptions.DsnandSentryLoggingOptions.InitializeSdkare kept as tombstones that throw when set, including when they are bound from theSentryconfiguration section, so an app that upgrades with a config-bound DSN fails at startup instead of silently reporting nothing. On the framework options it is gone entirely.SentryAspNetCoreOptions,SentryMauiOptionsandSentryBlazorOptionsnow derive from a new abstractSentryHostOptions : SentryOptions. They still haveMinimumBreadcrumbLevel,MinimumEventLevel,ConfigureScopeandAddLogEntryFilter, and the same keys still bind from theSentryconfiguration section, so existingUseSentrycallbacks andappsettings.jsonfiles keep working. Code that treats them as aSentryLoggingOptionsno longer compiles.ServiceCollectionExtensions.AddSentry<TOptions>now requiresTOptions : SentryHostOptions.builder.Logging.AddSentry()no longer registersSentryOptionsin the service collection, so resolvingSentryOptionsfrom DI on that path now throws. The MEL integration no longer owns an options object that initializes the SDK. The integrations that do initialize still register it.ConfigureScopecallbacks on the framework options now run when Sentry is initialized, rather than when the Sentry logger provider is first created.Before:
After:
Fixes
UseSentry. Blazor registered the plain MEL logger provider, which dependency injection built from a separate, defaultIOptions<SentryLoggingOptions>rather than theSentryBlazorOptionsconfigured inUseSentry.MinimumEventLevel,MinimumBreadcrumbLevel, log entry filters andConfigureScopewere all silently ignored by the logger; SDK initialization itself was unaffected. The same bug exists onmain.sentry.environment,sentry.releaseandserver.addresswere read fromSentryLoggingOptions, which the SDK was no longer initialized with. The structured logger now reads them from the hub.Notes for review
Why
SentryHostOptions.SentryLoggingOptionswas doing two jobs: configuring the MEL logger, and acting as the base class for integrations that initialize the SDK. Splitting them is what letsSentryLoggingOptionsgo standalone. The host options pass the log levels and filters through to an innerSentryLoggingOptionsinstance (the same object, not a copy), and that is what their logger providers receive.Why abstract. It's the natural options type for a generic-host init path (Add a non-logging way to initialise Sentry in generic host apps #5572). Making it concrete later is additive; the reverse would be breaking. It lives in
Sentry.Extensions.Loggingbecause that's the one package ASP.NET Core, MAUI and Blazor all reference. Where it ultimately belongs is the packaging question Add a non-logging way to initialise Sentry in generic host apps #5572 raises.ConfigureScopetiming. For MAUI and Blazor, the only thing that applied these callbacks used to be the MEL logger provider's constructor, gated onhub.IsEnabledwhen the provider was built. They're now applied right after init: inAddSentry<TOptions>'s hub factory (ASP.NET Core, Blazor) and inSentryMauiInitializer(MAUI). ASP.NET Core still also applies them per request inSentryMiddleware, and gRPC in its interceptor.SDK name.
SentryLoggerProviderno longer stampsSdk.Name/Sdk.Version, pushes a scope, or disposes the hub. Per Metrics andSentrySdk.Loggerlogs emitted during a request carry nosentry.sdk.name/sentry.sdk.versionon ASP.NET Core #5497 the SDK name identifies the integration that initialized the hub, and the logging integration is identified by the origin (auto.log.extensions_logging). ASP.NET Core, MAUI and gRPC set their own names, so they are unaffected; Blazor WebAssembly has none of its own, which is Blazor WebAssembly apps don't report a Blazor SDK name #5613.Migration guard. Mirrors feat(serilog): configuring a DSN on the sink now fails with a migration error #5611 (Serilog) and the NLog and log4net guards on this stack. Both MEL binding paths are covered: on .NET 6 and later the
Sentrysection binds throughBindableSentryLoggingOptions, which now carriesDsn/InitializeSdkand throws when either is present; on netstandard2.0 the configuration binder sets the properties directly and the tombstone setters throw.SentryLoggingOptionsTestsskips those two, sinceBindableTestsotherwise asserts that every bindable property round-trips onto the options.InitializeSdk. MAUI initializes inSentryMauiInitializer, so it calls an internal non-initializing overload ofAddSentry<TOptions>instead of setting a flag. Tests that setInitializeSdk = falseto avoid initializing now useDisableSdkDsnValue.UseSentry_OptionsNotInitializeSdk_DisabledSdktested the flag itself and is deleted;UseSentry_DisableDsn_DisabledSdkstill covers a disabled SDK.Blazor registrations.
UseSentrynow delegates to an internalILoggingBuilderextension so it can be unit tested (WebAssemblyHostBuilderneeds a browser runtime). The providers are registered by factory so they keep their existing types, and with them the existing provider alias and filter configuration. Configuration binding moved to a smallSentryHostOptionsSetup<TOptions>inSentry.Extensions.Logging, which has the configuration-binding source generator enabled, since Blazor WASM is trimmed.New tests.
ConfigureScopecallbacks, and the non-initializing path doesn't.ConfigureScopedata reaches events. Nothing tested that before.MinimumEventLevel.Each test was checked to fail with its fix reverted.
The
ApplyDefaultTagstests only usedSentryLoggingOptionsas a vehicle for a coreSentryOptionsmethod; they moved toSentryOptionsTests.The structured-logger tests now give their mocked hub options through
SentryOptionsForTestingOnlyand reset it afterwards. The MEL provider test previously passed only because another test class happened to leave it set.builder.Logging.AddSentry(dsn)was the only way to initialize Sentry in a generic-host app, sosamples/Sentry.Samples.GenericHostnow callsSentrySdk.Initdirectly. Add a non-logging way to initialise Sentry in generic host apps #5572 tracks the replacement and should land before this ships.ApiApprovalTests.Run.Net4_8can't regenerate on macOS. It was byte-identical to the other snapshots before this change, so it's a copy of the regenerated one.🤖 Generated with Claude Code