Skip to content

Commit f80486f

Browse files
authored
Merge branch 'develop' into dependabot/github_actions/actions/upload-artifact-5.0.0
2 parents 516cdf7 + eff1550 commit f80486f

File tree

5 files changed

+214
-3
lines changed

5 files changed

+214
-3
lines changed

libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,32 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
133133
// TODO: For casing parsing to be removed from Logging v2 when we get rid of outputcase without this CorrelationIdPaths.ApiGatewayRest would not work
134134
// TODO: This will be removed and replaced by JMesPath
135135

136-
var pathWithOutputCase = correlationIdPaths[i].ToCase(_currentConfig.LoggerOutputCase);
137-
if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
138-
break;
136+
var pathSegment = correlationIdPaths[i];
137+
JsonElement childElement;
138+
139+
// Try original path first (case-sensitive)
140+
if (!element.TryGetProperty(pathSegment, out childElement))
141+
{
142+
// Try with output case transformation
143+
var pathWithOutputCase = pathSegment.ToCase(_currentConfig.LoggerOutputCase);
144+
if (!element.TryGetProperty(pathWithOutputCase, out childElement))
145+
{
146+
// Try case-insensitive match as last resort
147+
var found = false;
148+
foreach (var property in element.EnumerateObject())
149+
{
150+
if (string.Equals(property.Name, pathSegment, StringComparison.OrdinalIgnoreCase))
151+
{
152+
childElement = property.Value;
153+
found = true;
154+
break;
155+
}
156+
}
157+
158+
if (!found)
159+
break;
160+
}
161+
}
139162

140163
element = childElement;
141164
if (i == correlationIdPaths.Length - 1)

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4+
using AWS.Lambda.Powertools.Logging.Internal;
45
using AWS.Lambda.Powertools.Logging.Internal.Helpers;
56

67
namespace AWS.Lambda.Powertools.Logging;
@@ -13,6 +14,22 @@ public static partial class Logger
1314
/// <value>The scope.</value>
1415
private static IDictionary<string, object> Scope { get; } = new Dictionary<string, object>(StringComparer.Ordinal);
1516

17+
/// <summary>
18+
/// Gets the correlation identifier from the log context.
19+
/// </summary>
20+
/// <value>The correlation identifier, or null if not set.</value>
21+
public static string CorrelationId
22+
{
23+
get
24+
{
25+
if (Scope.TryGetValue(LoggingConstants.KeyCorrelationId, out var value))
26+
{
27+
return value?.ToString();
28+
}
29+
return null;
30+
}
31+
}
32+
1633
/// <summary>
1734
/// Appending additional key to the log context.
1835
/// </summary>

libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ public static void Log(this ILogger logger, LogLevel logLevel, Exception excepti
170170

171171
#endregion
172172

173+
/// <summary>
174+
/// Gets the correlation identifier from the log context.
175+
/// </summary>
176+
/// <param name="logger">The logger instance.</param>
177+
/// <returns>The correlation identifier, or null if not set.</returns>
178+
public static string GetCorrelationId(this ILogger logger)
179+
{
180+
return Logger.CorrelationId;
181+
}
182+
173183
/// <summary>
174184
/// Appending additional key to the log context.
175185
/// </summary>

libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using Amazon.Lambda.APIGatewayEvents;
66
using Amazon.Lambda.ApplicationLoadBalancerEvents;
7+
using Amazon.Lambda.CloudWatchEvents;
78
using Amazon.Lambda.CloudWatchEvents.S3Events;
89
using Amazon.Lambda.TestUtilities;
910
using AWS.Lambda.Powertools.Common;
@@ -172,6 +173,7 @@ public void OnExit_WhenHandler_ClearState_Enabled_ClearKeys()
172173
[InlineData(CorrelationIdPaths.ApplicationLoadBalancer)]
173174
[InlineData(CorrelationIdPaths.EventBridge)]
174175
[InlineData("/headers/my_request_id_header")]
176+
[InlineData("/detail/correlationId")]
175177
public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationIdPath)
176178
{
177179
// Arrange
@@ -213,6 +215,15 @@ public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationI
213215
}
214216
});
215217
break;
218+
case "/detail/correlationId":
219+
_testHandlers.CorrelationCloudWatchEventCustomPath(new CloudWatchEvent<CwEvent>
220+
{
221+
Detail = new CwEvent
222+
{
223+
CorrelationId = correlationId
224+
}
225+
});
226+
break;
216227
}
217228

218229
// Assert
@@ -346,6 +357,106 @@ public void When_Setting_SamplingRate_Should_Add_Key()
346357
));
347358
}
348359

