@@ -14,20 +14,21 @@ public static partial class Logger
1414 /// Thread-safe dictionary for per-thread scope storage.
1515 /// Uses ManagedThreadId as key to ensure isolation when Lambda processes
1616 /// multiple concurrent requests (AWS_LAMBDA_MAX_CONCURRENCY > 1).
17+ /// Inner dictionary is ConcurrentDictionary for thread-safe operations.
1718 /// </summary>
18- private static readonly ConcurrentDictionary < int , Dictionary < string , object > > _threadScopes = new ( ) ;
19+ private static readonly ConcurrentDictionary < int , ConcurrentDictionary < string , object > > _threadScopes = new ( ) ;
1920
2021 /// <summary>
2122 /// Gets the scope for the current thread.
2223 /// Creates a new dictionary if one doesn't exist for this thread.
2324 /// </summary>
2425 /// <value>The scope.</value>
25- private static IDictionary < string , object > Scope
26+ private static ConcurrentDictionary < string , object > Scope
2627 {
2728 get
2829 {
2930 var threadId = Environment . CurrentManagedThreadId ;
30- return _threadScopes . GetOrAdd ( threadId , _ => new Dictionary < string , object > ( StringComparer . Ordinal ) ) ;
31+ return _threadScopes . GetOrAdd ( threadId , _ => new ConcurrentDictionary < string , object > ( StringComparer . Ordinal ) ) ;
3132 }
3233 }
3334
@@ -58,9 +59,10 @@ public static void AppendKey(string key, object value)
5859 {
5960 if ( string . IsNullOrWhiteSpace ( key ) )
6061 throw new ArgumentNullException ( nameof ( key ) ) ;
61-
62- Scope [ key ] = PowertoolsLoggerHelpers . ObjectToDictionary ( value ) ??
62+
63+ var convertedValue = PowertoolsLoggerHelpers . ObjectToDictionary ( value ) ??
6364 throw new ArgumentNullException ( nameof ( value ) ) ;
65+ Scope [ key ] = convertedValue ;
6466 }
6567
6668 /// <summary>
@@ -91,17 +93,18 @@ public static void RemoveKeys(params string[] keys)
9193 {
9294 if ( keys == null ) return ;
9395 foreach ( var key in keys )
94- if ( Scope . ContainsKey ( key ) )
95- Scope . Remove ( key ) ;
96+ Scope . TryRemove ( key , out _ ) ;
9697 }
9798
9899 /// <summary>
99100 /// Returns all additional keys added to the log context.
101+ /// Returns a snapshot to ensure thread-safety during enumeration.
100102 /// </summary>
101103 /// <returns>IEnumerable<KeyValuePair<System.String, System.Object>>.</returns>
102104 public static IEnumerable < KeyValuePair < string , object > > GetAllKeys ( )
103105 {
104- return Scope . AsEnumerable ( ) ;
106+ // Return a snapshot to avoid concurrent modification issues
107+ return Scope . ToArray ( ) ;
105108 }
106109
107110 /// <summary>
@@ -121,7 +124,6 @@ internal static void RemoveAllKeys()
121124 /// </summary>
122125 public static void RemoveKey ( string key )
123126 {
124- if ( Scope . ContainsKey ( key ) )
125- Scope . Remove ( key ) ;
127+ Scope . TryRemove ( key , out _ ) ;
126128 }
127129}
0 commit comments