From 97dc991c90db3a0682fbfd48dea668fe08651e12 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:54:50 +0000 Subject: [PATCH 1/5] feat(metrics): add function name support for metrics dimensions --- libraries/AWS.Lambda.Powertools.sln | 7 +- .../AWS.Lambda.Powertools.Metrics/IMetrics.cs | 6 ++ .../Internal/MetricsAspect.cs | 19 ++-- .../AWS.Lambda.Powertools.Metrics/Metrics.cs | 40 ++++---- .../MetricsAttribute.cs | 7 ++ .../MetricsBuilder.cs | 12 +++ .../MetricsOptions.cs | 5 + .../Handlers/FunctionHandler.cs | 12 +++ .../Handlers/FunctionHandlerTests.cs | 91 ++++++++++++++++++- .../Handlers/MetricsnBuilderHandler.cs | 1 + 10 files changed, 172 insertions(+), 28 deletions(-) diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index bcc1a2c9..c0dc580f 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -101,6 +101,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metri EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metrics.AspNetCore.Tests", "tests\AWS.Lambda.Powertools.Metrics.AspNetCore.Tests\AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj", "{F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Metrics", "Metrics", "{A566F2D7-F8FE-466A-8306-85F266B7E656}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -554,7 +556,6 @@ Global {3BA6251D-DE4E-4547-AAA9-25F4BA04C636} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} {1A3AC28C-3AEE-40FE-B229-9E38BB609547} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} {B68A0D0A-4785-48CB-864F-29E3A8ACA526} = {1CFF5568-8486-475F-81F6-06105C437528} - {A422C742-2CF9-409D-BDAE-15825AB62113} = {1CFF5568-8486-475F-81F6-06105C437528} {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E} = {1CFF5568-8486-475F-81F6-06105C437528} {A040AED5-BBB8-4BFA-B2A5-BBD82817B8A5} = {1CFF5568-8486-475F-81F6-06105C437528} {1ECB31E8-2EF0-41E2-8C71-CB9876D207F0} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} @@ -592,6 +593,8 @@ Global {E71C48D2-AD56-4177-BBD7-6BB859A40C92} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} {CC8CFF43-DC72-464C-A42D-55E023DE8500} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} {A2AD98B1-2BED-4864-B573-77BE7B52FED2} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB} = {1CFF5568-8486-475F-81F6-06105C437528} + {A566F2D7-F8FE-466A-8306-85F266B7E656} = {1CFF5568-8486-475F-81F6-06105C437528} + {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB} = {A566F2D7-F8FE-466A-8306-85F266B7E656} + {A422C742-2CF9-409D-BDAE-15825AB62113} = {A566F2D7-F8FE-466A-8306-85F266B7E656} EndGlobalSection EndGlobal diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs index 69ef2ee3..f80e3f9b 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs @@ -106,4 +106,10 @@ void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpa /// /// The metrics options. public MetricsOptions Options { get; } + + /// + /// Sets the function name. + /// + /// + void SetFunctionName(string functionName); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs index 1577f5fc..dae4c321 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs @@ -76,6 +76,7 @@ public void Before( options.Service = trigger.Service; options.RaiseOnEmptyMetrics = trigger.IsRaiseOnEmptyMetricsSet ? trigger.RaiseOnEmptyMetrics : null; options.CaptureColdStart = trigger.IsCaptureColdStartSet ? trigger.CaptureColdStart : null; + options.FunctionName = trigger.FunctionName; }); var eventArgs = new AspectEventArgs @@ -89,16 +90,22 @@ public void Before( Triggers = triggers }; - if (_metricsInstance.Options.CaptureColdStart != null && _metricsInstance.Options.CaptureColdStart.Value && _isColdStart) + if (_metricsInstance.Options.CaptureColdStart != null && _metricsInstance.Options.CaptureColdStart.Value && + _isColdStart) { - var defaultDimensions = _metricsInstance.Options?.DefaultDimensions; _isColdStart = false; - var context = GetContext(eventArgs); - - if (context is not null) + var functionName = _metricsInstance.Options?.FunctionName; + var defaultDimensions = _metricsInstance.Options?.DefaultDimensions; + + if (string.IsNullOrWhiteSpace(functionName)) + { + functionName = GetContext(eventArgs)?.FunctionName ?? ""; + } + + if (!string.IsNullOrWhiteSpace(functionName)) { - defaultDimensions?.Add("FunctionName", context.FunctionName); + defaultDimensions?.Add("FunctionName", functionName); } _metricsInstance.PushSingleMetric( diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index 8da23030..5d403ad4 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -17,7 +17,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using AWS.Lambda.Powertools.Common; @@ -62,7 +61,8 @@ internal static IMetrics Instance Namespace = GetNamespace(), Service = GetService(), RaiseOnEmptyMetrics = _raiseOnEmptyMetrics, - DefaultDimensions = GetDefaultDimensions() + DefaultDimensions = GetDefaultDimensions(), + FunctionName = _functionName }; /// @@ -94,7 +94,11 @@ internal static IMetrics Instance // Shared synchronization object // private readonly object _lockObj = new(); - + + /// + /// Function name is used for metric dimension across all metrics. + /// + private string _functionName; /// /// Initializes a new instance of the class. @@ -120,9 +124,21 @@ public static IMetrics Configure(Action configure) if (options.DefaultDimensions != null) SetDefaultDimensions(options.DefaultDimensions); + if (!string.IsNullOrEmpty(options.FunctionName)) + Instance.SetFunctionName(options.FunctionName); + return Instance; } + /// + /// Sets the function name. + /// + /// + void IMetrics.SetFunctionName(string functionName) + { + _functionName = functionName; + } + /// /// Creates a Metrics object that provides features to send metrics to Amazon Cloudwatch using the Embedded metric /// format (EMF). See @@ -140,7 +156,7 @@ internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string name _context = new MetricsContext(); _raiseOnEmptyMetrics = raiseOnEmptyMetrics; _captureColdStartEnabled = captureColdStartEnabled; - + Instance = this; _powertoolsConfigurations.SetExecutionEnvironment(this); @@ -329,7 +345,9 @@ void IMetrics.PushSingleMetric(string name, double value, MetricUnit unit, strin context.AddMetric(name, value, unit, resolution); - Flush(context); + var emfPayload = context.Serialize(); + + Console.WriteLine(emfPayload); } @@ -438,18 +456,6 @@ public static void ClearDefaultDimensions() } } - /// - /// Flushes metrics in Embedded Metric Format (EMF) to Standard Output. In Lambda, this output is collected - /// automatically and sent to Cloudwatch. - /// - /// If context is provided it is serialized instead of the global context object - private void Flush(MetricsContext context) - { - var emfPayload = context.Serialize(); - - Console.WriteLine(emfPayload); - } - /// /// Pushes single metric to CloudWatch using Embedded Metric Format. This can be used to push metrics with a different /// context. diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs index 6413b597..3e47c1e0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs @@ -112,6 +112,13 @@ public class MetricsAttribute : Attribute /// The namespace. public string Namespace { get; set; } + /// + /// Function name is used for metric dimension across all metrics. + /// This can be also set using the environment variable LAMBDA_FUNCTION_NAME. + /// If not set, the function name will be automatically set to the Lambda function name. + /// + public string FunctionName { get; set; } + /// /// Service name is used for metric dimension across all metrics. /// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME. diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs index ad5b516a..da16f21e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs @@ -79,6 +79,17 @@ public MetricsBuilder WithDefaultDimensions(Dictionary defaultDi return this; } + /// + /// Sets the function name for the metrics dimension. + /// + /// + /// + public MetricsBuilder WithFunctionName(string functionName) + { + _options.FunctionName = functionName; + return this; + } + /// /// Builds and configures the metrics instance. /// @@ -92,6 +103,7 @@ public IMetrics Build() opt.RaiseOnEmptyMetrics = _options.RaiseOnEmptyMetrics; opt.CaptureColdStart = _options.CaptureColdStart; opt.DefaultDimensions = _options.DefaultDimensions; + opt.FunctionName = _options.FunctionName; }); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs index 67ae87bc..71adc557 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs @@ -31,4 +31,9 @@ public class MetricsOptions /// Gets or sets the default dimensions to be added to all metrics. /// public Dictionary DefaultDimensions { get; set; } + + /// + /// Gets or sets the function name to be used as a metric dimension. + /// + public string FunctionName { get; set; } } \ 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 4abb6f45..1244f39b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs @@ -236,4 +236,16 @@ public void HandleOnlyDimensionsInColdStart(ILambdaContext context) { Metrics.AddMetric("MyMetric", 1); } + + [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true, FunctionName = "MyFunction")] + public void HandleFunctionNameWithContext(ILambdaContext context) + { + + } + + [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true, FunctionName = "MyFunction")] + public void HandleFunctionNameNoContext() + { + + } } \ 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 44d4e841..4f4c6bd8 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs @@ -198,7 +198,6 @@ public void Handler_WithMockedMetrics_ShouldCallAddMetric() Metrics.UseMetricsForTests(metricsMock); - var sut = new MetricsDependencyInjectionOptionsHandler(metricsMock); // Act @@ -206,7 +205,11 @@ public void Handler_WithMockedMetrics_ShouldCallAddMetric() // Assert metricsMock.Received(1).PushSingleMetric("ColdStart", 1, MetricUnit.Count, "dotnet-powertools-test", - service: "testService", Arg.Any>()); + service: "testService", + Arg.Is>(x => + x.ContainsKey("Environment") && x["Environment"] == "Prod" + && x.ContainsKey("Another") && x["Another"] == "One")); + metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); } @@ -263,7 +266,50 @@ public void Handler_With_Builder_Should_Configure_In_Constructor_Mock() }); metricsMock.Received(1).PushSingleMetric("ColdStart", 1, MetricUnit.Count, "dotnet-powertools-test", - service: "testService", Arg.Any>()); + service: "testService", + Arg.Is>(x => + x.ContainsKey("FunctionName") && x["FunctionName"] == "My_Function_Name" + && x.ContainsKey("Environment") && x["Environment"] == "Prod" + && x.ContainsKey("Another") && x["Another"] == "One")); + + metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); + } + + [Fact] + public void Handler_With_Builder_Should_Configure_FunctionName_In_Constructor_Mock() + { + var metricsMock = Substitute.For(); + + metricsMock.Options.Returns(new MetricsOptions + { + CaptureColdStart = true, + Namespace = "dotnet-powertools-test", + Service = "testService", + FunctionName = "My_Function_Custome_Name", + DefaultDimensions = new Dictionary + { + { "Environment", "Prod" }, + { "Another", "One" } + } + }); + + Metrics.UseMetricsForTests(metricsMock); + + var sut = new MetricsnBuilderHandler(metricsMock); + + // Act + sut.Handler(new TestLambdaContext + { + FunctionName = "This_Will_Be_Overwritten" + }); + + metricsMock.Received(1).PushSingleMetric("ColdStart", 1, MetricUnit.Count, "dotnet-powertools-test", + service: "testService", + Arg.Is>(x => + x.ContainsKey("FunctionName") && x["FunctionName"] == "My_Function_Custome_Name" + && x.ContainsKey("Environment") && x["Environment"] == "Prod" + && x.ContainsKey("Another") && x["Another"] == "One")); + metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); } @@ -326,6 +372,45 @@ public void Dimension_Only_Set_In_Cold_Start() "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", metricsOutput); } + + [Fact] + public void When_Function_Name_Is_Set() + { + // Arrange + var handler = new FunctionHandler(); + + // Act + handler.HandleFunctionNameWithContext(new TestLambdaContext + { + FunctionName = "This_Will_Be_Overwritten" + }); + + // Get the output and parse it + var metricsOutput = _consoleOut.ToString(); + + // Assert cold start function name is set MyFunction + Assert.Contains( + "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"MyFunction\",\"ColdStart\":1}", + metricsOutput); + } + + [Fact] + public void When_Function_Name_Is_Set_No_Context() + { + // Arrange + var handler = new FunctionHandler(); + + // Act + handler.HandleFunctionNameNoContext(); + + // Get the output and parse it + var metricsOutput = _consoleOut.ToString(); + + // Assert cold start function name is set MyFunction + Assert.Contains( + "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"MyFunction\",\"ColdStart\":1}", + metricsOutput); + } public void Dispose() { diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs index 82c16b9c..4c71a709 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs @@ -36,4 +36,5 @@ public void HandlerSingleMetricDimensions() { _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, defaultDimensions: _metrics.Options.DefaultDimensions); } + } \ No newline at end of file From 75ac8efc18cf8cd3cd964349166cc275083e1e27 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:38:15 +0000 Subject: [PATCH 2/5] feat(metrics): enhance documentation for Cold Start Function Name dimension and update test classes --- docs/core/metrics-v2.md | 50 ++++++++++++++++++- .../InternalsVisibleTo.cs | 3 +- .../MetricsFilterTests.cs | 9 +++- .../MetricsHelperTests.cs | 9 +++- .../MetricsMiddlewareExtensionsTests.cs | 3 ++ 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/docs/core/metrics-v2.md b/docs/core/metrics-v2.md index 5502edde..9bd4f481 100644 --- a/docs/core/metrics-v2.md +++ b/docs/core/metrics-v2.md @@ -657,7 +657,7 @@ By default it will skip all previously defined dimensions including default dime Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, defaultDimensions: Metrics.DefaultDimensions ); ... ``` -=== "Default Dimensions Options / Builder patterns .cs" +=== "Default Dimensions Options / Builder patterns" ```csharp hl_lines="9-13 18" using AWS.Lambda.Powertools.Metrics; @@ -682,6 +682,54 @@ By default it will skip all previously defined dimensions including default dime ... ``` +### Cold start Function Name dimension + +In cases where you want to customize the `FunctionName` dimension in Cold Start metrics. + +This is useful where you want to maintain the same name in case of auto generated handler names (cdk, top-level statement functions, etc.) + +Example: + +=== "In decorator" + + ```csharp hl_lines="5" + using AWS.Lambda.Powertools.Metrics; + + public class Function { + + [Metrics(FunctionName = "MyFunctionName", Namespace = "ExampleApplication", Service = "Booking")] + public async Task FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + Metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); + ... + } + ``` +=== "Configure / Builder patterns" + + ```csharp hl_lines="12" + using AWS.Lambda.Powertools.Metrics; + + public class Function { + + public Function() + { + Metrics.Configure(options => + { + options.Namespace = "dotnet-powertools-test"; + options.Service = "testService"; + options.CaptureColdStart = true; + options.FunctionName = "MyFunctionName"; + }); + } + + [Metrics] + public async Task FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + Metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); + ... + } + ``` + ## AspNetCore ### Installation diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs index 0e44da1c..c13381c0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs @@ -15,4 +15,5 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.Tests")] +[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore.Tests")] \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs index 6a0df634..efa36561 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs @@ -6,8 +6,15 @@ namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; -public class MetricsFilterTests +[Collection("Sequential")] +public class MetricsFilterTests : IDisposable { + public void Dispose() + { + MetricsHelper.ResetColdStart(); + MetricsAspect.ResetForTest(); + } + private readonly IMetrics _metrics; private readonly EndpointFilterInvocationContext _context; private readonly ILambdaContext _lambdaContext; diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsHelperTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsHelperTests.cs index 37064d24..dfd27e58 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsHelperTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsHelperTests.cs @@ -8,8 +8,15 @@ namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; -public class MetricsHelperTests +[Collection("Sequential")] +public class MetricsHelperTests : IDisposable { + public void Dispose() + { + MetricsHelper.ResetColdStart(); + MetricsAspect.ResetForTest(); + } + [Fact] public async Task CaptureColdStartMetrics_WhenEnabled_ShouldPushMetric() { diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs index 3d5a9892..d972a02f 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs @@ -7,16 +7,19 @@ namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; +[Collection("Sequential")] public class MetricsMiddlewareExtensionsTests : IDisposable { public MetricsMiddlewareExtensionsTests() { MetricsHelper.ResetColdStart(); + MetricsAspect.ResetForTest(); } public void Dispose() { MetricsHelper.ResetColdStart(); + MetricsAspect.ResetForTest(); } [Fact] From 7491f8cb6c9aba095d9846e58cd6386e12e001c4 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:13:32 +0000 Subject: [PATCH 3/5] fix logic that was removed on merge --- libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index 8eb4c1ce..ea0e0ca3 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -531,11 +531,12 @@ void IMetrics.CaptureColdStartMetric(ILambdaContext context) // bring default dimensions if exist var dimensions = Options?.DefaultDimensions; - - if (context is not null) + + var functionName = Options?.FunctionName ?? context?.FunctionName ?? ""; + if (!string.IsNullOrWhiteSpace(functionName)) { dimensions ??= new Dictionary(); - dimensions.Add("FunctionName", context.FunctionName); + dimensions.Add("FunctionName", functionName); } PushSingleMetric( From 583d4d97eafabea11c9b4015037549b8661c9469 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:25:10 +0000 Subject: [PATCH 4/5] clean comments --- .../Handlers/FunctionHandlerTests.cs | 7 ------- 1 file changed, 7 deletions(-) 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 87c501dd..254de9e9 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs @@ -409,13 +409,6 @@ public void Handler_With_Builder_Should_Configure_FunctionName_In_Constructor_Mo FunctionName = "This_Will_Be_Overwritten" }); - //metricsMock.Received(1).PushSingleMetric("ColdStart", 1, MetricUnit.Count, "dotnet-powertools-test", - // service: "testService", - // Arg.Is>(x => - // x.ContainsKey("FunctionName") && x["FunctionName"] == "My_Function_Custome_Name" - // && x.ContainsKey("Environment") && x["Environment"] == "Prod" - // && x.ContainsKey("Another") && x["Another"] == "One")); - metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); } From 75ee06be7b95ea41ffb770a2a5e3bb93313a2afe Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:22:09 +0000 Subject: [PATCH 5/5] feat(metrics): enhance WithFunctionName method to handle null or empty values and add corresponding unit tests --- .../MetricsBuilder.cs | 2 +- .../MetricsTests.cs | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs index da16f21e..4f6d3c3a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs @@ -86,7 +86,7 @@ public MetricsBuilder WithDefaultDimensions(Dictionary defaultDi /// public MetricsBuilder WithFunctionName(string functionName) { - _options.FunctionName = functionName; + _options.FunctionName = !string.IsNullOrWhiteSpace(functionName) ? functionName : null; return this; } diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs index ecdd94d6..03db82e9 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs @@ -321,4 +321,55 @@ public void Service_Should_Return_Null_When_Not_Set() // Assert Assert.Null(result); } + + [Fact] + public void WithFunctionName_Should_Set_FunctionName_In_Options() + { + // Arrange + var builder = new MetricsBuilder(); + var expectedFunctionName = "TestFunction"; + + // Act + var result = builder.WithFunctionName(expectedFunctionName); + var metrics = result.Build(); + + // Assert + Assert.Equal(expectedFunctionName, metrics.Options.FunctionName); + Assert.Same(builder, result); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void WithFunctionName_Should_Allow_NullOrEmpty_FunctionName(string functionName) + { + // Arrange + var builder = new MetricsBuilder(); + + // Act + var result = builder.WithFunctionName(functionName); + var metrics = result.Build(); + + // Assert + // Assert + Assert.Null(metrics.Options.FunctionName); // All invalid values should result in null + Assert.Same(builder, result); + } + + [Fact] + public void Build_Should_Preserve_FunctionName_When_Set_Through_Builder() + { + // Arrange + var builder = new MetricsBuilder() + .WithNamespace("TestNamespace") + .WithService("TestService") + .WithFunctionName("TestFunction"); + + // Act + var metrics = builder.Build(); + + // Assert + Assert.Equal("TestFunction", metrics.Options.FunctionName); + } } \ No newline at end of file