Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Fix logging enum serialization #670

Merged
Merged
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 @@ -13,8 +13,11 @@
* permissions and limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json.Serialization;

namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;

Expand All @@ -38,18 +41,31 @@
return new Dictionary<string, object>();
}

if (anonymousObject.GetType().Namespace is not null)
var type = anonymousObject.GetType();

if (type.IsEnum)
{
return anonymousObject;
}

if (type.Namespace != null && !type.IsEnum)
{
return anonymousObject;
}

return anonymousObject.GetType().GetProperties()
return type.GetProperties()
.Where(prop => prop.GetValue(anonymousObject, null) != null)
.ToDictionary(
prop => prop.Name,
prop => {
var value = prop.GetValue(anonymousObject, null);
return value != null ? ObjectToDictionary(value) : string.Empty;
if (value == null)
return string.Empty;

Check warning on line 63 in libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs#L63

Added line #L63 was not covered by tests

if (value.GetType().IsEnum)
return value;

return ObjectToDictionary(value);
}
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;

namespace AWS.Lambda.Powertools.Logging.Serializers;

internal class LogLevelJsonConverter : JsonConverter<LogLevel>
{
public override LogLevel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Enum.TryParse<LogLevel>(reader.GetString(),true, out var val) ? val : default;
}

public override void Write(Utf8JsonWriter writer, LogLevel value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ private static JsonSerializerOptions BuildJsonSerializerOptions()
_jsonOptions.Converters.Add(new TimeOnlyConverter());

#if NET8_0_OR_GREATER
_jsonOptions.Converters.Add(new JsonStringEnumConverter<LogLevel>());
_jsonOptions.Converters.Add(new LogLevelJsonConverter());
#elif NET6_0
_jsonOptions.Converters.Add(new JsonStringEnumConverter());
_jsonOptions.Converters.Add(new LogLevelJsonConverter());
#endif

_jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Amazon.Lambda.Core;
using Amazon.Lambda.TestUtilities;
using AWS.Lambda.Powertools.Common;
Expand All @@ -42,7 +43,50 @@ public LogFormatterTest()
{
_testHandler = new TestHandlers();
}


[Fact]
public void Serialize_ShouldHandleEnumValues()
{
var consoleOut = Substitute.For<StringWriter>();
SystemWrapper.Instance.SetOut(consoleOut);
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
FunctionVersion = "version",
InvokedFunctionArn = "function::arn",
AwsRequestId = "requestId",
MemoryLimitInMB = 128
};

var handler = new TestHandlers();
handler.TestEnums("fake", lambdaContext);

consoleOut.Received(1).WriteLine(Arg.Is<string>(i =>
i.Contains("\"message\":5")
));
consoleOut.Received(1).WriteLine(Arg.Is<string>(i =>
i.Contains("\"message\":\"Dog\"")
));

var json = JsonSerializer.Serialize(Pet.Dog, PowertoolsLoggingSerializer.GetSerializerOptions());
Assert.Contains("Dog", json);
}

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Pet
{
Cat = 1,
Dog = 3,
Lizard = 5
}

public enum Thing
{
One = 1,
Three = 3,
Five = 5
}

[Fact]
public void Log_WhenCustomFormatter_LogsCustomFormat()
{
Expand Down Expand Up @@ -204,7 +248,7 @@ public void Should_Log_CustomFormatter_When_Decorated()
consoleOut.Received(1).WriteLine(
Arg.Is<string>(i =>
i.Contains(
"\"correlation_ids\":{\"aws_request_id\":\"requestId\"},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_mb\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\""))
"\"correlation_ids\":{\"aws_request_id\":\"requestId\"},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_mb\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\""))
);
#else
consoleOut.Received(1).WriteLine(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* permissions and limitations under the License.
*/

using System.Text.Json.Serialization;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.ApplicationLoadBalancerEvents;
using Amazon.Lambda.CloudWatchEvents;
Expand Down Expand Up @@ -158,4 +159,26 @@ public void TestLogNoDecorator()
{
Logger.LogInformation("test");
}

[Logging(Service = "test", LoggerOutputCase = LoggerOutputCase.SnakeCase)]
public void TestEnums(string input, ILambdaContext context)
{
Logger.LogInformation(Pet.Dog);
Logger.LogInformation(Thing.Five);
}

public enum Thing
{
One = 1,
Three = 3,
Five = 5
}

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Pet
{
Cat = 1,
Dog = 3,
Lizard = 5
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public void SerializerOptions_ShouldHaveCorrectDefaultSettings()
converter => Assert.IsType<DateOnlyConverter>(converter),
converter => Assert.IsType<TimeOnlyConverter>(converter),
#if NET8_0_OR_GREATER
converter => Assert.IsType<JsonStringEnumConverter<LogLevel>>(converter));
converter => Assert.IsType<LogLevelJsonConverter>(converter));
#elif NET6_0
converter => Assert.IsType<JsonStringEnumConverter>(converter));
converter => Assert.IsType<LogLevelJsonConverter>(converter));
#endif

Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder);
Expand All @@ -90,9 +90,9 @@ public void SerializerOptions_ShouldHaveCorrectDefaultSettings_WhenDynamic()
converter => Assert.IsType<DateOnlyConverter>(converter),
converter => Assert.IsType<TimeOnlyConverter>(converter),
#if NET8_0_OR_GREATER
converter => Assert.IsType<JsonStringEnumConverter<LogLevel>>(converter));
converter => Assert.IsType<LogLevelJsonConverter>(converter));
#elif NET6_0
converter => Assert.IsType<JsonStringEnumConverter>(converter));
converter => Assert.IsType<LogLevelJsonConverter>(converter));
#endif

Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System.Text.Json;
using AWS.Lambda.Powertools.Logging.Serializers;
using Microsoft.Extensions.Logging;
using Xunit;

