Skip to content

feat: Microsoft.Extensions.Logging integration no longer initializes the SDK - #5595

Open
jamescrosswell wants to merge 11 commits into
feat/no-init-from-logging-log4net-5245from
feat/no-init-from-logging-mel-5245
Open

jamescrosswell wants to merge 11 commits into
feat/no-init-from-logging-log4net-5245from
feat/no-init-from-logging-mel-5245

Conversation

@jamescrosswell

@jamescrosswell jamescrosswell commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

The Microsoft.Extensions.Logging portion of #5245, stacked on #5592 (log4net) and following the same design. AddSentry now only wires up the logger providers; Sentry has to be initialized separately via SentrySdk.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

  • The Microsoft.Extensions.Logging integration no longer initializes the SDK. Sentry must now be initialized separately from the logging integration (using SentrySdk.Init or UseSentry) - #5595
  • Blazor WebAssembly's logger now respects the MinimumEventLevel, MinimumBreadcrumbLevel, log entry filters and ConfigureScope callbacks set in UseSentry - #5595

Breaking changes

  • SentryLoggingOptions no longer derives from SentryOptions, matching the Serilog and NLog options. It carries only MinimumBreadcrumbLevel, MinimumEventLevel and log entry filters, so builder.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.ConfigureScope is removed. Call SentrySdk.ConfigureScope after initializing Sentry.
  • ILoggingBuilder.AddSentry(string dsn) no longer initializes Sentry. It is kept as an obsolete-as-error tombstone that throws NotSupportedException with migration guidance, so both code callers and reflection-based callers fail loudly.
  • ILoggerFactory.AddSentry(…) no longer initializes Sentry, replaces the current hub, or assigns a MelDiagnosticLogger as the SDK's DiagnosticLogger.
  • InitializeSdk no longer has any effect. SentryLoggingOptions.Dsn and SentryLoggingOptions.InitializeSdk are kept as tombstones that throw when set, including when they are bound from the Sentry configuration 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, SentryMauiOptions and SentryBlazorOptions now derive from a new abstract SentryHostOptions : SentryOptions. They still have MinimumBreadcrumbLevel, MinimumEventLevel, ConfigureScope and AddLogEntryFilter, and the same keys still bind from the Sentry configuration section, so existing UseSentry callbacks and appsettings.json files keep working. Code that treats them as a SentryLoggingOptions no longer compiles.
  • ServiceCollectionExtensions.AddSentry<TOptions> now requires TOptions : SentryHostOptions.
  • builder.Logging.AddSentry() no longer registers SentryOptions in the service collection, so resolving SentryOptions from 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.
  • ConfigureScope callbacks on the framework options now run when Sentry is initialized, rather than when the Sentry logger provider is first created.

Before:

var builder = Host.CreateApplicationBuilder();
builder.Logging.AddSentry("https://key@sentry.io/1");

After:

var builder = Host.CreateApplicationBuilder();

using var sentry = SentrySdk.Init(o => o.Dsn = "https://key@sentry.io/1");

builder.Logging.AddSentry();

Fixes

  • Blazor WebAssembly's logger ignored the logging settings in UseSentry. Blazor registered the plain MEL logger provider, which dependency injection built from a separate, default IOptions<SentryLoggingOptions> rather than the SentryBlazorOptions configured in UseSentry. MinimumEventLevel, MinimumBreadcrumbLevel, log entry filters and ConfigureScope were all silently ignored by the logger; SDK initialization itself was unaffected. The same bug exists on main.
  • Structured logs from plain MEL took default attributes from the wrong options. This came from the first commit on this branch: sentry.environment, sentry.release and server.address were read from SentryLoggingOptions, which the SDK was no longer initialized with. The structured logger now reads them from the hub.

