Skip to content

Commit 5b27080

Browse files
authored
Merge pull request #670 from hjgraca/fix(logging)-enum-serialization
chore: Fix logging enum serialization
2 parents 5eb8338 + c8684e8 commit 5b27080

File tree

8 files changed

+346
-25
lines changed

8 files changed

+346
-25
lines changed

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

+19-3
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
* permissions and limitations under the License.
1414
*/
1515

16+
using System;
1617
using System.Collections.Generic;
1718
using System.Linq;
19+
using System.Reflection;
20+
using System.Text.Json.Serialization;
1821

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

@@ -38,18 +41,31 @@ internal static object ObjectToDictionary(object anonymousObject)
3841
return new Dictionary<string, object>();
3942
}
4043

41-
if (anonymousObject.GetType().Namespace is not null)
44+
var type = anonymousObject.GetType();
45+
46+
if (type.IsEnum)
47+
{
48+
return anonymousObject;
49+
}
50+
51+
if (type.Namespace != null && !type.IsEnum)
4252
{
4353
return anonymousObject;
4454
}
4555

46-
return anonymousObject.GetType().GetProperties()
56+
return type.GetProperties()
4757
.Where(prop => prop.GetValue(anonymousObject, null) != null)
4858
.ToDictionary(
4959
prop => prop.Name,
5060
prop => {
5161
var value = prop.GetValue(anonymousObject, null);
52-
return value != null ? ObjectToDictionary(value) : string.Empty;
62+
if (value == null)
63+
return string.Empty;
64+
65+
if (value.GetType().IsEnum)
66+
return value;
67+
68+
return ObjectToDictionary(value);
5369
}
5470
);
5571
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace AWS.Lambda.Powertools.Logging.Serializers;
7+
8+
internal class LogLevelJsonConverter : JsonConverter<LogLevel>
9+
{
10+
public override LogLevel Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
11+
{
12+
return Enum.TryParse<LogLevel>(reader.GetString(),true, out var val) ? val : default;
13+
}
14+
15+
public override void Write(Utf8JsonWriter writer, LogLevel value, JsonSerializerOptions options)
16+
{
17+
writer.WriteStringValue(value.ToString());
18+
}
19+
}

libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ private static JsonSerializerOptions BuildJsonSerializerOptions()
153153
_jsonOptions.Converters.Add(new TimeOnlyConverter());
154154

155155
#if NET8_0_OR_GREATER
156-
_jsonOptions.Converters.Add(new JsonStringEnumConverter<LogLevel>());
156+
_jsonOptions.Converters.Add(new LogLevelJsonConverter());
157157
#elif NET6_0
158-
_jsonOptions.Converters.Add(new JsonStringEnumConverter());
158+
_jsonOptions.Converters.Add(new LogLevelJsonConverter());
159159
#endif
160160

161161
_jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;

libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs

