\.NET 6: Ensuring Scoped Log Levels Apply to All Logging Providers
0 reputation · 03 Apr 2022, 20:07 UTC
0 reputation · 03 Apr 2022, 20:07 UTC
In .NET 6, developers often use ILogger.BeginScope to set a minimum log level for a block of code, hoping to suppress noisy alerts. However, some logging providers do not honor these scoped levels automatically, leading to inconsistent noise suppression across the application.
The goal is to reduce notification noise by applying scoped log levels uniformly, while still allowing critical alerts to surface. The constraints include provider compatibility, the need to register DiagnosticSource listeners early, and the fact that DiagnosticSource.AllListeners is immutable after startup.
Key questions remain:
BeginScope log level changes are respected by all logging providers in .NET 6?DiagnosticSource and ILogger without missing critical events?35275 reputation · 04 Apr 2022, 00:42 UTC
ILogger.BeginScope does not change the minimum log level. It only attaches ambient state (key/value data) to log entries. Whether that state is honored at all is up to each provider: the built-in console provider includes scopes in output only when IncludeScopes = true, and no built-in provider in .NET 6 interprets scope contents as a level filter. So there is no switch that makes "scoped log levels" apply to all providers — you have to implement the filtering yourself, either in a wrapper provider or via log filtering rules.
Confirmed, well-established behavior: scopes flow through ILogger.BeginScope to every provider that opts in to reading them; filtering in Microsoft.Extensions.Logging is category/provider-based via LoggerFilterOptions, not scope-based. I'm assuming you're on the stock Microsoft.Extensions.Logging stack (console/debug providers), not Serilog/NLog, which have their own enrichment models. If you're actually on Serilog, the answer changes — say so.
Don't try to make scopes control levels. Instead:
ILoggerFactory:builder.Logging.AddFilter("MyApp.NoisyComponent", LogLevel.Warning);
builder.Logging.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Warning);
ILoggerProvider that decorates the real logger, reads a level value out of the current scope via ISupportExternalScope/scope state, and drops messages below it. Register it early in Program.cs before the host builds the service provider. Keep it simple: store the threshold in an AsyncLocal<LogLevel?> set by a small helper that mimics BeginScope semantics, and check it in IsEnabled/Log.Error/Critical. Hard-code a floor in the wrapper so critical alerts always pass regardless of the scoped threshold.Your constraints are correct: DiagnosticSource.AllListeners subscriptions must be registered before the sources you care about start emitting — effectively at process startup, before the host runs. Subscribe with a predicate so you only pay for relevant sources, and route those events through ILogger at Information or higher so they respect your normal filter rules instead of bypassing them.
In a scratch console app: configure AddFilter("*", LogLevel.Warning), log at Debug inside and outside a BeginScope, and confirm output is identical — proving scopes don't filter. Then add your wrapper and confirm scoped suppression works and that an Error log inside the scope still appears. Also verify scopes are disposed (no growing scope chain) under load.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.