@@ -24,6 +24,7 @@ public sealed class SerilogLoggerProvider : ILoggerProvider, ILogEventEnricher,
2424 // May be null; if it is, Log.Logger will be lazily used
2525 readonly ILogger ? _logger ;
2626 readonly Action ? _dispose ;
27+ readonly ThreadLocal < ScopeCollector > _scopeCollector = new ( ( ) => new ScopeCollector ( ) ) ;
2728#if FEATURE_ASYNCDISPOSABLE
2829 readonly Func < ValueTask > ? _disposeAsync ;
2930#endif
@@ -90,36 +91,41 @@ public IDisposable BeginScope<T>(T state)
9091 /// <inheritdoc />
9192 public void Enrich ( LogEvent logEvent , ILogEventPropertyFactory propertyFactory )
9293 {
93- List < LogEventPropertyValue > ? scopeItems = null ;
94+ var scopeCollector = _scopeCollector . Value ! ;
95+
9496 for ( var scope = CurrentScope ; scope != null ; scope = scope . Parent )
9597 {
9698 scope . EnrichAndCreateScopeItem ( logEvent , propertyFactory , out var scopeItem ) ;
9799
98100 if ( scopeItem != null )
99101 {
100- scopeItems ??= [ ] ;
101- scopeItems . Add ( scopeItem ) ;
102+ scopeCollector . AddItem ( scopeItem ) ;
102103 }
103104 }
104105
105- scopeItems ? . Reverse ( ) ;
106+ scopeCollector . ScopeItems ? . Reverse ( ) ;
106107
107- _externalScopeProvider ? . ForEachScope ( ( state , accumulatingLogEvent ) =>
108+ _externalScopeProvider ? . ForEachScope ( static ( state , parameters ) =>
108109 {
109110 SerilogLoggerScope . EnrichWithStateAndCreateScopeItem (
110- accumulatingLogEvent , propertyFactory , state , update : true , out var scopeItem ) ;
111+ parameters . LogEvent ,
112+ parameters . PropertyFactory ,
113+ state ,
114+ update : true ,
115+ out var scopeItem ) ;
111116
112117 if ( scopeItem != null )
113118 {
114- scopeItems ??= new List < LogEventPropertyValue > ( ) ;
115- scopeItems . Add ( scopeItem ) ;
119+ parameters . ScopeCollector . AddItem ( scopeItem ) ;
116120 }
117- } , logEvent ) ;
121+ } , ( ScopeCollector : scopeCollector , PropertyFactory : propertyFactory , LogEvent : logEvent ) ) ;
118122
119- if ( scopeItems != null )
123+ if ( scopeCollector . ScopeItems != null )
120124 {
121- logEvent . AddPropertyIfAbsent ( new LogEventProperty ( ScopePropertyName , new SequenceValue ( scopeItems ) ) ) ;
125+ logEvent . AddPropertyIfAbsent ( new LogEventProperty ( ScopePropertyName , new SequenceValue ( scopeCollector . ScopeItems ) ) ) ;
122126 }
127+
128+ scopeCollector . Clear ( ) ;
123129 }
124130
125131 /// <inheritdoc />
@@ -149,4 +155,20 @@ public ValueTask DisposeAsync()
149155 return _disposeAsync ? . Invoke ( ) ?? default ;
150156 }
151157#endif
158+
159+ /// <summary>
160+ /// A wrapper around a list to allow lazy initialization when iterating through scopes.
161+ /// </summary>
162+ private sealed class ScopeCollector
163+ {
164+ public List < LogEventPropertyValue > ? ScopeItems { get ; private set ; }
165+
166+ public void AddItem ( LogEventPropertyValue scopeItem )
167+ {
168+ ScopeItems ??= [ ] ;
169+ ScopeItems . Add ( scopeItem ) ;
170+ }
171+
172+ public void Clear ( ) => this . ScopeItems = null ;
173+ }
152174}
0 commit comments