diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj b/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj index 0e90e99b..e20deb7e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs index abbefc5f..a72e205e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Amazon.Lambda.Core; using AspectInjector.Broker; using AWS.Lambda.Powertools.Common; @@ -34,11 +35,6 @@ public class MetricsAspect /// private static bool _isColdStart; - /// - /// Specify to clear Lambda Context on exit - /// - private bool _clearLambdaContext; - /// /// Gets the metrics instance. /// @@ -100,14 +96,14 @@ public void Before( var nameSpace = _metricsInstance.GetNamespace(); var service = _metricsInstance.GetService(); Dictionary dimensions = null; - - _clearLambdaContext = PowertoolsLambdaContext.Extract(eventArgs); - - if (PowertoolsLambdaContext.Instance is not null) + + var context = GetContext(eventArgs); + + if (context is not null) { dimensions = new Dictionary { - { "FunctionName", PowertoolsLambdaContext.Instance.FunctionName } + { "FunctionName", context.FunctionName } }; } @@ -129,8 +125,6 @@ public void Before( public void Exit() { _metricsInstance.Flush(); - if (_clearLambdaContext) - PowertoolsLambdaContext.Clear(); } @@ -142,6 +136,21 @@ internal static void ResetForTest() _metricsInstance = null; _isColdStart = true; Metrics.ResetForTest(); - PowertoolsLambdaContext.Clear(); + } + + /// + /// Gets the Lambda context + /// + /// + /// + private static ILambdaContext GetContext(AspectEventArgs args) + { + var index = Array.FindIndex(args.Method.GetParameters(), p => p.ParameterType == typeof(ILambdaContext)); + if (index >= 0) + { + return (ILambdaContext)args.Args[index]; + } + + return null; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs index 2112c1c5..3929beec 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricResolution.cs @@ -1,4 +1,3 @@ -using System.Runtime.Serialization; using System.Text.Json.Serialization; namespace AWS.Lambda.Powertools.Metrics; @@ -6,6 +5,7 @@ namespace AWS.Lambda.Powertools.Metrics; /// /// Enum MetricResolution /// +[JsonConverter(typeof(MetricResolutionJsonConverter))] public enum MetricResolution { /// diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs index 132b10f3..2fbb389d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs @@ -24,7 +24,7 @@ namespace AWS.Lambda.Powertools.Metrics; #if NET8_0_OR_GREATER [JsonConverter(typeof(JsonStringEnumConverter))] #else -[JsonConverter(typeof(StringEnumConverter))] +[JsonConverter(typeof(JsonStringEnumConverter))] #endif public enum MetricUnit { diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricResolutionJsonConverter.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricResolutionJsonConverter.cs new file mode 100644 index 00000000..710b1a2a --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricResolutionJsonConverter.cs @@ -0,0 +1,49 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AWS.Lambda.Powertools.Metrics; + +/// +/// Class MetricResolutionJsonConverter. +/// Implements the +/// +/// +public class MetricResolutionJsonConverter : JsonConverter +{ + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// The being converted. + /// The being used. + /// The object value. + public override MetricResolution Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var stringValue = reader.GetString(); + if (int.TryParse(stringValue, out int value)) + { + return (MetricResolution)value; + } + } + else if (reader.TokenType == JsonTokenType.Number) + { + return (MetricResolution)reader.GetInt32(); + } + + throw new JsonException(); + } + + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The value to convert. + /// The being used. + public override void Write(Utf8JsonWriter writer, MetricResolution value, JsonSerializerOptions options) + { + writer.WriteNumberValue((int)value); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/StringEnumConverter.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/StringEnumConverter.cs deleted file mode 100644 index 242ee90b..00000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/StringEnumConverter.cs +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.Linq; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.Metrics; - -#if NET6_0 - -/// -/// Class StringEnumConverter. -/// Implements the -/// .NET 6 only -/// -/// -public class StringEnumConverter : JsonConverterFactory -{ - /// - /// The allow integer values - /// - private readonly bool _allowIntegerValues; - - /// - /// The base converter - /// - private readonly JsonStringEnumConverter _baseConverter; - - /// - /// The naming policy - /// - private readonly JsonNamingPolicy _namingPolicy; - - /// - /// Initializes a new instance of the class. - /// - public StringEnumConverter() : this(null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The naming policy. - /// if set to true [allow integer values]. - private StringEnumConverter(JsonNamingPolicy namingPolicy = null, bool allowIntegerValues = true) - { - _namingPolicy = namingPolicy; - _allowIntegerValues = allowIntegerValues; - _baseConverter = new JsonStringEnumConverter(namingPolicy, allowIntegerValues); - } - - /// - /// When overridden in a derived class, determines whether the converter instance can convert the specified object - /// type. - /// - /// The type of the object to check whether it can be converted by this converter instance. - /// - /// if the instance can convert the specified object type; otherwise, - /// . - /// - public override bool CanConvert(Type typeToConvert) - { - return _baseConverter.CanConvert(typeToConvert); - } - - /// - /// Creates a converter for a specified type. - /// - /// The type handled by the converter. - /// The serialization options to use. - /// A converter for which type is compatible with . - public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) - { - var query = from field in typeToConvert.GetFields(BindingFlags.Public | BindingFlags.Static) - let attr = field.GetCustomAttribute() - where attr != null - select (field.Name, attr.Value); - var dictionary = query.ToDictionary(p => p.Item1, p => p.Item2); - return dictionary.Count > 0 - ? new JsonStringEnumConverter(new DictionaryLookupNamingPolicy(dictionary, _namingPolicy), - _allowIntegerValues).CreateConverter(typeToConvert, options) - : _baseConverter.CreateConverter(typeToConvert, options); - } -} -#endif \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs index 8ae59b9f..8447575d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs @@ -169,8 +169,14 @@ public void HandleWithLambdaContext(ILambdaContext context) } [Metrics(Namespace = "ns", Service = "svc")] - public void HandleWithLambdaContextAndMetrics(TestLambdaContext context) + public void HandleColdStartNoContext() { Metrics.AddMetric("MyMetric", 1); } + + [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true)] + public void HandleWithParamAndLambdaContext(string input, ILambdaContext context) + { + + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs index 556d6cce..075da0a0 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs @@ -88,6 +88,46 @@ public void When_LambdaContext_Should_Add_FunctioName_Dimension_CaptureColdStart "\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"],[\"Service\"]]}]}", metricsOutput); } + + [Fact] + public void When_LambdaContext_And_Parameter_Should_Add_FunctioName_Dimension_CaptureColdStart() + { + // Arrange + var context = new TestLambdaContext + { + FunctionName = "My Function with context" + }; + + // Act + _handler.HandleWithParamAndLambdaContext("Hello",context); + var metricsOutput = _consoleOut.ToString(); + + // Assert + Assert.Contains( + "\"FunctionName\":\"My Function with context\"", + metricsOutput); + + Assert.Contains( + "\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"],[\"Service\"]]}]}", + metricsOutput); + } + + [Fact] + public void When_No_LambdaContext_Should_Not_Add_FunctioName_Dimension_CaptureColdStart() + { + // Act + _handler.HandleColdStartNoContext(); + var metricsOutput = _consoleOut.ToString(); + + // Assert + Assert.DoesNotContain( + "\"FunctionName\"", + metricsOutput); + + Assert.Contains( + "\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", + metricsOutput); + } public void Dispose() { diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/SerializationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/SerializationTests.cs new file mode 100644 index 00000000..2433c5a2 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/SerializationTests.cs @@ -0,0 +1,80 @@ +using System; +using System.Text.Json; +using Xunit; + +namespace AWS.Lambda.Powertools.Metrics.Tests; + +[Collection("Sequential")] +public class SerializationTests +{ + [Fact] + public void Metrics_Resolution_JsonConverter() + { + // Arrange + var options = new JsonSerializerOptions(); + options.Converters.Add(new MetricResolutionJsonConverter()); + + { + var myInt = JsonSerializer.Deserialize(1, options); + Assert.Equal(MetricResolution.High, myInt); + Assert.Equal("1", JsonSerializer.Serialize(myInt, options)); + } + + { + var myInt = JsonSerializer.Deserialize("1", options); + Assert.Equal(MetricResolution.High, myInt); + Assert.Equal("1", JsonSerializer.Serialize(myInt, options)); + } + + { + var myInt = JsonSerializer.Deserialize("60", options); + Assert.Equal(MetricResolution.Standard, myInt); + Assert.Equal("60", JsonSerializer.Serialize(myInt, options)); + } + + { + var myInt = JsonSerializer.Deserialize("0", options); + Assert.Equal(MetricResolution.Default, myInt); + Assert.Equal("0", JsonSerializer.Serialize(myInt, options)); + } + + { + var myInt = JsonSerializer.Deserialize(@"""1""", options); + Assert.Equal(MetricResolution.High, myInt); + Assert.Equal("1", JsonSerializer.Serialize(myInt, options)); + } + { + var myInt = JsonSerializer.Deserialize(@"""60""", options); + Assert.Equal(MetricResolution.Standard, myInt); + Assert.Equal("60", JsonSerializer.Serialize(myInt, options)); + } + { + var myInt = JsonSerializer.Deserialize(@"""0""", options); + Assert.Equal(MetricResolution.Default, myInt); + Assert.Equal("0", JsonSerializer.Serialize(myInt, options)); + } + } + + [Fact] + public void Metrics_Resolution_JsonConverter_Exception() + { + // Arrange + var options = new JsonSerializerOptions(); + options.Converters.Add(new MetricResolutionJsonConverter()); + + { + Action act = () => JsonSerializer.Deserialize("1.3", options); + Assert.Throws(act); + } + + { + Action act = () => JsonSerializer.Deserialize(@"""1.3""", options); + Assert.Throws(act); + } + + { + Action act = () => JsonSerializer.Deserialize(@"""abc""", options); + Assert.Throws(act); + } + } +} \ No newline at end of file diff --git a/version.json b/version.json index ec7a6148..9fb16922 100644 --- a/version.json +++ b/version.json @@ -1,7 +1,7 @@ { "Core": { "Logging": "1.5.1", - "Metrics": "1.7.0", + "Metrics": "1.7.1", "Tracing": "1.4.2" }, "Utilities": {