diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index 93dce5f..2ad7738 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs @@ -24,6 +24,7 @@ public sealed class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher, // May be null; if it is, Log.Logger will be lazily used readonly ILogger? _logger; readonly Action? _dispose; + readonly ThreadLocal _scopeCollector = new(() => new ScopeCollector()); #if FEATURE_ASYNCDISPOSABLE readonly Func? _disposeAsync; #endif @@ -90,35 +91,38 @@ public IDisposable BeginScope(T state) /// public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { - List? scopeItems = null; + var scopeCollector = _scopeCollector.Value!; + for (var scope = CurrentScope; scope != null; scope = scope.Parent) { scope.EnrichAndCreateScopeItem(logEvent, propertyFactory, out var scopeItem); if (scopeItem != null) { - scopeItems ??= []; - scopeItems.Add(scopeItem); + scopeCollector.AddItem(scopeItem); } } - scopeItems?.Reverse(); + scopeCollector.ReverseItems(); - _externalScopeProvider?.ForEachScope((state, accumulatingLogEvent) => + _externalScopeProvider?.ForEachScope(static (state, parameters) => { SerilogLoggerScope.EnrichWithStateAndCreateScopeItem( - accumulatingLogEvent, propertyFactory, state, update: true, out var scopeItem); + parameters.LogEvent, + parameters.PropertyFactory, + state, + update: true, + out var scopeItem); if (scopeItem != null) { - scopeItems ??= new List(); - scopeItems.Add(scopeItem); + parameters.ScopeCollector.AddItem(scopeItem); } - }, logEvent); + }, (ScopeCollector: scopeCollector, PropertyFactory: propertyFactory, LogEvent: logEvent)); - if (scopeItems != null) + if (scopeCollector.Complete() is { } items) { - logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeItems))); + logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(items))); } } @@ -149,4 +153,27 @@ public ValueTask DisposeAsync() return _disposeAsync?.Invoke() ?? default; } #endif + + /// + /// A wrapper around a list to allow lazy initialization when iterating through scopes. + /// + sealed class ScopeCollector + { + List? _scopeItems; + + public void AddItem(LogEventPropertyValue scopeItem) + { + _scopeItems ??= []; + _scopeItems.Add(scopeItem); + } + + public void ReverseItems() => _scopeItems?.Reverse(); + + public List? Complete() + { + var scopeItems = _scopeItems; + _scopeItems = null; + return scopeItems; + } + } }