Repository navigation
feat(mel)!: the Microsoft.Extensions.Logging integration no longer initializes the SDK - #5595
Conversation
The Sentry sink for Serilog now only configures the sink. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - SentrySerilogOptions no longer derives from SentryOptions and only carries sink settings; InitializeSdk is removed - Remove the WriteTo.Sentry(string dsn, ...) overload - Rename ApplySerilogScopeToEvents() to UseSerilog(), make it idempotent - The sink logs a one-time diagnostic warning when UseSerilog() was not called on the options used to initialize Sentry Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…into feat/no-init-from-logging-5245
The Sentry target for NLog now only configures the target. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - SentryNLogOptions no longer derives from SentryOptions and only carries target settings; FlushTimeout moves onto it directly - Remove InitializeSdk, Dsn/DsnLayout, Release/ReleaseLayout, Environment/EnvironmentLayout and ShutdownTimeoutSeconds. Events take release and environment from the SDK options - Collapse the AddSentry overloads into AddSentry(optionsConfig, targetName); the dsn overloads are removed - The target no longer routes SDK diagnostics to NLog's InternalLogger Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com>
Remove SentryTarget.FlushTimeoutSeconds and SentryNLogOptions.FlushTimeout. When NLog flushes the target, the hub is now flushed with the FlushTimeout from the options used to initialize Sentry, since the target no longer owns the SDK. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Sentry appender for log4net now only sends log events to Sentry. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - Remove SentryAppender.Dsn and the lazy SDK initialization on first append - Remove SentryAppender.Environment; events take the environment from the options used to initialize Sentry - Remove the OnClose override, which only disposed the SDK the appender had initialized Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…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.96% 75.07% +0.10%
==========================================================================
Files 515 519 +4
Lines 18832 18872 +40
Branches 3655 3658 +3
==========================================================================
+ Hits 14118 14168 +50
+ Misses 3855 3840 -15
- Partials 859 864 +5 ☔ 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.
The sample sets the DSN in code via UseSentry, so the commented-out Dsn entry is misleading. EnableTracing is declared on BindableSentryOptions but never applied, so setting it has no effect. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Emit can run concurrently, so the check-then-set on the warned flag could let more than one thread log the warning. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…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>
…into feat/no-init-from-logging-nlog-5245
Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The sink identifies itself through the log origin (auto.log.serilog) instead. See #5497. Events are no longer stamped with sentry.dotnet.serilog, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…logging-nlog-5245
Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The target identifies itself through the log origin (auto.log.nlog) instead. See #5497. Events are no longer stamped with sentry.dotnet.nlog, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. With no remaining callers, Constants is deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…from-logging-log4net-5245
Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The appender identifies itself through the log origin (auto.log.log4net) instead. See #5497. Events are no longer stamped with sentry.dotnet.log4net, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. 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>
The hub plumbing on the logging path is temporaryWorth recording why this PR fixes the
This PR makes the initializing registration replace any existing accessor, so the order of the two calls no longer matters. That's deliberately targeted, not the end state. The end state is #5572: the host-level init path takes ownership of the hub plumbing, It can't be done in this PR: Same reasoning applies to |
There was a problem hiding this comment.
I ran the test projects this touches, and the inline comments cover what I found. Events from the logging integration also lose nuget:Sentry.Extensions.Logging in sdk.packages, which #5649 tracks.
Up to you if we include these changes, otherwise looks great! ✅
…5651) The V3 test project links the other test files added in #5592, but not SentryAppenderConfigurationBindingTests. That file didn't compile against log4net 3, which marks the XmlElement parameter of XmlConfigurator.Configure as non-nullable. With a null-forgiving operator on DocumentElement it compiles, so the V3 project now links it and the Dsn tombstone behavior is pinned on both log4net versions. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(nlog): Report a stale dsn and accept initializeSdk=false Both tombstones could leave an upgraded app worse off than it needed to be. A dsn left in NLog.config was silent with NLog's default settings. NLog swallows the setter's exception unless throwConfigExceptions is on, so the target attached, Sentry was never initialized and nothing was printed. The runtime warning also stayed quiet, because it only looks for a DSN in the environment or an assembly attribute. The Dsn setter now writes the migration message to standard error before it throws. initializeSdk="false" was the recommended v6 setting next to UseSentry, and it already matches the new behavior. With throwConfigExceptions on, it still threw, NLog rejected the whole configuration and the app lost every NLog target. The setter now only throws for true, like the Microsoft.Extensions.Logging tombstone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(nlog): report a stale initializeSdk="true" as well The stale dsn message only came from the Dsn setter, so a config carrying initializeSdk="true" instead was still silent with NLog's default throwConfigExceptions: the setter threw, NLog discarded it, the target attached and nothing was printed. Both setters now go through one report-and-throw helper. Reporting from both setters means a v6 config carrying dsn and initializeSdk="true" together would print the same message twice, so the helper reports at most once per target. A configuration reload builds a new target, and so reports again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com>
…245' into feat/no-init-from-logging-log4net-5245 Picks up #5652: a stale dsn or initializeSdk="true" in NLog.config is now reported to standard error, and initializeSdk="false" is accepted rather than failing the configuration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t-5245' into feat/no-init-from-logging-log4net-5245 Picks up #5651, which runs the appender configuration binding tests against log4net 3 as well. It adds its own <Compile Include> to Sentry.Log4Net.V3.Tests beside the one for the uninitialized-SDK tests, so the two merged cleanly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t-5245' into feat/no-init-from-logging-mel-5245 Picks up #5652 (a stale dsn or initializeSdk="true" in NLog.config is reported to standard error, and initializeSdk="false" is accepted rather than failing the configuration) and #5651 (the appender configuration binding tests also run against log4net 3), via the log4net branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SentryConstants.DisableSdkDsnValue is an empty string, and SettingLocator.GetDsn treats an empty Dsn as unset and falls back to SENTRY_DSN. So on a machine with that variable set, these two tests initialised a working SDK and captured the event they assert is never captured. FakeSettings() swaps in a locator that reads only from its own dictionary, so the fallback can't reach the real environment. Both tests now pass with and without SENTRY_DSN set. The fallback itself predates this PR and is also on main: options.Dsn = "" does not disable the SDK when SENTRY_DSN is set. Reported by @ric-oliv on #5595. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The logging integrations no longer initialize the SDK, so the logging provider section is no longer a place where a DSN or any other core SDK setting has an effect. Reading them there would silently ignore them. The root 'Sentry' section now configures the whole of SentryHostOptions, and 'Logging:Sentry' configures only the logging half. Any other key there throws, naming the offending keys, so an app that upgrades with core settings in the logging section fails at startup instead of reporting nothing. A single SentryLoggingConfiguration binds the logging section for both the standalone MEL options and the host options, on every target framework. That replaces BindableSentryLoggingOptions and the separate netstandard2.0 reflection path. The integrations that initialize Sentry read 'Logging:Sentry' from the app's configuration rather than through ILoggerProviderConfiguration: an app that passes the whole configuration to AddConfiguration, as Google Cloud Functions does, makes the root 'Sentry' section part of the provider section, and every core setting in it would then be rejected. Plain MEL keeps the provider configuration, where rejecting core settings is correct. Also fixes ASP.NET Core registering SentryAspNetCoreOptionsSetup twice - once with the 'Sentry' section and once with the logging provider configuration - which applied everything its Configure adds beyond binding twice: two Kestrel deduplication log filters and two TraceIgnoreStatusCodeTransactionProcessor instances. The IConfiguration substitute in SentryWebHostBuilderExtensionsTests returned an empty string for every key, where configuration returns null for an absent one. Returning null removes a NET8_0-only workaround. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 04a5a54. Configure here.
Blazor WebAssembly sets DetectStartupTime, RequestBodyCompressionLevel and IsGlobalModeEnabled because the platform requires them, in the same Configure delegate that runs the UseSentry callback. That delegate was registered before the configuration binding, and IConfigureOptions<T> run in registration order, so a Sentry:DetectStartupTime key in wwwroot/appsettings.json won over the platform default - reintroducing the PlatformNotSupportedException the default is there to avoid - and configuration won over anything set in UseSentry, the opposite of ASP.NET Core. The two setups are now registered first, so precedence runs from the Sentry section, through Logging:Sentry, to the callback and the platform defaults. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Brings 6.12.0 and main's changes since 4af9645 into version7, including #5640, #5643, #5646, #5656, #5659, #5661, #5662 and #5667. Conflict resolutions: - Directory.Build.props: keep version7's 7.0.0 / prerelease. - LoggingBuilderExtensions.cs: keep version7's side. #5595 removed the generic AddSentry<TOptions> that #5640 changed; version7 fixes the same Blazor bug in AddSentryBlazor. - LoggingBuilderExtensionsTests.cs: drop #5640's two AddSentry_DerivedOptions_* tests (merged cleanly, don't compile on version7). - ServiceCollectionExtensions.cs: keep version7's side and move #5646's issue 103 comment to the registrations in AddSentryHub. - ServiceCollectionExtensionsTests.cs: keep version7's tests and port #5646's two tests to run on both version7 init paths: the host (AddSentry<TestHostOptions>(initializeSdk: true)) and SentrySdk.Init plus Logging.AddSentry(). - .github/workflows/build.yml: keep version7's .NET 11 integration-test steps with #5662's integration-test 3.4.1 pin and #5667's ubuntu-24.04 runners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings.json (#5658) * feat: Serilog sink no longer initializes the SDK The Sentry sink for Serilog now only configures the sink. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - SentrySerilogOptions no longer derives from SentryOptions and only carries sink settings; InitializeSdk is removed - Remove the WriteTo.Sentry(string dsn, ...) overload - Rename ApplySerilogScopeToEvents() to UseSerilog(), make it idempotent - The sink logs a one-time diagnostic warning when UseSerilog() was not called on the options used to initialize Sentry Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Accept API verifier changes * Tweak comments in the samples * feat: NLog target no longer initializes the SDK The Sentry target for NLog now only configures the target. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - SentryNLogOptions no longer derives from SentryOptions and only carries target settings; FlushTimeout moves onto it directly - Remove InitializeSdk, Dsn/DsnLayout, Release/ReleaseLayout, Environment/EnvironmentLayout and ShutdownTimeoutSeconds. Events take release and environment from the SDK options - Collapse the AddSentry overloads into AddSentry(optionsConfig, targetName); the dsn overloads are removed - The target no longer routes SDK diagnostics to NLog's InternalLogger Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Tweaked wording Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com> * feat: NLog target flushes using the SDK's FlushTimeout Remove SentryTarget.FlushTimeoutSeconds and SentryNLogOptions.FlushTimeout. When NLog flushes the target, the hub is now flushed with the FlushTimeout from the options used to initialize Sentry, since the target no longer owns the SDK. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: log4net appender no longer initializes the SDK The Sentry appender for log4net now only sends log events to Sentry. Sentry must be initialized separately (SentrySdk.Init, UseSentry, etc). - Remove SentryAppender.Dsn and the lazy SDK initialization on first append - Remove SentryAppender.Environment; events take the environment from the options used to initialize Sentry - Remove the OnClose override, which only disposed the SDK the appender had initialized Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: Microsoft.Extensions.Logging integration no longer initializes 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> * feat: SentryHostOptions replaces SentryLoggingOptions as the base for 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> * docs: drop unused Sentry settings from the Serilog sample appsettings The sample sets the DSN in code via UseSentry, so the commented-out Dsn entry is misleading. EnableTracing is declared on BindableSentryOptions but never applied, so setting it has no effect. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serilog): make the UseSerilog warning check atomic Emit can run concurrently, so the check-then-set on the warned flag could let more than one thread log the warning. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: the logging integration no longer stamps the SDK name or disposes 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> * refactor: the Serilog sink no longer sets the SDK name Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The sink identifies itself through the log origin (auto.log.serilog) instead. See #5497. Events are no longer stamped with sentry.dotnet.serilog, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: the NLog target no longer sets the SDK name Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The target identifies itself through the log origin (auto.log.nlog) instead. See #5497. Events are no longer stamped with sentry.dotnet.nlog, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. With no remaining callers, Constants is deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: the log4net appender no longer sets the SDK name Sdk.Name should identify the integration that initialised the hub, which after this change can no longer be a logging integration. The appender identifies itself through the log origin (auto.log.log4net) instead. See #5497. Events are no longer stamped with sentry.dotnet.log4net, and structured logs no longer carry it as sentry.sdk.name; both now report the SDK that initialised Sentry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: structured logs from Microsoft.Extensions.Logging no longer 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> * feat(serilog): configuring a DSN on the sink now fails with a migration error Serilog configuration providers bind sink arguments by parameter name, so removing the dsn-first overload made them drop `dsn` silently: the sink still binds, Sentry is never initialized, and nothing is reported. Keeping the overload as an [Obsolete(error: true)] tombstone that throws makes both Serilog.Settings.Configuration (appsettings.json) and Serilog.Settings.AppSettings (app.config) fail loudly with migration guidance, while code callers get a compile error instead of a type mismatch on the second argument. The overload mirrors the surviving overload's parameters plus `dsn`. With only `string dsn` it loses Serilog's overload ranking whenever a configuration supplies two or more of the surviving arguments, which would restore the silent behaviour. Part of #5245 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(serilog): pin the DSN tombstone against Serilog.Settings.Configuration The migration guard works only because of Serilog's overload ranking, and nothing exercised that path. These tests bind a sink from IConfiguration the way a provider does, so a Serilog change that stops selecting the tombstone fails here rather than silently dropping the DSN again. Verified they fail without the tombstone overload. Selection behaves the same on Serilog.Settings.Configuration 3.4.0 (Serilog 2.12) and 10.0.1 (Serilog 4.3); 3.4.0 is referenced to avoid bumping Serilog in the tests. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(nlog): configuring a DSN on the target now fails with a migration error Mirrors the Serilog guard (#5611). The v6 AddSentry(dsn, ...) overloads and the SentryTarget.Dsn / InitializeSdk properties come back as tombstones: obsolete-as-error for code callers, throwing NotSupportedException so NLog.config bindings fail loudly with migration guidance instead of reporting an unknown property. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(serilog): reword the DSN migration error Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(nlog): reword the DSN migration error to match Serilog Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(log4net): configuring a DSN on the appender now reports a migration error Brings back SentryAppender.Dsn as a tombstone: [Obsolete(error: true)] with a setter that throws NotSupportedException carrying migration guidance, so code callers get a compile error and XML configs report the message instead of log4net's "Cannot find Property [Dsn]". Unlike Serilog and NLog, this cannot fail configuration loading: log4net catches exceptions thrown while setting a parameter, so the appender is still attached and the message surfaces through log4net's internal logging. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(mel): configuring a DSN through the logging integration now fails 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> * fix(mel): only treat a supplied Dsn or InitializeSdk as a migration error 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> * fix(serilog): the Sentry sink registers the Serilog scope event processor automatically (#5612) * fix: make the SentryOptions processor collections thread safe SentryClient enumerates these collections lazily for the whole duration of a capture, and AddEventProcessor is documented as supporting registration after the SDK is initialised. They were plain Lists, so appending to one while a capture was in flight threw InvalidOperationException - which the SDK catches and logs at Debug, silently dropping the event. Swap them for ConcurrentBagLite, which snapshots on enumeration. Scope.EventProcessors already uses it for the same reason. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(serilog): register the Serilog scope event processor automatically The sink no longer initialises the SDK, so integrators have to call UseSerilog() on the options used to initialise Sentry. Forgetting it was only reported as a warning gated behind Debug and DiagnosticLevel, so in practice it was silent. The sink now registers SerilogScopeEventProcessor itself: at construction when Sentry is already initialised, otherwise on the first log event. The sink and the processor live in the same assembly, so no reflection is needed and this stays AOT safe. UseSerilog() is still the better option - it applies from the first event rather than from the first log line - and the warning now says so. Also fixes a feedback loop this exposed. Emit answered a reentrant log event with another diagnostic, which Serilog routed straight back into the sink, each message embedding the last. With DiagnosticLevel at Info that produced 55 MB of logs in 17 seconds and the app stopped serving requests. The SDK-namespace filter that breaks the cycle now runs before the reentrancy check instead of after it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Removed unnecessary comments Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com> * fix(serilog): register the scope event processor atomically Sinks sharing one set of SentryOptions can reach registration concurrently - each sink's guard is per-instance - so the check and the add have to happen under a lock, not as check-then-act. The sink now learns from the result whether it was the one that registered, which is what the warning reports. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(serilog): use the Lock shim for the registration lock Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Tidy comments Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com> * docs: samples are exempt from the no-comments rule Restores the DSN comment dropped from the Serilog sample's appsettings.json, pointing at where this sample actually sets it, and records in AGENTS.md that "prefer no comments" covers the library rather than samples - including their JSON configuration files. Part of #5245 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Apply suggestion from @jamescrosswell * feat(serilog): warn at runtime when the sink drops events because Sentry is not initialized The tombstoned overloads catch everyone who passes a DSN to the sink, but they cannot see the `WriteTo.Sentry(o => ...)` callback that only sets sink options and gets its DSN from SENTRY_DSN or a [Dsn] assembly attribute. On 6.x that overload initialized the SDK itself; now it compiles, nothing calls Init, and the sink drops everything silently. Warn once, on the first event at or above MinimumEventLevel, when the hub is disabled and a DSN can still be found. There is no DiagnosticLogger to write to in that state, so the warning goes to Serilog's SelfLog and to standard error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(nlog): warn at runtime when the target drops events because Sentry is not initialized Mirrors the Serilog sink: the tombstoned Dsn/InitializeSdk properties cannot see an AddSentry(o => ...) call that only sets target options and gets its DSN from SENTRY_DSN or a [Dsn] assembly attribute, so warn once on the first event at or above MinimumEventLevel when the hub is disabled and a DSN can still be found. The warning goes to NLog's InternalLogger and to standard error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(log4net): warn at runtime when the appender drops events because Sentry is not initialized Mirrors the Serilog sink and the NLog target: the tombstoned Dsn property cannot see an appender that is configured with only appender settings and gets its DSN from SENTRY_DSN or a [Dsn] assembly attribute, so warn once on the first event that would have become a Sentry event when the hub is disabled and a DSN can still be found. The warning goes to log4net's LogLog and to standard error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat(mel): warn at runtime when the integration drops events because Sentry is not initialized Completes the cascade from Serilog, NLog and log4net: the tombstoned Dsn/InitializeSdk properties cannot see an AddSentry(o => ...) call that only sets logging options and gets its DSN from SENTRY_DSN or a [Dsn] assembly attribute, so warn once on the first log event that would have become a Sentry event when the hub is disabled and a DSN can still be found. Microsoft.Extensions.Logging has no self-diagnostics channel, so the warning only goes to standard error. The warning is owned by SentryLoggerProvider so that it is shared by every category's logger rather than repeated per category. SentryLogger.Log now reads IHub.IsEnabled once per call instead of twice, via the level-only IsEnabledForLevel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(mel): match the other integrations' migration-error phrasing The MEL tombstone said "(or an integration such as UseSentry)" where Serilog, NLog and log4net all say "(or UseSentry via one of the integrations)". Same wording everywhere now. Only Sentry.Extensions.Logging's API snapshots carry the message, since the tombstoned members are on SentryLoggingOptions. Net4_8 can't regenerate on macOS, so it got the same substitution by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: UseSentry now initializes Sentry even when Logging.AddSentry ran first The logging integration registers a non-initializing Func<IHub>, and the initializing one was registered with TryAdd, so whichever ran first won. Calling builder.Logging.AddSentry() before UseSentry therefore left the SDK disabled with no indication, where v6 failed loudly at startup with "You must supply a DSN". The initializing registration now replaces any existing accessor, so the order of the two calls no longer matters. Tests cover both orders; the AddSentry-first one fails without this change. Calling both is still redundant and still registers two pairs of logger providers, which is unchanged from v6. Reported by Cursor Bugbot on #5595. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(log4net): Run the configuration binding tests against log4net 3 (#5651) The V3 test project links the other test files added in #5592, but not SentryAppenderConfigurationBindingTests. That file didn't compile against log4net 3, which marks the XmlElement parameter of XmlConfigurator.Configure as non-nullable. With a null-forgiving operator on DocumentElement it compiles, so the V3 project now links it and the Dsn tombstone behavior is pinned on both log4net versions. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> * fix(nlog): report a stale dsn and accept initializeSdk=false (#5652) * fix(nlog): Report a stale dsn and accept initializeSdk=false Both tombstones could leave an upgraded app worse off than it needed to be. A dsn left in NLog.config was silent with NLog's default settings. NLog swallows the setter's exception unless throwConfigExceptions is on, so the target attached, Sentry was never initialized and nothing was printed. The runtime warning also stayed quiet, because it only looks for a DSN in the environment or an assembly attribute. The Dsn setter now writes the migration message to standard error before it throws. initializeSdk="false" was the recommended v6 setting next to UseSentry, and it already matches the new behavior. With throwConfigExceptions on, it still threw, NLog rejected the whole configuration and the app lost every NLog target. The setter now only throws for true, like the Microsoft.Extensions.Logging tombstone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(nlog): report a stale initializeSdk="true" as well The stale dsn message only came from the Dsn setter, so a config carrying initializeSdk="true" instead was still silent with NLog's default throwConfigExceptions: the setter threw, NLog discarded it, the target attached and nothing was printed. Both setters now go through one report-and-throw helper. Reporting from both setters means a v6 config carrying dsn and initializeSdk="true" together would print the same message twice, so the helper reports at most once per target. A configuration reload builds a new target, and so reports again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com> * test: keep the real environment out of the disabled-SDK tests SentryConstants.DisableSdkDsnValue is an empty string, and SettingLocator.GetDsn treats an empty Dsn as unset and falls back to SENTRY_DSN. So on a machine with that variable set, these two tests initialised a working SDK and captured the event they assert is never captured. FakeSettings() swaps in a locator that reads only from its own dictionary, so the fallback can't reach the real environment. Both tests now pass with and without SENTRY_DSN set. The fallback itself predates this PR and is also on main: options.Dsn = "" does not disable the SDK when SENTRY_DSN is set. Reported by @ric-oliv on #5595. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * feat: read core SDK settings only from the Sentry configuration section The logging integrations no longer initialize the SDK, so the logging provider section is no longer a place where a DSN or any other core SDK setting has an effect. Reading them there would silently ignore them. The root 'Sentry' section now configures the whole of SentryHostOptions, and 'Logging:Sentry' configures only the logging half. Any other key there throws, naming the offending keys, so an app that upgrades with core settings in the logging section fails at startup instead of reporting nothing. A single SentryLoggingConfiguration binds the logging section for both the standalone MEL options and the host options, on every target framework. That replaces BindableSentryLoggingOptions and the separate netstandard2.0 reflection path. The integrations that initialize Sentry read 'Logging:Sentry' from the app's configuration rather than through ILoggerProviderConfiguration: an app that passes the whole configuration to AddConfiguration, as Google Cloud Functions does, makes the root 'Sentry' section part of the provider section, and every core setting in it would then be rejected. Plain MEL keeps the provider configuration, where rejecting core settings is correct. Also fixes ASP.NET Core registering SentryAspNetCoreOptionsSetup twice - once with the 'Sentry' section and once with the logging provider configuration - which applied everything its Configure adds beyond binding twice: two Kestrel deduplication log filters and two TraceIgnoreStatusCodeTransactionProcessor instances. The IConfiguration substitute in SentryWebHostBuilderExtensionsTests returned an empty string for every key, where configuration returns null for an absent one. Returning null removes a NET8_0-only workaround. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(blazor): bind configuration before the UseSentry callback Blazor WebAssembly sets DetectStartupTime, RequestBodyCompressionLevel and IsGlobalModeEnabled because the platform requires them, in the same Configure delegate that runs the UseSentry callback. That delegate was registered before the configuration binding, and IConfigureOptions<T> run in registration order, so a Sentry:DetectStartupTime key in wwwroot/appsettings.json won over the platform default - reintroducing the PlatformNotSupportedException the default is there to avoid - and configuration won over anything set in UseSentry, the opposite of ASP.NET Core. The two setups are now registered first, so precedence runs from the Sentry section, through Logging:Sentry, to the callback and the platform defaults. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Format code * fix(maui)!: options set in UseSentry now take precedence over appsettings.json SentryMauiOptionsSetup both bound the Sentry configuration section and applied the settings MAUI forces (global mode, the debug logger, the screenshot and SDK event processors). It was registered after the UseSentry callback, and IConfigureOptions<T> run in registration order, so a key in appsettings.json overrode the same setting passed to UseSentry - the reverse of ASP.NET Core. Moving the whole setup ahead of the callback would have broken the forced half, which reads Debug and AttachScreenshot as the callback leaves them. The binding now lives in SentryMauiConfigurationOptionsSetup, registered before the callback; SentryMauiOptionsSetup keeps the forced settings and still runs last. Fixes #5657 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io> Co-authored-by: Ricardo Colombo Oliveira <github@ricoliv.com>
…ment EnableLogs belongs in the Sentry section. Since #5595, setting it under Logging:Sentry fails at startup, so the old hint sent readers to the wrong place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Blazor, where logs are opt-in (#5642) * feat(logging): Add EnableLogs to SentryHostOptions ASP.NET Core, Google Cloud Functions, MAUI and Blazor WebAssembly add the MEL integration on the user's behalf, and that isn't consent to send logs. Since #5504, these apps sent every ILogger entry to Sentry. SentryHostOptions now has EnableLogs, default false. It hides the obsolete SentryOptions.EnableLogs and binds from the existing EnableLogs configuration key. SentryStructuredLogger reads it from the hub's options, so Logging.AddSentry() on a hub created by SentrySdk.Init keeps sending logs, like the other logging integrations. With no log envelopes sent by default, the WebIntegrationTests.Versioning snapshots and the Google Cloud Functions SDK-name assertion return to how they were before #5504. Refs #5183 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(samples): Set EnableLogs again in the framework samples Restores EnableLogs = true where #5504 removed it, so these samples keep sending logs: ASP.NET Core Basic, gRPC, MVC, ME.AI and MAUI. The Google Cloud Functions sample already has it again after main was merged into version7. Refs #5183 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Accept API verifier changes * feat(logging)!: Remove SentryOptions.EnableLogs Only the integrations that derive from Sentry.Extensions.Logging (ASP.NET Core, MAUI, Blazor WebAssembly) need the option, so it now lives on SentryHostOptions alone. SentrySdk.Logger and the logging integrations always send logs, and the logs spec dropped the global option in 3.0.0. James Crosswell agreed to remove it in v7. The obsolete base property also made code that set EnableLogs through a SentryOptions reference compile, warn that logs are always enabled, and leave them off on host options. That code now fails to compile instead. NLog's SentryTarget.EnableLogs stays as an obsolete no-op with its own message, so existing NLog.config files still load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(aspnetcore): Check EnableLogs end to end through UseSentry The existing tests build a Hub directly. These run a real UseSentry host, which reaches the gate through HubAdapter, and cover the default, the opt-in in code and the opt-in from the Sentry configuration section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(samples): Clarify what EnableLogs covers in the ASP.NET Core sample Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(nlog)!: Remove SentryTarget.EnableLogs The Sentry target always sends logs, so the option was an ignored no-op. v7 removes SentryOptions.EnableLogs and Serilog's enableLogs parameter, so the NLog target drops its copy too. A leftover enableLogs attribute in NLog.config is ignored with NLog's default settings. With throwConfigExceptions or throwExceptions on, NLog refuses to load the configuration and names the attribute, so the app fails on its first run after the upgrade. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Accept API verifier changes * test: Assert the single log in the EnableLogs opt-in tests ContainSingle with a predicate passed when other logs were also sent. Asserting a single log, then its message, also catches extra logs, such as the SDK's own, and the failure shows the log that arrived. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(nlog): Keep SentryTarget.EnableLogs so v6 configurations still load Removing the property broke NLog.config files that still set enableLogs. With throwConfigExceptions on, NLog rejected the whole configuration, so the app lost every target, not only Sentry. #5652 fixed the same failure for initializeSdk="false". This reverts 8b80e53. The property stays obsolete and ignored, with its own obsolete message, because SentryOptions.ObsoleteEnableLogs is gone. LoadConfiguration_WithEnableLogs_DoesNotThrow loads both values with throwConfigExceptions on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(samples): Drop the Logging section hint from the EnableLogs comment EnableLogs belongs in the Sentry section. Since #5595, setting it under Logging:Sentry fails at startup, so the old hint sent readers to the wrong place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(logging): Declare EnableLogs on the host options, not SentryHostOptions EnableLogs is public on SentryAspNetCoreOptions, SentryMauiOptions and SentryBlazorOptions, the hosts that add the ILogger provider for the user. Each one forwards to an internal flag on SentryHostOptions, so a future host that derives from it only gets the setting if it declares one. Settling this before v7 ships avoids a breaking change later. SentryStructuredLogger still reads the flag from the hub's options, so an explicit Logging.AddSentry() in a host app stays off unless EnableLogs is set. BindableSentryHostOptions keeps binding EnableLogs from the Sentry section for all three hosts. The remarks no longer mention SentrySdk.Logger and the logging integrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io> Co-authored-by: James Crosswell <jamescrosswell@users.noreply.github.com>

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) - #5595Logging:Sentry) now only configures the logging integration. Core SDK settings there, such asDsn, no longer have any effect and fail at startup with migration guidance: move them to theSentrysection, or pass them where Sentry is initialized - #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.Logging:Sentrybinds MEL's ownLogLevelrules plusMinimumEventLevelandMinimumBreadcrumbLevel; any other key there throwsNotSupportedExceptionnaming the offending keys. Core SDK settings still bind from the rootSentrysection for the integrations that initialize Sentry, which is where every sample and docs guide already puts them.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.AddSentry(o => …)call that only sets logging settings still compiles, and on 6.x it initialized the SDK itself, taking the DSN fromSENTRY_DSNor a[Dsn]assembly attribute. The integration therefore warns at runtime — on the first log event that would have become a Sentry event, if Sentry is not initialized but a DSN can still be found, it writes one line to standard error, at most once per logger provider.Microsoft.Extensions.Logginghas no self-diagnostics channel, so standard error is the only one available; the shared policy lives inSentry.Internal.UninitializedSdkWarning, added in feat(serilog)!: the Sentry sink no longer initializes the SDK #5573.SentryLogger.Logalso now readsIHub.IsEnabledonce per call instead of twice.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.UseSentryregisteredSentryAspNetCoreOptionsSetuptwice, once with the rootSentrysection and once with the logging provider configuration (Logging:Sentry). Different sections, but the same setup class, so everything itsConfigureadds beyond binding was applied twice: two Kestrel deduplication log filters and twoTraceIgnoreStatusCodeTransactionProcessorinstances. The setup now binds theSentrysection once, and the logging section gets its own setup that only applies logging settings. On netstandard2.0 the section was bound by a plainConfigure<SentryAspNetCoreOptions>(section)call and the setup ran once, so the extras were already applied once there; that branch is gone, and the setup now runs once on every target.DetectStartupTime,RequestBodyCompressionLevelandIsGlobalModeEnabledare set because Blazor WASM requires them (System.Diagnostics.Processisn't supported there, andHttpClientHandlercan't compress), but they are applied in the sameConfiguredelegate as theUseSentrycallback, which was registered before the configuration binding.IConfigureOptions<T>run in registration order, so aSentry:DetectStartupTimekey inwwwroot/appsettings.jsonwon over the platform default, reintroducing thePlatformNotSupportedExceptionit exists to avoid - and configuration won over anything set inUseSentry, the opposite of ASP.NET Core. The setups are now registered first, so precedence runs from theSentrysection, throughLogging:Sentry, to the callback and the platform defaults.version7has the same inversion, where the genericAddSentry<SentryBlazorOptions>(delegate)registered the callback before its options setup.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
UseSentryno longer loses aTryAddrace withLogging.AddSentry(). The logging integration registers a non-initializingFunc<IHub>, so whichever call ran first used to win. Callingbuilder.Logging.AddSentry()beforeUseSentryleft the SDK disabled with no indication, where v6 failed loudly at startup with "You must supply a DSN". The initializing registration now replaces any existing accessor, so order no longer matters; both orders are tested. Calling both is still redundant and still registers two pairs of logger providers, unchanged from v6. MAUI was never affected, since it initializes inSentryMauiInitializerrather than through the hub accessor.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. A single
SentryLoggingConfigurationnow binds the logging section on every target framework: it applies the two levels, ignores MEL's ownLogLevelrules, and throws for anything else - the obsolete-as-error message forDsnorInitializeSdk: true, and a message naming the offending keys otherwise. That replacesBindableSentryLoggingOptionsand the separate netstandard2.0 reflection path, which is what made the tombstone setters value-dependent (the configuration binder writes each property's own value back, and the unconditional version broke Windows CI earlier on this branch). The setters stay value-dependent, since anyone can still callconfiguration.Bind(options)themselves.Which configuration each integration reads. The integrations that initialize Sentry read
Logging:Sentryfrom the app's configuration rather than throughILoggerProviderConfiguration, because an app that passes the whole configuration toAddConfiguration- asSentry.Google.Cloud.Functionsdoes - makes the rootSentrysection part of the provider section, and every core setting in it would then be rejected. Plain MEL keeps the provider configuration, where rejecting core settings is right: nothing else binds them ontoSentryLoggingOptions, so they really would be ignored.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.Sentrysection configures both halves of the host options, the logging section configures only the logging half, a core setting there throws, and the section's extras are applied once.AddConfigurationstill gets its rootSentrysection bound rather than rejected.SentryLoggingOptionsis a key the logging section accepts, so a new logging option can't be mistaken for an SDK setting.UseSentrycallback wins over configuration while configuration still applies to what the callback leaves alone.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.samples/Sentry.Samples.GenericHostkept its log levels in the rootSentrysection and passed the whole configuration tobuilder.Logging.AddConfiguration, which is how they reached the logging integration. They now live underLogging:Sentry, and that call is gone.SentryWebHostBuilderExtensionsTests'IConfigurationsubstitute returned""for every key, where configuration returnsnullfor an absent one. It now returnsnull, which removed a#if NET8_0-only workaround inUseSentry_Logging_AddLoggerProviders.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