+46-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using System.Linq;
2020
using System.Reflection;
2121
using System.Text.Json;
22+
using System.Text.Json.Serialization;
2223
using Amazon.Lambda.Core;
2324
using Amazon.Lambda.TestUtilities;
2425
using AWS.Lambda.Powertools.Common;
@@ -42,7 +43,50 @@ public LogFormatterTest()
4243
{
4344
_testHandler = new TestHandlers();
4445
}
45-
46+
47+
[Fact]
48+
public void Serialize_ShouldHandleEnumValues()
49+
{
50+
var consoleOut = Substitute.For<StringWriter>();
51+
SystemWrapper.Instance.SetOut(consoleOut);
52+
var lambdaContext = new TestLambdaContext
53+
{
54+
FunctionName = "funtionName",
55+
FunctionVersion = "version",
56+
InvokedFunctionArn = "function::arn",
57+
AwsRequestId = "requestId",
58+
MemoryLimitInMB = 128
59+
};
60+
61+
var handler = new TestHandlers();
62+
handler.TestEnums("fake", lambdaContext);
63+
64+
consoleOut.Received(1).WriteLine(Arg.Is<string>(i =>
65+
i.Contains("\"message\":5")
66+
));
67+
consoleOut.Received(1).WriteLine(Arg.Is<string>(i =>
68+
i.Contains("\"message\":\"Dog\"")
69+
));
70+
71+
var json = JsonSerializer.Serialize(Pet.Dog, PowertoolsLoggingSerializer.GetSerializerOptions());
72+
Assert.Contains("Dog", json);
73+
}
74+
75+
[JsonConverter(typeof(JsonStringEnumConverter))]
76+
public enum Pet
77+
{
78+
Cat = 1,
79+
Dog = 3,
80+
Lizard = 5
81+
}
82+
83+
public enum Thing
84+
{
85+
One = 1,
86+
Three = 3,
87+
Five = 5
88+
}
89+
4690
[Fact]
4791
public void Log_WhenCustomFormatter_LogsCustomFormat()
4892
{
@@ -204,7 +248,7 @@ public void Should_Log_CustomFormatter_When_Decorated()
204248
consoleOut.Received(1).WriteLine(
205249
Arg.Is<string>(i =>
206250
i.Contains(
207-
"\"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\""))
251+
"\"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\""))
208252
);
209253
#else
210254
consoleOut.Received(1).WriteLine(

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

+23
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
* permissions and limitations under the License.
1414
*/
1515

16+
using System.Text.Json.Serialization;
1617
using Amazon.Lambda.APIGatewayEvents;
1718
using Amazon.Lambda.ApplicationLoadBalancerEvents;
1819
using Amazon.Lambda.CloudWatchEvents;
@@ -158,4 +159,26 @@ public void TestLogNoDecorator()
158159
{
159160
Logger.LogInformation("test");
160161
}
162+
163+
[Logging(Service = "test", LoggerOutputCase = LoggerOutputCase.SnakeCase)]
164+
public void TestEnums(string input, ILambdaContext context)
165+
{
166+
Logger.LogInformation(Pet.Dog);
167+
Logger.LogInformation(Thing.Five);
168+
}
169+
170+
public enum Thing
171+
{
172+
One = 1,
173+
Three = 3,
174+
Five = 5
175+
}
176+
177+
[JsonConverter(typeof(JsonStringEnumConverter))]
178+
public enum Pet
179+
{
180+
Cat = 1,
181+
Dog = 3,
182+
Lizard = 5
183+
}
161184
}

libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,9 @@ public void SerializerOptions_ShouldHaveCorrectDefaultSettings()
6262
converter => Assert.IsType<DateOnlyConverter>(converter),
6363
converter => Assert.IsType<TimeOnlyConverter>(converter),
6464
#if NET8_0_OR_GREATER
65-
converter => Assert.IsType<JsonStringEnumConverter<LogLevel>>(converter));
65+
converter => Assert.IsType<LogLevelJsonConverter>(converter));
6666
#elif NET6_0
67-
converter => Assert.IsType<JsonStringEnumConverter>(converter));
67+
converter => Assert.IsType<LogLevelJsonConverter>(converter));
6868
#endif
6969

7070
Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder);
@@ -90,9 +90,9 @@ public void SerializerOptions_ShouldHaveCorrectDefaultSettings_WhenDynamic()
9090
converter => Assert.IsType<DateOnlyConverter>(converter),
9191
converter => Assert.IsType<TimeOnlyConverter>(converter),
9292
#if NET8_0_OR_GREATER
93-
converter => Assert.IsType<JsonStringEnumConverter<LogLevel>>(converter));
93+
converter => Assert.IsType<LogLevelJsonConverter>(converter));
9494
#elif NET6_0
95-
converter => Assert.IsType<JsonStringEnumConverter>(converter));
95+
converter => Assert.IsType<LogLevelJsonConverter>(converter));
9696
#endif
9797