360+
[Fact]
361+
public void CorrelationId_Property_Should_Return_CorrelationId()
362+
{
363+
// Arrange
364+
var correlationId = Guid.NewGuid().ToString();
365+
366+
// Act
367+
_testHandlers.CorrelationCloudWatchEventCustomPath(new CloudWatchEvent<CwEvent>
368+
{
369+
Detail = new CwEvent
370+
{
371+
CorrelationId = correlationId
372+
}
373+
});
374+
375+
// Assert - Static Logger property
376+
Assert.Equal(correlationId, Logger.CorrelationId);
377+
}
378+
379+
[Fact]
380+
public void CorrelationId_Extension_Should_Return_CorrelationId_Via_ILogger()
381+
{
382+
// Arrange
383+
var correlationId = Guid.NewGuid().ToString();
384+
385+
// Act
386+
_testHandlers.CorrelationIdExtensionTest(new CloudWatchEvent<CwEvent>
387+
{
388+
Detail = new CwEvent
389+
{
390+
CorrelationId = correlationId
391+
}
392+
});
393+
394+
// Assert - The test handler will verify the extension method works
395+
Assert.Equal(correlationId, Logger.CorrelationId);
396+
}
397+
398+
[Fact]
399+
public void CorrelationId_Should_Return_Null_When_Not_Set()
400+
{
401+
// Arrange - no correlation ID set
402+
403+
// Act & Assert
404+
Assert.Null(Logger.CorrelationId);
405+
}
406+
407+
[Fact]
408+
public void OnEntry_WhenPropertyCasingDoesNotMatch_UsesCaseInsensitiveFallback()
409+
{
410+
// Arrange
411+
var correlationId = Guid.NewGuid().ToString();
412+
413+
// This test uses a path "/detail/CORRELATIONID" (all caps)
414+
// but the actual property is "correlationId" (camelCase)
415+
// This should trigger the case-insensitive fallback
416+
417+
// Act
418+
_testHandlers.CorrelationIdCaseInsensitiveFallback(new CloudWatchEvent<CwEvent>
419+
{
420+
Detail = new CwEvent
421+
{
422+
CorrelationId = correlationId
423+
}
424+
});
425+
426+
// Assert
427+
var allKeys = Logger.GetAllKeys()
428+
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
429+
430+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
431+
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
432+
}
433+
434+
[Fact]
435+
public void OnEntry_WhenNestedPropertyCasingDoesNotMatch_UsesCaseInsensitiveFallback()
436+
{
437+
// Arrange
438+
var correlationId = Guid.NewGuid().ToString();
439+
440+
// This test uses a path with mismatched casing at multiple levels
441+
// Path: "/DETAIL/CORRELATIONID" but actual properties are "detail" and "correlationId"
442+
443+
// Act
444+
_testHandlers.CorrelationIdNestedCaseInsensitive(new CloudWatchEvent<CwEvent>
445+
{
446+
Detail = new CwEvent
447+
{
448+
CorrelationId = correlationId
449+
}
450+
});
451+
452+
// Assert
453+
var allKeys = Logger.GetAllKeys()
454+
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
455+
456+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
457+
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
458+
}
459+
349460
[Fact]
350461
public void When_Setting_Service_Should_Update_Key()
351462
{

libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,41 @@ public void CorrelationCloudWatchEvent(CloudWatchEvent<S3ObjectCreate> cwEvent)
6666
{
6767
}
6868

69+
[Logging(CorrelationIdPath = "/detail/correlationId")]
70+
public void CorrelationCloudWatchEventCustomPath(CloudWatchEvent<CwEvent> cwEvent)
71+
{
72+
}
73+
74+
[Logging(CorrelationIdPath = "/detail/correlationId")]
75+
public void CorrelationIdExtensionTest(CloudWatchEvent<CwEvent> cwEvent)
76+
{
77+
// Test that the ILogger extension method works
78+
var logger = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { }).CreateLogger(nameof(TestHandlers));
79+
var correlationIdFromExtension = logger.GetCorrelationId();
80+
81+
// Verify it matches the static property
82+
if (correlationIdFromExtension != Logger.CorrelationId)
83+
{
84+
throw new Exception("Extension method returned different value than static property");
85+
}
86+
}
87+
88+
[Logging(CorrelationIdPath = "/detail/CORRELATIONID")]
89+
public void CorrelationIdCaseInsensitiveFallback(CloudWatchEvent<CwEvent> cwEvent)
90+
{
91+
// This handler uses all caps "CORRELATIONID" in the path
92+
// but the actual JSON property is "correlationId" (camelCase)
93+
// This tests the case-insensitive fallback logic
94+
}
95+
96+
[Logging(CorrelationIdPath = "/DETAIL/CORRELATIONID")]
97+
public void CorrelationIdNestedCaseInsensitive(CloudWatchEvent<CwEvent> cwEvent)
98+
{
99+
// This handler uses all caps for both path segments
100+
// but the actual JSON properties are "detail" and "correlationId"
101+
// This tests the case-insensitive fallback at multiple levels
102+
}
103+
69104
[Logging(CorrelationIdPath = "/headers/my_request_id_header")]
70105
public void CorrelationIdFromString(TestObject testObject)
71106
{
@@ -172,6 +207,21 @@ public enum Pet
172207
}
173208
}
174209

210+
public class CwEvent
211+
{
212+
[JsonPropertyName("rideId")]
213+
public string RideId { get; set; } = string.Empty;
214+
215+
[JsonPropertyName("riderId")]
216+
public string RiderId { get; set; } = string.Empty;
217+
218+
[JsonPropertyName("riderName")]
219+
public string RiderName { get; set; } = string.Empty;
220+
221+
[JsonPropertyName("correlationId")]
222+
public string? CorrelationId { get; set; }
223+
}
224+
175225
public class TestServiceHandler
176226
{
177227
public void LogWithEnv()

0 commit comments

Comments
 (0)