Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,32 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
// TODO: For casing parsing to be removed from Logging v2 when we get rid of outputcase without this CorrelationIdPaths.ApiGatewayRest would not work
// TODO: This will be removed and replaced by JMesPath

var pathWithOutputCase = correlationIdPaths[i].ToCase(_currentConfig.LoggerOutputCase);
if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
break;
var pathSegment = correlationIdPaths[i];
JsonElement childElement;

// Try original path first (case-sensitive)
if (!element.TryGetProperty(pathSegment, out childElement))
{
// Try with output case transformation
var pathWithOutputCase = pathSegment.ToCase(_currentConfig.LoggerOutputCase);
if (!element.TryGetProperty(pathWithOutputCase, out childElement))
{
// Try case-insensitive match as last resort
var found = false;
foreach (var property in element.EnumerateObject())
{
if (string.Equals(property.Name, pathSegment, StringComparison.OrdinalIgnoreCase))
{
childElement = property.Value;
found = true;
break;
}
}

if (!found)
break;
}
}

element = childElement;
if (i == correlationIdPaths.Length - 1)
Expand Down
17 changes: 17 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AWS.Lambda.Powertools.Logging.Internal;
using AWS.Lambda.Powertools.Logging.Internal.Helpers;

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

/// <summary>
/// Gets the correlation identifier from the log context.
/// </summary>
/// <value>The correlation identifier, or null if not set.</value>
public static string CorrelationId
{
get
{
if (Scope.TryGetValue(LoggingConstants.KeyCorrelationId, out var value))
{
return value?.ToString();
}
return null;
}
}

/// <summary>
/// Appending additional key to the log context.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ public static void Log(this ILogger logger, LogLevel logLevel, Exception excepti

#endregion

/// <summary>
/// Gets the correlation identifier from the log context.
/// </summary>
/// <param name="logger">The logger instance.</param>
/// <returns>The correlation identifier, or null if not set.</returns>
public static string GetCorrelationId(this ILogger logger)
{
return Logger.CorrelationId;
}

/// <summary>
/// Appending additional key to the log context.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Linq;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.ApplicationLoadBalancerEvents;
using Amazon.Lambda.CloudWatchEvents;
using Amazon.Lambda.CloudWatchEvents.S3Events;
using Amazon.Lambda.TestUtilities;
using AWS.Lambda.Powertools.Common;
Expand Down Expand Up @@ -172,6 +173,7 @@ public void OnExit_WhenHandler_ClearState_Enabled_ClearKeys()
[InlineData(CorrelationIdPaths.ApplicationLoadBalancer)]
[InlineData(CorrelationIdPaths.EventBridge)]
[InlineData("/headers/my_request_id_header")]
[InlineData("/detail/correlationId")]
public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationIdPath)
{
// Arrange
Expand Down Expand Up @@ -213,6 +215,15 @@ public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationI
}
});
break;
case "/detail/correlationId":
_testHandlers.CorrelationCloudWatchEventCustomPath(new CloudWatchEvent<CwEvent>
{
Detail = new CwEvent
{
CorrelationId = correlationId
}
});
break;
}

// Assert
Expand Down Expand Up @@ -346,6 +357,106 @@ public void When_Setting_SamplingRate_Should_Add_Key()
));
}

[Fact]
public void CorrelationId_Property_Should_Return_CorrelationId()
{
// Arrange
var correlationId = Guid.NewGuid().ToString();

// Act
_testHandlers.CorrelationCloudWatchEventCustomPath(new CloudWatchEvent<CwEvent>
{
Detail = new CwEvent
{
CorrelationId = correlationId
}
});

// Assert - Static Logger property
Assert.Equal(correlationId, Logger.CorrelationId);
}

[Fact]
public void CorrelationId_Extension_Should_Return_CorrelationId_Via_ILogger()
{
// Arrange
var correlationId = Guid.NewGuid().ToString();

// Act
_testHandlers.CorrelationIdExtensionTest(new CloudWatchEvent<CwEvent>
{
Detail = new CwEvent
{
CorrelationId = correlationId
}
});

// Assert - The test handler will verify the extension method works
Assert.Equal(correlationId, Logger.CorrelationId);
}

