diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs new file mode 100644 index 00000000..a6f969e5 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.Logging.Internal.Converters; + +/// +/// DateOnly JSON converter +/// +public class DateOnlyConverter : JsonConverter +{ + private const string DateFormat = "yyyy-MM-dd"; + + /// + /// Converts DateOnly from JSON. + /// + /// + /// + /// + /// + public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return DateOnly.ParseExact(reader.GetString()!, DateFormat, CultureInfo.InvariantCulture); + } + + /// + /// Converts DateOnly to JSON. + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(DateFormat, CultureInfo.InvariantCulture)); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs new file mode 100644 index 00000000..737362ca --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Globalization; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.Logging.Internal.Converters; + +/// +/// TimeOnly JSON converter +/// +internal class TimeOnlyConverter : JsonConverter +{ + private const string TimeFormat = "HH:mm:ss.FFFFFFF"; + + /// + /// Converts TimeOnly from JSON. + /// + /// + /// + /// + /// + public override TimeOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return TimeOnly.ParseExact(reader.GetString()!, TimeFormat, CultureInfo.InvariantCulture); + } + + /// + /// Converts TimeOnly to JSON. + /// + /// + /// + /// + public override void Write(Utf8JsonWriter writer, TimeOnly value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString(TimeFormat, CultureInfo.InvariantCulture)); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs index 4b262490..441cdc22 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectHandler.cs @@ -288,6 +288,8 @@ private static JsonSerializerOptions BuildJsonSerializerOptions() jsonOptions.Converters.Add(new ExceptionConverter()); jsonOptions.Converters.Add(new MemoryStreamConverter()); jsonOptions.Converters.Add(new ConstantClassConverter()); + jsonOptions.Converters.Add(new DateOnlyConverter()); + jsonOptions.Converters.Add(new TimeOnlyConverter()); return jsonOptions; } diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs index 265090aa..707d4a8e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs @@ -466,6 +466,8 @@ private JsonSerializerOptions BuildJsonSerializerOptions() jsonOptions.Converters.Add(new ExceptionConverter()); jsonOptions.Converters.Add(new MemoryStreamConverter()); jsonOptions.Converters.Add(new ConstantClassConverter()); + jsonOptions.Converters.Add(new DateOnlyConverter()); + jsonOptions.Converters.Add(new TimeOnlyConverter()); jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs index 71db5932..c0fd3c09 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs @@ -1219,5 +1219,87 @@ public void Log_Set_Execution_Environment_Context() $"{Constants.FeatureContextIdentifier}/Logger/{assemblyVersion}"); env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); } + + [Fact] + public void Log_Should_Serialize_DateOnly() + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); + + var logger = new PowertoolsLogger(loggerName, configurations, systemWrapper, () => + new LoggerConfiguration + { + Service = null, + MinimumLevel = null, + LoggerOutputCase = LoggerOutputCase.CamelCase + }); + + var message = new + { + PropOne = "Value 1", + PropTwo = "Value 2", + Date = new DateOnly(2022, 1, 1) + }; + + logger.LogInformation(message); + + // Assert + systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"date\":\"2022-01-01\"}") + ) + ); + } + + [Fact] + public void Log_Should_Serialize_TimeOnly() + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); + + var logger = new PowertoolsLogger(loggerName, configurations, systemWrapper, () => + new LoggerConfiguration + { + Service = null, + MinimumLevel = null, + LoggerOutputCase = LoggerOutputCase.CamelCase + }); + + var message = new + { + PropOne = "Value 1", + PropTwo = "Value 2", + Time = new TimeOnly(12, 0, 0) + }; + + logger.LogInformation(message); + + // Assert + systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"time\":\"12:00:00\"}") + ) + ); + } } } \ No newline at end of file