Skip to content

Commit 6a2e23a

Browse files
authored
Merge pull request #152 from amirkaws/amirkaws-custom-exception-json-converter
fix: Custom exception JSON converter
2 parents efe4a56 + 2140040 commit 6a2e23a

File tree

9 files changed

+538
-30
lines changed

9 files changed

+538
-30
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Text.Json;
18+
using System.Text.Json.Serialization;
19+
20+
namespace AWS.Lambda.Powertools.Logging.Internal.Converters;
21+
22+
/// <summary>
23+
/// Converts an byte[] to JSON.
24+
/// </summary>
25+
internal class ByteArrayConverter : JsonConverter<byte[]>
26+
{
27+
/// <summary>
28+
/// Converter throws NotSupportedException. Deserializing ByteArray is not allowed.
29+
/// </summary>
30+
/// <param name="reader">Reference to the JsonReader</param>
31+
/// <param name="typeToConvert">The type which should be converted.</param>
32+
/// <param name="options">The Json serializer options.</param>
33+
/// <returns></returns>
34+
/// <exception cref="NotSupportedException"></exception>
35+
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
36+
{
37+
throw new NotSupportedException("Deserializing ByteArray is not allowed");
38+
}
39+
40+
/// <summary>
41+
/// Write the exception value as JSON.
42+
/// </summary>
43+
/// <param name="writer">The unicode JsonWriter.</param>
44+
/// <param name="values">The byte array.</param>
45+
/// <param name="options">The JsonSerializer options.</param>
46+
public override void Write(Utf8JsonWriter writer, byte[] values, JsonSerializerOptions options)
47+
{
48+
if (values == null)
49+
{
50+
writer.WriteNullValue();
51+
}
52+
else
53+
{
54+
writer.WriteStartArray();
55+
56+
foreach (var value in values)
57+
{
58+
writer.WriteNumberValue(value);
59+
}
60+
61+
writer.WriteEndArray();
62+
}
63+
}
64+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Text.Json;
19+
using System.Text.Json.Serialization;
20+
21+
namespace AWS.Lambda.Powertools.Logging.Internal.Converters;
22+
23+
/// <summary>
24+
/// JsonConvert to handle the AWS SDK for .NET custom enum classes that derive from the class called ConstantClass.
25+
/// </summary>
26+
public class ConstantClassConverter : JsonConverter<object>
27+
{
28+
private static readonly HashSet<string> ConstantClassNames = new()
29+
{
30+
"Amazon.S3.EventType",
31+
"Amazon.DynamoDBv2.OperationType",
32+
"Amazon.DynamoDBv2.StreamViewType"
33+
};
34+
35+
/// <summary>
36+
/// Check to see if the type is derived from ConstantClass.
37+
/// </summary>
38+
/// <param name="typeToConvert">The type which should be converted.</param>
39+
/// <returns>True if the type is derived from ConstantClass, False otherwise.</returns>
40+
public override bool CanConvert(Type typeToConvert)
41+
{
42+
return ConstantClassNames.Contains(typeToConvert.FullName);
43+
}
44+
45+
/// <summary>
46+
/// Converter throws NotSupportedException. Deserializing ConstantClass is not allowed.
47+
/// </summary>
48+
/// <param name="reader">Reference to the JsonReader</param>
49+
/// <param name="typeToConvert">The type which should be converted.</param>
50+
/// <param name="options">The Json serializer options.</param>
51+
/// <returns></returns>
52+
/// <exception cref="NotSupportedException"></exception>
53+
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
54+
{
55+
throw new NotSupportedException("Deserializing ConstantClass is not allowed");
56+
}
57+
58+
/// <summary>
59+
/// Write the ConstantClass instance as JSON.
60+
/// </summary>
61+
/// <param name="writer">The unicode JsonWriter.</param>
62+
/// <param name="value">The exception instance.</param>
63+
/// <param name="options">The JsonSerializer options.</param>
64+
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
65+
{
66+
writer.WriteStringValue(value.ToString());
67+
}
68+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using System.Text.Json;
19+
using System.Text.Json.Serialization;
20+
21+
namespace AWS.Lambda.Powertools.Logging.Internal.Converters;
22+
23+
/// <summary>
24+
/// Converts an exception to JSON.
25+
/// </summary>
26+
internal class ExceptionConverter : JsonConverter<Exception>
27+
{
28+
/// <summary>
29+
/// Determines whether the type can be converted.
30+
/// </summary>
31+
/// <param name="typeToConvert">The type which should be converted.</param>
32+
/// <returns>True if the type can be converted, False otherwise.</returns>
33+
public override bool CanConvert(Type typeToConvert)
34+
{
35+
return typeof(Exception).IsAssignableFrom(typeToConvert);
36+
}
37+
38+
/// <summary>
39+
/// Converter throws NotSupportedException. Deserializing Exception is not allowed.
40+
/// </summary>
41+
/// <param name="reader">Reference to the JsonReader</param>
42+
/// <param name="typeToConvert">The type which should be converted.</param>
43+
/// <param name="options">The Json serializer options.</param>
44+
/// <returns></returns>
45+
/// <exception cref="NotSupportedException"></exception>
46+
public override Exception Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
47+
{
48+
throw new NotSupportedException("Deserializing Exception is not allowed");
49+
}
50+
51+
/// <summary>
52+
/// Write the exception value as JSON.
53+
/// </summary>
54+
/// <param name="writer">The unicode JsonWriter.</param>
55+
/// <param name="value">The exception instance.</param>
56+
/// <param name="options">The JsonSerializer options.</param>
57+
public override void Write(Utf8JsonWriter writer, Exception value, JsonSerializerOptions options)
58+
{
59+
var exceptionType = value.GetType();
60+
var properties = exceptionType.GetProperties()
61+
.Where(prop => prop.Name != nameof(Exception.TargetSite))
62+
.Select(prop => new { prop.Name, Value = prop.GetValue(value) });
63+
64+
if (options.DefaultIgnoreCondition == JsonIgnoreCondition.WhenWritingNull)
65+
properties = properties.Where(prop => prop.Value != null);
66+
67+
var props = properties.ToArray();
68+
if (!props.Any())
69+
return;
70+
71+
writer.WriteStartObject();
72+
writer.WriteString(ApplyPropertyNamingPolicy("Type", options), exceptionType.FullName);
73+
74+
foreach (var prop in props)
75+
{
76+
switch (prop.Value)
77+
{
78+
case IntPtr intPtr:
79+
writer.WriteNumber(ApplyPropertyNamingPolicy(prop.Name, options), intPtr.ToInt64());
80+
break;
81+
case UIntPtr uIntPtr:
82+
writer.WriteNumber(ApplyPropertyNamingPolicy(prop.Name, options), uIntPtr.ToUInt64());
83+
break;
84+
case Type propType:
85+
writer.WriteString(ApplyPropertyNamingPolicy(prop.Name, options), propType.FullName);
86+
break;
87+
default:
88+
writer.WritePropertyName(ApplyPropertyNamingPolicy(prop.Name, options));
89+
JsonSerializer.Serialize(writer, prop.Value, options);
90+
break;
91+
}
92+
}
93+
94+
writer.WriteEndObject();
95+
}
96+
97+
/// <summary>
98+
/// Applying the property naming policy to property name
99+
/// </summary>
100+
/// <param name="propertyName">The name of the property</param>
101+
/// <param name="options">The JsonSerializer options.</param>
102+
/// <returns></returns>
103+
private static string ApplyPropertyNamingPolicy(string propertyName, JsonSerializerOptions options)
104+
{
105+
return !string.IsNullOrWhiteSpace(propertyName) && options?.PropertyNamingPolicy is not null
106+
? options.PropertyNamingPolicy.ConvertName(propertyName)
107+
: propertyName;
108+
}
109+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.IO;
18+
using System.Text.Json;
19+
using System.Text.Json.Serialization;
20+
21+
namespace AWS.Lambda.Powertools.Logging.Internal.Converters;
22+
23+
/// <summary>
24+
/// Handles converting MemoryStreams to base 64 strings.
25+
/// </summary>
26+
internal class MemoryStreamConverter : JsonConverter<MemoryStream>
27+
{
28+
/// <summary>
29+
/// Converter throws NotSupportedException. Deserializing MemoryStream is not allowed.
30+
/// </summary>
31+
/// <param name="reader">Reference to the JsonReader</param>
32+
/// <param name="typeToConvert">The type which should be converted.</param>
33+
/// <param name="options">The Json serializer options.</param>
34+
/// <returns></returns>
35+
/// <exception cref="NotSupportedException"></exception>
36+
public override MemoryStream Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
37+
{
38+
throw new NotSupportedException("Deserializing MemoryStream is not allowed");
39+
}
40+
41+
/// <summary>
42+
/// Write the MemoryStream as a base 64 string.
43+
/// </summary>
44+
/// <param name="writer">The unicode JsonWriter.</param>
45+
/// <param name="value">The MemoryStream instance.</param>
46+
/// <param name="options">The JsonSerializer options.</param>
47+
public override void Write(Utf8JsonWriter writer, MemoryStream value, JsonSerializerOptions options)
48+
{
49+
writer.WriteStringValue(Convert.ToBase64String(value.ToArray()));
50+
}
51+
}

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
using System.IO;
1818
using System.Linq;
1919
using System.Text.Json;
20+
using System.Text.Json.Serialization;
2021
using AWS.Lambda.Powertools.Common;
22+
using AWS.Lambda.Powertools.Logging.Internal.Converters;
2123
using Microsoft.Extensions.Logging;
2224

2325
namespace AWS.Lambda.Powertools.Logging.Internal;
@@ -93,6 +95,18 @@ internal class LoggingAspectHandler : IMethodAspectHandler
9395
/// Specify to clear Lambda Context on exit
9496
/// </summary>
9597
private bool _clearLambdaContext;
98+
99+
/// <summary>
100+
/// The JsonSerializer options
101+
/// </summary>
102+
private static JsonSerializerOptions _jsonSerializerOptions;
103+
104+
/// <summary>
105+
/// Get JsonSerializer options.
106+
/// </summary>
107+
/// <value>The current configuration.</value>
108+
private static JsonSerializerOptions JsonSerializerOptions =>
109+
_jsonSerializerOptions ??= BuildJsonSerializerOptions();
96110

97111
/// <summary>
98112
/// Initializes a new instance of the <see cref="LoggingAspectHandler" /> class.
@@ -259,6 +273,22 @@ private void CaptureLambdaContext(AspectEventArgs eventArgs)
259273
_systemWrapper.LogLine(
260274
"Skipping Lambda Context injection because ILambdaContext context parameter not found.");
261275
}
276+
277+
/// <summary>
278+
/// Builds JsonSerializer options.
279+
/// </summary>
280+
private static JsonSerializerOptions BuildJsonSerializerOptions()
281+
{
282+
var jsonOptions = new JsonSerializerOptions
283+
{
284+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
285+
};
286+
jsonOptions.Converters.Add(new ByteArrayConverter());
287+
jsonOptions.Converters.Add(new ExceptionConverter());
288+
jsonOptions.Converters.Add(new MemoryStreamConverter());
289+
jsonOptions.Converters.Add(new ConstantClassConverter());
290+
return jsonOptions;
291+
}
262292

263293
/// <summary>
264294
/// Captures the correlation identifier.
@@ -286,7 +316,7 @@ private void CaptureCorrelationId(object eventArg)
286316
try
287317
{
288318
var correlationId = string.Empty;
289-
var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg));
319+
var jsonDoc = JsonDocument.Parse(JsonSerializer.Serialize(eventArg, JsonSerializerOptions));
290320
var element = jsonDoc.RootElement;
291321

292322
for (var i = 0; i < correlationIdPaths.Length; i++)

0 commit comments

Comments
 (0)