namespace AWS.Lambda.Powertools.Logging.Tests.Utilities;

public class LogLevelJsonConverterTests
{
private readonly LogLevelJsonConverter _converter;
private readonly JsonSerializerOptions _options;

public LogLevelJsonConverterTests()
{
_converter = new LogLevelJsonConverter();
_options = new JsonSerializerOptions
{
Converters = { _converter }
};
}

[Theory]
[InlineData("Information", LogLevel.Information)]
[InlineData("Error", LogLevel.Error)]
[InlineData("Warning", LogLevel.Warning)]
[InlineData("Debug", LogLevel.Debug)]
[InlineData("Trace", LogLevel.Trace)]
[InlineData("Critical", LogLevel.Critical)]
[InlineData("None", LogLevel.None)]
public void Read_ValidLogLevel_ReturnsCorrectEnum(string input, LogLevel expected)
{
// Arrange
var json = $"\"{input}\"";

// Act
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);

// Assert
Assert.Equal(expected, result);
}

[Theory]
[InlineData("information", LogLevel.Information)]
[InlineData("ERROR", LogLevel.Error)]
[InlineData("Warning", LogLevel.Warning)]
[InlineData("deBUG", LogLevel.Debug)]
public void Read_CaseInsensitive_ReturnsCorrectEnum(string input, LogLevel expected)
{
// Arrange
var json = $"\"{input}\"";

// Act
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);

// Assert
Assert.Equal(expected, result);
}

[Theory]
[InlineData("")]
[InlineData("InvalidLevel")]
[InlineData("NotALevel")]
public void Read_InvalidLogLevel_ReturnsDefault(string input)
{
// Arrange
var json = $"\"{input}\"";

// Act
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);

// Assert
Assert.Equal(default(LogLevel), result);
}

[Fact]
public void Read_NullValue_ReturnsDefault()
{
// Arrange
var json = "null";

// Act
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);

// Assert
Assert.Equal(default(LogLevel), result);
}

[Theory]
[InlineData(LogLevel.Information, "Information")]
[InlineData(LogLevel.Error, "Error")]
[InlineData(LogLevel.Warning, "Warning")]
[InlineData(LogLevel.Debug, "Debug")]
[InlineData(LogLevel.Trace, "Trace")]
[InlineData(LogLevel.Critical, "Critical")]
[InlineData(LogLevel.None, "None")]
public void Write_ValidLogLevel_WritesCorrectString(LogLevel input, string expected)
{
// Act
var result = JsonSerializer.Serialize(input, _options);

// Assert
Assert.Equal($"\"{expected}\"", result);
}

[Fact]
public void Write_DefaultLogLevel_WritesCorrectString()
{
// Arrange
var input = default(LogLevel);

// Act
var result = JsonSerializer.Serialize(input, _options);

// Assert
Assert.Equal($"\"{input}\"", result);
}

[Fact]
public void Converter_CanConvert_LogLevelType()
{
// Act
var canConvert = _converter.CanConvert(typeof(LogLevel));

// Assert
Assert.True(canConvert);
}

[Fact]
public void SerializeAndDeserialize_RoundTrip_MaintainsValue()
{
// Arrange
var originalValue = LogLevel.Information;

// Act
var serialized = JsonSerializer.Serialize(originalValue, _options);
var deserialized = JsonSerializer.Deserialize<LogLevel>(serialized, _options);

// Assert
Assert.Equal(originalValue, deserialized);
}
}
Loading
Loading