Skip to content

Commit eb7ba1d

Browse files
authored
feat(logging): thread safety with ConcurrentDictionary for scope storage (#1099)
1 parent 1a1a3fa commit eb7ba1d

File tree

1 file changed

+12
-10
lines changed

1 file changed

+12
-10
lines changed

libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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&lt;KeyValuePair&lt;System.String, System.Object&gt;&gt;.</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

Comments
 (0)