[Fact]
public void CorrelationId_Should_Return_Null_When_Not_Set()
{
// Arrange - no correlation ID set

// Act & Assert
Assert.Null(Logger.CorrelationId);
}

[Fact]
public void OnEntry_WhenPropertyCasingDoesNotMatch_UsesCaseInsensitiveFallback()
{
// Arrange
var correlationId = Guid.NewGuid().ToString();

// This test uses a path "/detail/CORRELATIONID" (all caps)
// but the actual property is "correlationId" (camelCase)
// This should trigger the case-insensitive fallback

// Act
_testHandlers.CorrelationIdCaseInsensitiveFallback(new CloudWatchEvent<CwEvent>
{
Detail = new CwEvent
{
CorrelationId = correlationId
}
});

// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);

Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
}

[Fact]
public void OnEntry_WhenNestedPropertyCasingDoesNotMatch_UsesCaseInsensitiveFallback()
{
// Arrange
var correlationId = Guid.NewGuid().ToString();

// This test uses a path with mismatched casing at multiple levels
// Path: "/DETAIL/CORRELATIONID" but actual properties are "detail" and "correlationId"

// Act
_testHandlers.CorrelationIdNestedCaseInsensitive(new CloudWatchEvent<CwEvent>
{
Detail = new CwEvent
{
CorrelationId = correlationId
}
});

// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);

Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
}

[Fact]
public void When_Setting_Service_Should_Update_Key()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,41 @@ public void CorrelationCloudWatchEvent(CloudWatchEvent<S3ObjectCreate> cwEvent)
{
}

[Logging(CorrelationIdPath = "/detail/correlationId")]
public void CorrelationCloudWatchEventCustomPath(CloudWatchEvent<CwEvent> cwEvent)
{
}

[Logging(CorrelationIdPath = "/detail/correlationId")]
public void CorrelationIdExtensionTest(CloudWatchEvent<CwEvent> cwEvent)
{
// Test that the ILogger extension method works
var logger = Microsoft.Extensions.Logging.LoggerFactory.Create(builder => { }).CreateLogger(nameof(TestHandlers));
var correlationIdFromExtension = logger.GetCorrelationId();

// Verify it matches the static property
if (correlationIdFromExtension != Logger.CorrelationId)
{
throw new Exception("Extension method returned different value than static property");
}
}

[Logging(CorrelationIdPath = "/detail/CORRELATIONID")]
public void CorrelationIdCaseInsensitiveFallback(CloudWatchEvent<CwEvent> cwEvent)
{
// This handler uses all caps "CORRELATIONID" in the path
// but the actual JSON property is "correlationId" (camelCase)
// This tests the case-insensitive fallback logic
}

[Logging(CorrelationIdPath = "/DETAIL/CORRELATIONID")]
public void CorrelationIdNestedCaseInsensitive(CloudWatchEvent<CwEvent> cwEvent)
{
// This handler uses all caps for both path segments
// but the actual JSON properties are "detail" and "correlationId"
// This tests the case-insensitive fallback at multiple levels
}

[Logging(CorrelationIdPath = "/headers/my_request_id_header")]
public void CorrelationIdFromString(TestObject testObject)
{
Expand Down Expand Up @@ -172,6 +207,21 @@ public enum Pet
}
}

public class CwEvent
{
[JsonPropertyName("rideId")]
public string RideId { get; set; } = string.Empty;

[JsonPropertyName("riderId")]
public string RiderId { get; set; } = string.Empty;

[JsonPropertyName("riderName")]
public string RiderName { get; set; } = string.Empty;

[JsonPropertyName("correlationId")]
public string? CorrelationId { get; set; }
}

public class TestServiceHandler
{
public void LogWithEnv()
Expand Down
Loading