9898
Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
using System.Text.Json;
2+
using AWS.Lambda.Powertools.Logging.Serializers;
3+
using Microsoft.Extensions.Logging;
4+
using Xunit;
5+
6+
namespace AWS.Lambda.Powertools.Logging.Tests.Utilities;
7+
8+
public class LogLevelJsonConverterTests
9+
{
10+
private readonly LogLevelJsonConverter _converter;
11+
private readonly JsonSerializerOptions _options;
12+
13+
public LogLevelJsonConverterTests()
14+
{
15+
_converter = new LogLevelJsonConverter();
16+
_options = new JsonSerializerOptions
17+
{
18+
Converters = { _converter }
19+
};
20+
}
21+
22+
[Theory]
23+
[InlineData("Information", LogLevel.Information)]
24+
[InlineData("Error", LogLevel.Error)]
25+
[InlineData("Warning", LogLevel.Warning)]
26+
[InlineData("Debug", LogLevel.Debug)]
27+
[InlineData("Trace", LogLevel.Trace)]
28+
[InlineData("Critical", LogLevel.Critical)]
29+
[InlineData("None", LogLevel.None)]
30+
public void Read_ValidLogLevel_ReturnsCorrectEnum(string input, LogLevel expected)
31+
{
32+
// Arrange
33+
var json = $"\"{input}\"";
34+
35+
// Act
36+
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);
37+
38+
// Assert
39+
Assert.Equal(expected, result);
40+
}
41+
42+
[Theory]
43+
[InlineData("information", LogLevel.Information)]
44+
[InlineData("ERROR", LogLevel.Error)]
45+
[InlineData("Warning", LogLevel.Warning)]
46+
[InlineData("deBUG", LogLevel.Debug)]
47+
public void Read_CaseInsensitive_ReturnsCorrectEnum(string input, LogLevel expected)
48+
{
49+
// Arrange
50+
var json = $"\"{input}\"";
51+
52+
// Act
53+
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);
54+
55+
// Assert
56+
Assert.Equal(expected, result);
57+
}
58+
59+
[Theory]
60+
[InlineData("")]
61+
[InlineData("InvalidLevel")]
62+
[InlineData("NotALevel")]
63+
public void Read_InvalidLogLevel_ReturnsDefault(string input)
64+
{
65+
// Arrange
66+
var json = $"\"{input}\"";
67+
68+
// Act
69+
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);
70+
71+
// Assert
72+
Assert.Equal(default(LogLevel), result);
73+
}
74+
75+
[Fact]
76+
public void Read_NullValue_ReturnsDefault()
77+
{
78+
// Arrange
79+
var json = "null";
80+
81+
// Act
82+
var result = JsonSerializer.Deserialize<LogLevel>(json, _options);
83+
84+
// Assert
85+
Assert.Equal(default(LogLevel), result);
86+
}
87+
88+
[Theory]
89+
[InlineData(LogLevel.Information, "Information")]
90+
[InlineData(LogLevel.Error, "Error")]
91+
[InlineData(LogLevel.Warning, "Warning")]
92+
[InlineData(LogLevel.Debug, "Debug")]
93+
[InlineData(LogLevel.Trace, "Trace")]
94+
[InlineData(LogLevel.Critical, "Critical")]
95+
[InlineData(LogLevel.None, "None")]
96+
public void Write_ValidLogLevel_WritesCorrectString(LogLevel input, string expected)
97+
{
98+
// Act
99+
var result = JsonSerializer.Serialize(input, _options);
100+
101+
// Assert
102+
Assert.Equal($"\"{expected}\"", result);
103+
}
104+
105+
[Fact]
106+
public void Write_DefaultLogLevel_WritesCorrectString()
107+
{
108+
// Arrange
109+
var input = default(LogLevel);
110+
111+
// Act
112+
var result = JsonSerializer.Serialize(input, _options);
113+
114+
// Assert
115+
Assert.Equal($"\"{input}\"", result);
116+
}
117+
118+
[Fact]
119+
public void Converter_CanConvert_LogLevelType()
120+
{
121+
// Act
122+
var canConvert = _converter.CanConvert(typeof(LogLevel));
123+
124+
// Assert
125+
Assert.True(canConvert);
126+
}
127+
128+
[Fact]
129+
public void SerializeAndDeserialize_RoundTrip_MaintainsValue()
130+
{
131+
// Arrange
132+
var originalValue = LogLevel.Information;
133+
134+
// Act
135+
var serialized = JsonSerializer.Serialize(originalValue, _options);
136+
var deserialized = JsonSerializer.Deserialize<LogLevel>(serialized, _options);
137+
138+
// Assert
139+
Assert.Equal(originalValue, deserialized);
140+
}
141+
}

0 commit comments

Comments
 (0)