Notes for review

  • Why SentryHostOptions. SentryLoggingOptions was doing two jobs: configuring the MEL logger, and acting as the base class for integrations that initialize the SDK. Splitting them is what lets SentryLoggingOptions go standalone. The host options pass the log levels and filters through to an inner SentryLoggingOptions instance (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.Logging because 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.

  • ConfigureScope timing. For MAUI and Blazor, the only thing that applied these callbacks used to be the MEL logger provider's constructor, gated on hub.IsEnabled when the provider was built. They're now applied right after init: in AddSentry<TOptions>'s hub factory (ASP.NET Core, Blazor) and in SentryMauiInitializer (MAUI). ASP.NET Core still also applies them per request in SentryMiddleware, and gRPC in its interceptor.

  • SDK name. SentryLoggerProvider no longer stamps Sdk.Name/Sdk.Version, pushes a scope, or disposes the hub. Per Metrics and SentrySdk.Logger logs emitted during a request carry no sentry.sdk.name/sentry.sdk.version on 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 Sentry section binds through BindableSentryLoggingOptions, which now carries Dsn/InitializeSdk and throws when either is present; on netstandard2.0 the configuration binder sets the properties directly and the tombstone setters throw. SentryLoggingOptionsTests skips those two, since BindableTests otherwise asserts that every bindable property round-trips onto the options.

  • InitializeSdk. MAUI initializes in SentryMauiInitializer, so it calls an internal non-initializing overload of AddSentry<TOptions> instead of setting a flag. Tests that set InitializeSdk = false to avoid initializing now use DisableSdkDsnValue. UseSentry_OptionsNotInitializeSdk_DisabledSdk tested the flag itself and is deleted; UseSentry_DisableDsn_DisabledSdk still covers a disabled SDK.

  • Blazor registrations. UseSentry now delegates to an internal ILoggingBuilder extension so it can be unit tested (WebAssemblyHostBuilder needs 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 small SentryHostOptionsSetup<TOptions> in Sentry.Extensions.Logging, which has the configuration-binding source generator enabled, since Blazor WASM is trimmed.

  • New tests.

    • The DI init path runs ConfigureScope callbacks, and the non-initializing path doesn't.
    • MAUI ConfigureScope data reaches events. Nothing tested that before.
    • The Blazor logger respects MinimumEventLevel.

    Each test was checked to fail with its fix reverted.

  • The ApplyDefaultTags tests only used SentryLoggingOptions as a vehicle for a core SentryOptions method; they moved to SentryOptionsTests.

  • The structured-logger tests now give their mocked hub options through SentryOptionsForTestingOnly and 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, so samples/Sentry.Samples.GenericHost now calls SentrySdk.Init directly. 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_8 can'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

…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

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.18605% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.65%. Comparing base (ba78b29) to head (a082174).

Files with missing lines Patch % Lines
...or.WebAssembly/WebAssemblyHostBuilderExtensions.cs 86.66% 2 Missing ⚠️
.../Sentry.Extensions.Logging/SentryLoggingOptions.cs 75.00% 1 Missing and 1 partial ⚠️
...entry.Extensions.Logging/SentryStructuredLogger.cs 75.00% 0 Missing and 1 partial ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

… 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>
@jamescrosswell

Copy link
Copy Markdown
Collaborator Author

Some background for reviewers on why this PR splits the options class.

Initialising Sentry

Generally Sentry's integrations fall into two camps:

  1. Integrations for features (e.g. databases)
  2. Integrations for platforms (e.g. ASP.NET Core or MAUI)

As a rule, we provide UseSentry extensions on the Builders for the platforms that let you initialise the Sentry SDK at the same time as configuring the Sentry integration for those platforms. We pass a Sentry DSN into the UseSentry method which is used to initialise a Sentry Hub and Client that will be used to send captured information (errors, traces etc.) to the SDK user's Sentry project... and typically there are a bunch of other options specific to that platform that can be set at the same time.

For people not using any specific platform, they can instead initialise Sentry by calling SentrySdk.Init.

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 way

In version 6 the options classes look something like this:

classDiagram
    SentryOptions <|-- SentryLoggingOptions
    SentryLoggingOptions <|-- SentryAspNetCoreOptions
    SentryLoggingOptions <|-- SentryMauiOptions
    SentryLoggingOptions <|-- SentryBlazorOptions
Loading

SentryLoggingOptions in that diagram does two things: the options type for the MEL integration, and the base class for the ASP.NET Core, MAUI and Blazor options. Those integrations genuinely need to initialise the SDK so they still need an options class that contains a DSN. But in version 7.0 we don't want the options class used to initialise the MEL integration to have a DSN on it since people shouldn't be initialising the Sentry SDK via that integration anymore.

The new way

As such, in version 7.0 we're splitting that options class up:

  • SentryLoggingOptions — now standalone, covering only "which log entries go to Sentry" (two minimum levels plus filters). It no longer inherits SentryOptions, so you cannot set a DSN on it. This is the options class that will be used to initialise MEL in v7.
  • SentryHostOptions (src/Sentry.Extensions.Logging/SentryHostOptions.cs, the new file) — inherits SentryOptions, so it has the DSN, release, sampling and so on, and is the base for integrations that own initialisation. It also exposes the log-level settings, forwarding them to a SentryLoggingOptions it holds internally, so existing UseSentry(o => o.MinimumEventLevel = ...) code and appsettings.json keep working unchanged... we avoid breaking people's existing code.
classDiagram
    SentryOptions <|-- SentryHostOptions
    SentryHostOptions <|-- SentryAspNetCoreOptions
    SentryHostOptions <|-- SentryMauiOptions
    SentryHostOptions <|-- SentryBlazorOptions
    SentryHostOptions *-- SentryLoggingOptions : holds internally
Loading

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a note on why SentryHostOptions.Logging is a composite property:

  1. SentryLoggingOptions must be a concrete public class. It's what users configure in builder.Logging.AddSentry(o => …), and when using MEL IOptions<SentryLoggingOptions> resolves from DI for the options binding. IOptions<T> is constrained to class, new(), so this can't be an interface.
  2. SentryHostOptions has to be a SentryOptions. It's the object handed to SentrySdk.InitHub... so it can't descend from SentryLoggingOptions - 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(
}
}
});

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});

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.

jamescrosswell and others added 3 commits September 22, 2026 17:08
…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>
… 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>
jamescrosswell and others added 6 commits September 23, 2026 11:47
…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
@jamescrosswell
jamescrosswell marked this pull request as ready for review September 24, 2026 02:54
@github-actions github-actions Bot added the risk: high PR risk score: high label Sep 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: high PR risk score: high

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant