Replace logging fallback with MEL-backed factory and AsyncLocal routing for acceptance tests#7758
Merged
Conversation
andreasohlund
approved these changes
May 18, 2026
Contributor
Author
|
@andreasohlund please review the additions |
Contributor
Author
|
Will test an alpha release locally with the transactional session later in the afternoon |
DavidBoike
approved these changes
May 18, 2026
andreasohlund
approved these changes
May 19, 2026
… FallbackLoggerFactory - Add AsyncLocal<MelILoggerFactory?> ambientDefaultFactory to LogManager for per-async-context default factory routing (replaces global UseFactory bridge in ScenarioRunner) - Replace trace-writing FallbackLoggerFactory with real MEL-backed implementation using RollingLoggerProvider + ColoredConsoleLoggerProvider via isolated ServiceProvider - Add SlotUnregisterer (IAsyncDisposable) registered after AddLogging in EndpointCreator.Configure so MS DI reverse-order disposal unregisters slot before ILoggerFactory disposal - Modify SlotAwareLogger.GetDefaultLogger, WriteOrEnqueueScopedStartupLog, and UnregisterSlot drain to check ambient factory first - Remove LogManager.UseFactory bridge and Use<DefaultFactory> teardown from ScenarioRunner; replace with SetAmbientDefaultFactory after BuildServiceProvider, clear after ServiceProvider disposal - Add EndpointLoggingScope as DI-resolvable IReadOnlyList for ILogger.BeginScope - Add LoggingBuilderExtensions with AddNServiceBusLoggingProviders - Add acceptance tests for out-of-slot log routing (both WithServiceResolve and endpoint ServiceResolve) and EndpointLoggingScope - Add unit tests for ambient factory routing, AsyncLocal isolation, SlotUnregisterer idempotency, and stale scope drain
…add tests for scope behavior
868f238 to
ee1728a
Compare
Contributor
Author
|
Tested with the transactional session update branch and resolves the lost logs |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When NServiceBus logs outside an endpoint slot scope (for example, from a static
ILogfield before startup or during shutdown), those logs currently fall through toSystem.Diagnostics.Trace. They are invisible to users and to the acceptance test framework.There is also no way for downstream packages (such as Transactional Session) to attach endpoint context to logs when operating outside the message pipeline. The endpoint name and identifier are only available through the internal
LogManager.BeginSlotScopemechanism.The previous attempt to fix the first problem used a
LogManager.UseFactorybridge inScenarioRunnerto route out-of-slot logs to the scenario's MEL factory. That approach mutates process-global static state, requires fragile teardown ordering, and cannot support parallel test execution.Solution
Four changes address the production and test concerns independently, without global state mutation.
EndpointLoggingScope (new public type)
A DI-resolvable type that carries endpoint name and identifier as
IReadOnlyList<KeyValuePair<string, object?>>, compatible withILogger.BeginScope. Downstream packages can enrich their logs with endpoint context without depending on internal LogManager APIs:Registered as a singleton per endpoint in
EndpointCreator.Configure.LogScopeStateswas refactored to share the scope entry construction logic.MEL-backed fallback factory
FallbackLoggerFactorynow builds an isolatedServiceCollectionwithRollingLoggerProviderandColoredConsoleLoggerProvider. Out-of-slot logs go to a real rolling file and console output instead ofSystem.Diagnostics.Trace. The factory is a lazy singleton with process-lifetime scope, never disposed.AsyncLocal ambient default factory
LogManager.SetAmbientDefaultFactorystores a per-async-context MELILoggerFactoryreference. The acceptance testing framework sets it after building the scenario's service provider, and clears it after disposal. BecauseAsyncLocalflows naturally through async chains, each scenario's out-of-slot logs reachScenarioContext.Logswithout touching global state. The slot-scoped hot path is unaffected;GetDefaultLoggeris only reached on the cold out-of-slot path.DI-managed slot cleanup
SlotUnregistereris anIAsyncDisposableregistered right afterAddLogginginEndpointCreator.Configure. MS DI disposes services in reverse registration order, so the slot is unregistered beforeILoggerFactoryis disposed. This eliminates the window where a background thread could route logs through a disposed factory during container teardown.RunningEndpointInstanceno longer callsLogManager.UnregisterSlotdirectly.Trade-offs
FallbackLoggerFactoryis never disposed. This is intentional: it is a safety net for the gap between process start and endpoint initialization, and between endpoint shutdown and process exit. The OS reclaims resources on exit.ScenarioWithContext.DonespawnsTask.Runthat does not inherit the ambient factory. This is an accepted limitation because the Done polling loop only checks boolean flags and does not log.AsyncLocaldoes not flow acrossThreadPool.QueueUserWorkItemor fire-and-forget boundaries withoutExecutionContextcapture. Production out-of-slot code that uses these patterns still falls through toFallbackLoggerFactory, which is the correct behavior.What stays unchanged
LogManager.UseFactoryandLogManager.Use<T>: still work as deprecated APIs. The ambient factory only affects the out-of-slot routing path.