From 8fb1e0f8899026e19beccdbddc7bd844ad340f53 Mon Sep 17 00:00:00 2001 From: Andrii Babanin Date: Tue, 3 Jun 2025 16:13:40 +0300 Subject: [PATCH 1/2] Remove closure allocation in SerilogLoggerProvider. --- .../Logging/SerilogLoggerProvider.cs | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index 93dce5f..695617c 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,36 +91,41 @@ 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.ScopeItems?.Reverse(); - _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.ScopeItems != null) { - logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeItems))); + logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeCollector.ScopeItems))); } + + scopeCollector.Clear(); } /// @@ -149,4 +155,20 @@ public ValueTask DisposeAsync() return _disposeAsync?.Invoke() ?? default; } #endif + + /// + /// A wrapper around a list to allow lazy initialization when iterating through scopes. + /// + private sealed class ScopeCollector + { + public List? ScopeItems { get; private set; } + + public void AddItem(LogEventPropertyValue scopeItem) + { + ScopeItems ??= []; + ScopeItems.Add(scopeItem); + } + + public void Clear() => this.ScopeItems = null; + } } From 46dfbf4887dcbb9a07151340e9bbb93e7cf830ce Mon Sep 17 00:00:00 2001 From: Andrii Babanin Date: Wed, 4 Jun 2025 13:50:43 +0300 Subject: [PATCH 2/2] Encapsulate item management logic in ScopeCollector. --- .../Logging/SerilogLoggerProvider.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs index 695617c..2ad7738 100644 --- a/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs +++ b/src/Serilog.Extensions.Logging/Extensions/Logging/SerilogLoggerProvider.cs @@ -103,7 +103,7 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) } } - scopeCollector.ScopeItems?.Reverse(); + scopeCollector.ReverseItems(); _externalScopeProvider?.ForEachScope(static (state, parameters) => { @@ -120,12 +120,10 @@ public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) } }, (ScopeCollector: scopeCollector, PropertyFactory: propertyFactory, LogEvent: logEvent)); - if (scopeCollector.ScopeItems != null) + if (scopeCollector.Complete() is { } items) { - logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(scopeCollector.ScopeItems))); + logEvent.AddPropertyIfAbsent(new LogEventProperty(ScopePropertyName, new SequenceValue(items))); } - - scopeCollector.Clear(); } /// @@ -159,16 +157,23 @@ public ValueTask DisposeAsync() /// /// A wrapper around a list to allow lazy initialization when iterating through scopes. /// - private sealed class ScopeCollector + sealed class ScopeCollector { - public List? ScopeItems { get; private set; } + List? _scopeItems; public void AddItem(LogEventPropertyValue scopeItem) { - ScopeItems ??= []; - ScopeItems.Add(scopeItem); + _scopeItems ??= []; + _scopeItems.Add(scopeItem); } - public void Clear() => this.ScopeItems = null; + public void ReverseItems() => _scopeItems?.Reverse(); + + public List? Complete() + { + var scopeItems = _scopeItems; + _scopeItems = null; + return scopeItems; + } } }