From 38e8badd082aa559e93fe86de0229b37503208bf Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Thu, 18 Dec 2025 12:14:29 +0000 Subject: [PATCH] feat(logging): enhance thread safety with ConcurrentDictionary for scope storage - Replace inner Dictionary with ConcurrentDictionary in _threadScopes to ensure thread-safe operations on scope values - Update Scope property return type from IDictionary to ConcurrentDictionary for consistency - Refactor SetKey method to use intermediate variable for better readability and null handling - Replace manual ContainsKey/Remove checks with TryRemove for atomic operations in RemoveKeys and RemoveKey methods - Update GetAllKeys to return a snapshot via ToArray() to prevent concurrent modification exceptions during enumeration - Add documentation clarifying that inner dictionary is ConcurrentDictionary and that GetAllKeys returns a snapshot for thread-safety - Improve code consistency by using ConcurrentDictionary methods throughout instead of mixing Dictionary and ConcurrentDictionary patterns --- .../Logger.Scope.cs | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs index ae7f4050..0ef8cdb7 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs @@ -14,20 +14,21 @@ public static partial class Logger /// Thread-safe dictionary for per-thread scope storage. /// Uses ManagedThreadId as key to ensure isolation when Lambda processes /// multiple concurrent requests (AWS_LAMBDA_MAX_CONCURRENCY > 1). + /// Inner dictionary is ConcurrentDictionary for thread-safe operations. /// - private static readonly ConcurrentDictionary> _threadScopes = new(); + private static readonly ConcurrentDictionary> _threadScopes = new(); /// /// Gets the scope for the current thread. /// Creates a new dictionary if one doesn't exist for this thread. /// /// The scope. - private static IDictionary Scope + private static ConcurrentDictionary Scope { get { var threadId = Environment.CurrentManagedThreadId; - return _threadScopes.GetOrAdd(threadId, _ => new Dictionary(StringComparer.Ordinal)); + return _threadScopes.GetOrAdd(threadId, _ => new ConcurrentDictionary(StringComparer.Ordinal)); } } @@ -58,9 +59,10 @@ public static void AppendKey(string key, object value) { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key)); - - Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ?? + + var convertedValue = PowertoolsLoggerHelpers.ObjectToDictionary(value) ?? throw new ArgumentNullException(nameof(value)); + Scope[key] = convertedValue; } /// @@ -91,17 +93,18 @@ public static void RemoveKeys(params string[] keys) { if (keys == null) return; foreach (var key in keys) - if (Scope.ContainsKey(key)) - Scope.Remove(key); + Scope.TryRemove(key, out _); } /// /// Returns all additional keys added to the log context. + /// Returns a snapshot to ensure thread-safety during enumeration. /// /// IEnumerable<KeyValuePair<System.String, System.Object>>. public static IEnumerable> GetAllKeys() { - return Scope.AsEnumerable(); + // Return a snapshot to avoid concurrent modification issues + return Scope.ToArray(); } /// @@ -121,7 +124,6 @@ internal static void RemoveAllKeys() /// public static void RemoveKey(string key) { - if (Scope.ContainsKey(key)) - Scope.Remove(key); + Scope.TryRemove(key, out _); } }