Repository navigation
Generic host: ClearProviders() after UseSentry silently disables the ILogger integration, or the SDK entirely #5755
Description
Problem
UseSentry in the new Sentry.Extensions.Hosting package does not initialise the SDK directly. It registers services, and initialisation happens the first time Func<IHub> is resolved. Two things resolve it:
SentryHostedService— its constructor does_ = services.GetRequiredService<Func<IHub>>(). Runs when the host starts.SentryLoggerProvider— its factory callsGetRequiredService<IHub>(), so it initialises Sentry as a side effect wheneverILoggerFactoryis first materialised.
ILoggingBuilder.ClearProviders() is services.RemoveAll<ILoggerProvider>(). Called after UseSentry, it removes both of Sentry's logger providers — and with them trigger 2 and the whole ILogger → Sentry bridge. The Func<IHub> registration and the IHostedService registration are not ILoggerProviders, so they survive. The result is a partial, silent failure.
Happy path — ClearProviders() before UseSentry
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.UseSentry(options => options.Dsn = "..."); // registered after the clear
var host = builder.Build();
await host.RunAsync();Sentry's logger providers survive, so logger.LogError(...) reaches Sentry and the SDK has two independent initialisation triggers.
Unhappy path 1 — ClearProviders() after UseSentry, host started
var builder = Host.CreateApplicationBuilder(args);
builder.UseSentry(options => options.Dsn = "..."); // registers the logger providers
builder.Logging.ClearProviders(); // ...and this removes them
builder.Logging.AddConsole();
var host = builder.Build();
await host.RunAsync();The SDK initialises, because SentryHostedService still runs at host start. Unhandled exceptions and explicit SentrySdk.CaptureException work as expected. But the ILogger integration is gone: logger.LogError(...) produces no event, no breadcrumbs and no structured logs. Nothing in the output indicates this.
Unhappy path 2 — same, but the host is never started
var builder = Host.CreateApplicationBuilder(args);
builder.UseSentry(options => options.Dsn = "...");
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
var host = builder.Build(); // no Sentry logger provider -> nothing resolves Func<IHub>
// no StartAsync/RunAsync -> SentryHostedService is never constructed
try
{
await DoWork(host.Services);
}
catch (Exception e)
{
SentrySdk.CaptureException(e); // no-op: the hub was never initialised
Environment.Exit(1);
}Both triggers are gone, so the SDK never initialises and nothing is reported at all. Building a host and resolving services off it without starting it is a normal shape for CLI-style and scheduled-job apps.
Why this is easy to hit
In 6.x, the initialisation registration and the logger provider registration were the same call on the same ILoggingBuilder:
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddSentry(options => options.Dsn = "..."); // naturally lands after the clear
})Clear-then-add is the obvious way to write that, so Sentry's registration came last and survived.
v7 splits the two across levels — UseSentry on the host builder, ClearProviders() on the logging builder — which inverts the natural order. The 7.0.0 upgrade notes say to "initialize Sentry first with SentrySdk.Init, or UseSentry", and samples/Sentry.Samples.GenericHost puts builder.UseSentry(...) at the top of the chain. Someone upgrading a worker service that clears providers, and following that guidance, lands on unhappy path 1.
Impact
| Order | Host started | 6.x | 7.0.0-rc.2 |
|---|---|---|---|
ClearProviders() → Sentry |
either | works | works |
Sentry → ClearProviders() |
started | SDK never initialises | SDK initialises; ILogger bridge silently gone |
Sentry → ClearProviders() |
never | SDK never initialises | SDK never initialises |
The middle row is the regression that worries me: an app that captured logger.LogError events in 6.x stops doing so, with no error, no warning and no change the author associates with Sentry.
test/Sentry.Extensions.Hosting.Tests/SentryHostedServiceTests.cs has StartAsync_LoggerProvidersCleared_SdkRunsBeforeAppHostedServiceStarts, which covers exactly this configuration and asserts that the SDK starts — but not that the logging integration survived the clear, which is the part that breaks.
Possible improvements
- Warn when it happens. In
SentryHostedService.StartAsync, check whether aSentryLoggerProviderstill resolves, and log a warning whenUseSentryran but the providers are gone. That turns both unhappy paths into diagnosable ones, and fits beside the existing warning for a web app missing the ASP.NET Core integration. - Make initialisation independent of logger providers and of host start.
Host's constructor resolvesIOptions<HostOptions>, so anIConfigureOptions<HostOptions>registered byUseSentrywould fire duringBuild()in both cases above. It works, but it leans on a hosting implementation detail and would fire even when a host is built only to inspect its services — worth weighing against simply documenting that the SDK starts when the host starts. - Re-register the providers defensively, so a later
ClearProviders()cannot leaveUseSentryhalf-applied. - Document the ordering constraint on the generic host page, and state that initialisation happens at host start — so an app that never starts its host knows to call
SentrySdk.Initor start the host.
Found by source inspection against the 7.0.0-rc.2 tag while dogfooding the release; happy to put up a runnable repro for all three cases if that would help.
Activity
linear-code commentedon Oct 20, 2026
NET-876
jamescrosswell commentedon Oct 20, 2026
Reproduced against
Sentry.Extensions.Hosting7.0.0-rc.2 on .NET 9. One program, three scenarios,BeforeSendreturningnullso nothing leaves the process.Output:
Reading the three:
IsEnabledis already true afterBuild(), because materialisingILoggerFactoryconstructsSentryLoggerProvider, which resolvesIHub. Both probes are captured.IsEnabledis false afterBuild()and true afterStartAsync(), soSentryHostedServiceis doing the initialising. Only one of the two probes is captured:SentrySdk.CaptureExceptionworks, andlogger.LogError(...)produces nothing. That is the silent half — the SDK looks healthy and theILoggerintegration is gone.SentrySdk.CaptureExceptionis a no-op.Also confirms the environment bridge is working as intended in all cases where the SDK runs:
EnvironmentName = "Staging"on the host comes out asenvironment=stagingon the event, with noSENTRY_ENVIRONMENTset.Happy to turn the
unhappy1case into a failing test againstSentryHostedServiceTestsif you'd like it alongside the existingStartAsync_LoggerProvidersCleared_SdkRunsBeforeAppHostedServiceStarts— asserting that aSentryLoggerProviderstill resolves, which is the assertion that would have caught this.chore: upgrade to Sentry 7.0.0-rc.2 (#146)
feat: source maps, readable releases, logs, replay and user feedback (#…
ric-oliv commentedon Oct 21, 2026
Thanks for the repro, James! I ran the same scenarios against the 7.0.0-rc.2 packages on net10, with both
IHostApplicationBuilderandIHostBuilder, and got the same results. I also tried a few more setups, and two of them change how I'd fix this.Build()UseSentrythenClearProviders(), host startedStartAsyncCaptureExceptionUseSentrythenClearProviders()StartAsyncCaptureExceptionStartAsyncCaptureExceptionServices.AddSerilog(), noClearProviders(), host never startedUnhappy path 1 isn't new
Sentry.AspNetCore already behaves this way in 6.x. In 6.12.1 it drops the
ILoggerintegration exactly like the generic host does in rc.2. That makes sense, sinceClearProviders()removes every provider registered before it, ours included. So the generic host now matches ASP.NET Core. The regression only shows up during migration. Moving fromLogging.AddSentry()toUseSentrytends to put the Sentry call aboveClearProviders().Sometimes removing our provider is the right result
NLog's getting-started guide calls
builder.Logging.ClearProviders()and thenbuilder.Host.UseNLog(). In v7 those apps also needUseSentryorSentrySdk.Init, because the NLog target no longer starts the SDK. I tried that shape withAddNLog()and the Sentry NLog target:With
UseSentrylast, the error with an exception arrives once, because duplicate detection drops the second copy. The message-only error arrives twice. So re-registering our providers defensively, option 3, would cause duplicates for these apps. A warning on the host logger would also fire on a setup that's working as intended.Unhappy path 2 isn't about
ClearProviders()Services.AddSerilog()replaces the logger factory, so our providers never get created there either. If that app builds its host and never starts it, the SDK never starts. That happens withUseSentryin its usual place and noClearProviders()call at all. It's the gap we accepted in #5739, where the SDK starts with the host when logging is replaced. One small thing on option 1 too. The hosted service never runs in path 2, so a warning there can't cover it.What I'd suggest
ClearProviders()beforeUseSentry, explain when the SDK starts, and show NLog and log4net users the setup that avoids duplicates.Debug = truewhen someone asks why theirLogErrorcalls are missing, and it doesn't bother the NLog apps.IConfigureOptions<HostOptions>hook does start the SDK duringBuild()on both builders. It would close path 2 and the Serilog case. It still relies onHostreadingHostOptionsin its constructor, and I haven't tried it on the Microsoft.Extensions.Hosting 3.1 floor we use for netstandard2.0. I'd rather decide it separately than squeeze it into this issue.About the test, I'd rather not assert that a
SentryLoggerProviderstill resolves afterClearProviders(), since the NLog setup shows that removing it can be correct. What do you think about a test for the diagnostic message instead? I'm happy to pick up the docs and the warning if that sounds good to you.jamescrosswell commentedon Oct 22, 2026
OK agreed on the docs, agreed on the option 1 check via the diagnostic logger, agreed that option 2 deserves its own issue, and agreed on dropping option 3 — defensive re-registration does cause duplicates for the NLog apps.
However, I'm wondering if we should actually take a different tack and reverse one of our earlier design decisions.
Alternative - reintroduce
builder.Logging.AddSentry()What if
UseSentrystopped registering theILoggerbridge, and the bridge stayed where the other logging integrations live?That gives us:
UseSentrybecomes order-independent, because nothing a logging call does can remove the SDK. Path 2 viaClearProvidersstops existing, and theAddSerilogcase is unaffected by it too (though it still needs option 2 to initialise duringBuild()).Logging.AddSentry(). No ordering trick, noMinimumEventLevel: None, no heuristic for detecting a configured target, no warning that fires on correct setups.AddSentry()vsClearProviders()is two calls on the same builder, three lines apart, behaving exactly likeAddConsole(). Nobody is surprised that clearing providers after adding one removes it.HostLoggerProvidersMarkerandRedundantWithHostIntegrationgo away, since nothing is competing for the bridge any more.It would also simplify how we explain this to customers. Separating the concerns makes it one rule — structured logs come from whatever logging integration you added, configured through that integration — and
EnableLogscan come off all four host options rather than needing an explanation in the docs about when it matters and when it doesn't.It feels like a cleaner design but it's not without some complications...
UseSentrytoday.AddFilter<SentryAspNetCoreStructuredLoggerProvider>("Sentry.AspNetCore.SentryMiddleware", LogLevel.None), which is there to stop a feedback loop.builder.Logging.AddSentry()would have to pick the right provider based on the registered options type... and it has to be done without circular dependencies. That's possible, I think - just might require some trickery.EnableLogsonly ever gated structured logs; creating events and breadcrumbs from logs has always happened automatically without needing to be enabled explicitly. If we go down this path, an app that only callsUseSentrywould stop turninglogger.LogErrorinto issues... quite a big call.Worth desiding up front whethere we want to go down this route or try to patch up the design we already have .
jamescrosswell commentedon Oct 22, 2026
Alternatively, something to complete/complement version of your option 1...
The natural fix for unhappy path 1 — "my
LogErrorcalls stopped reaching Sentry, let me add the logging integration back" — does not work:HostLoggerProvidersMarkeris registered withTryAddSingletonand isn't anILoggerProvider, soClearProviders()removes the providers but leaves the marker behind.Logging.AddSentry()then sees the marker, concludes it would be duplicating providers that no longer exist, installsNullLoggerProvidertwice, and logsRedundantWithHostIntegration— telling the user their fix is unnecessary.So today the only remedy is reordering the calls. That makes the docs change harder than it looks: there's nothing else we can tell someone who has already hit it.
Potential fix
LoggingBuilderExtensionsusesHubAdapter.Instancerather thanGetRequiredService<IHub>()per #103, so it never captures a hub. That means the recovered bridge forwards to the current hub but initialises nothing, unlike the host integration's own provider.So fixing the marker alone leaves an app in unhappy path 2 with a perfectly good bridge to a disabled SDK.
Part 1 — initialisation stops depending on logger providers
You mentioned you'd checked that an
IConfigureOptions<HostOptions>hook starts the SDK duringBuild()on both builders. That's the piece:Resolving
Func<IHub>is already what initialises the SDK. This only pulls it forward toBuild()instead of leaving it to whichever ofSentryHostedServiceorSentryLoggerProvidergets there first.SentryHostedServicestays as it is, since it's what registersApplicationStopped -> SentrySdk.Closefor started hosts.This closes unhappy path 2 and your
AddSerilogcase, neither of which depends on a logger provider any more.Part 2 — the marker shares the lifetime of what it stands for
and
Logging.AddSentry()tests the service collection instead of resolving the marker:The capture is load-bearing. A check performed while
AddSentryruns would break theAddSentry()-before-UseSentryordering and send everything twice — whichLoggingAddSentry_BeforeUseSentry_DoesNotSendErrorsTwicealready pins. Deferring to resolution keeps that order-independent while still noticing the clear.Effect
ClearProviders()thenUseSentryUseSentrythenClearProviders(), host startedILoggerdead,AddSentry()cannot fix itILoggerdead,AddSentry()fixes itUseSentrythenClearProviders(), host never startedAddSentry()restoresILoggerServices.AddSerilog(), host never startedYour NLog and log4net apps are untouched throughout: they don't call
Logging.AddSentry(), so nothing re-registers our provider and there are no duplicates. No detection heuristics and no opt-out option needed.I prototyped part 2 against
version7to check the mechanism — the two existingDoesNotSendErrorsTwicetests keep passing, and the recovery case passes once the host is started. I haven't put a PR up, since this is stillState: Needs Discussionand it's your call which direction we take.Two things this gives your plan
Build(), so the check can live there instead and cover both paths. It should test the descriptors rather than the resolved providers, since the order in whichHost's constructor resolvesIOptions<HostOptions>versusILogger<Host>isn't something to depend on.ClearProviders()beforeUseSentry", they can say "or addbuilder.Logging.AddSentry()after it" — which is the advice someone who has already shipped the wrong order actually needs.What it still doesn't do
Unhappy path 1's default is unchanged.
UseSentrythenClearProviders()still silently leavesILoggerdisconnected; we'd have made the SDK reliable, the failure diagnosable and the fix effective, but someone still has to notice.So still worth considering the 'reintroduce builder.Logging.AddSentry()' solution above...
docs(dotnet): Apply review suggestions to the v7 migration guide
fix(hosting): start the SDK during Build() when logging is cleared or…
fix(logging): restore host logger providers in Logging.AddSentry()
fix(logging): make Logging.AddSentry() work after UseSentry and Clear…
fix(logging): warn when the app removes Sentry's logger providers
fix(logging): warn when ClearProviders() removes the logger providers…
jamescrosswell commentedon Oct 27, 2026
Closing this — all three parts of the original report are addressed on
version7:UseSentrythenClearProviders()on a host that is never started captured nothing at allSentrySdkInitializerstarts the SDK duringBuild()viaIConfigureOptions<HostOptions>, so initialisation no longer depends on Sentry's logger providers surviving. Also covers a replaced logger factory, e.g.Services.AddSerilog().Logging.AddSentry()couldn't recover theILoggerintegration — it saw the leftover marker and installedNullLoggerProvidertwiceHostLoggerProvidersreplaces the marker and keeps the descriptors it registered, soAddSentry()rebuilds the host's own providers (keeping the host's options and its filter rules, including the one behind #1284).Debug = truethe SDK warns when the providers were removed and not added back.Option 4 was considered and decided against
I proposed going further: have
UseSentrystop registering theILoggerbridge altogether and move it to an explicitbuilder.Logging.AddSentry(). That would have made the ordering question disappear rather than be mitigated — nothing a logging-level call does could undo a host-level one, andClearProviders()before or afterUseSentrywould stop mattering.We're not doing it, for two reasons:
EnableLogsonly ever gated structured logs; theILogger→ event and breadcrumb bridge has always been on withUseSentryalone. Under option 4 an app that calls onlyUseSentrywould stop turninglogger.LogErrorinto issues, across all four host integrations. That trades one silent surprise for another, with a much larger blast radius.The mitigations above give recoverability and a diagnostic without touching the default, which is the right trade for now. If we revisit it, the cost to weigh is that
AddSentry()would need to select the correct host-specific provider per integration — the problem #5776 solved a different way.Not covered
Build(), and MAUI and Blazor WebAssembly have no warning check. Both tracked in Start the SDK during Build() in ASP.NET Core and Blazor WebAssembly, as the generic host does #5815.UseSentrythenClearProviders()withoutAddSentry()still drops theILoggerintegration by default — now diagnosable rather than silent, but unchanged. That's the deliberate consequence of the decision above.docs(dotnet): Bring the v7 migration guide up to date for rc.2