diff --git a/docs/core/metrics-v2.md b/docs/core/metrics-v2.md index ec6c536e..4854bae5 100644 --- a/docs/core/metrics-v2.md +++ b/docs/core/metrics-v2.md @@ -602,6 +602,30 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use **`PushSing === "Function.cs" + ```csharp hl_lines="8-13" + using AWS.Lambda.Powertools.Metrics; + + public class Function { + + [Metrics(Namespace = ExampleApplication, Service = "Booking")] + public async Task FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + Metrics.PushSingleMetric( + metricName: "ColdStart", + value: 1, + unit: MetricUnit.Count, + nameSpace: "ExampleApplication", + service: "Booking"); + ... + ``` + +By default it will skip all previously defined dimensions including default dimensions. Use `dimensions` argument if you want to reuse default dimensions or specify custom dimensions from a dictionary. + +- `Metrics.DefaultDimensions`: Reuse default dimensions when using static Metrics +- `Options.DefaultDimensions`: Reuse default dimensions when using Builder or Configure patterns + +=== "New Default Dimensions.cs" + ```csharp hl_lines="8-17" using AWS.Lambda.Powertools.Metrics; @@ -622,6 +646,47 @@ CloudWatch EMF uses the same dimensions across all your metrics. Use **`PushSing }); ... ``` +=== "Default Dimensions static.cs" + + ```csharp hl_lines="8-12" + using AWS.Lambda.Powertools.Metrics; + + public class Function { + + [Metrics(Namespace = ExampleApplication, Service = "Booking")] + public async Task FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) + { + Metrics.SetDefaultDimensions(new Dictionary + { + { "Default", "SingleMetric" } + }); + Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions ); + ... + ``` +=== "Default Dimensions Options / Builder patterns .cs" + + ```csharp hl_lines="9-13 18" + using AWS.Lambda.Powertools.Metrics; + + public MetricsnBuilderHandler(IMetrics metrics = null) + { + _metrics = metrics ?? new MetricsBuilder() + .WithCaptureColdStart(true) + .WithService("testService") + .WithNamespace("dotnet-powertools-test") + .WithDefaultDimensions(new Dictionary + { + { "Environment", "Prod1" }, + { "Another", "One" } + }).Build(); + } + + public void HandlerSingleMetricDimensions() + { + _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions); + } + ... + ``` ## AspNetCore diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index 510fde9f..38e405ae 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -36,6 +36,21 @@ public static IMetrics Instance get => _instance ?? new Metrics(PowertoolsConfigurations.Instance, consoleWrapper: new ConsoleWrapper()); private set => _instance = value; } + + /// + /// Gets DefaultDimensions + /// + public static Dictionary DefaultDimensions => Instance.Options.DefaultDimensions; + + /// + /// Gets Namespace + /// + public static string Namespace => Instance.Options.Namespace; + + /// + /// Gets Service + /// + public static string Service => Instance.Options.Service; /// public MetricsOptions Options => _options ?? diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs index adce5337..04b86b3c 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs @@ -176,6 +176,32 @@ public void When_PushSingleMetric_With_Namespace() Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); } + [Trait("Category", "SchemaValidation")] + [Fact] + public void When_PushSingleMetric_With_No_DefaultDimensions() + { + // Act + _handler.PushSingleMetricNoDefaultDimensions(); + + var metricsOutput = _consoleOut.ToString(); + + // Assert + Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"SingleMetric\":1}", metricsOutput); + } + + [Trait("Category", "SchemaValidation")] + [Fact] + public void When_PushSingleMetric_With_DefaultDimensions() + { + // Act + _handler.PushSingleMetricDefaultDimensions(); + + var metricsOutput = _consoleOut.ToString(); + + // Assert + Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); + } + [Trait("Category", "SchemaValidation")] [Fact] public void When_PushSingleMetric_With_Env_Namespace() 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 910ca0a9..b9f90994 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs @@ -63,6 +63,22 @@ public void PushSingleMetricWithNamespace() }); } + [Metrics(Namespace = "ExampleApplication")] + public void PushSingleMetricNoDefaultDimensions() + { + Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count); + } + + [Metrics(Namespace = "ExampleApplication")] + public void PushSingleMetricDefaultDimensions() + { + Metrics.SetDefaultDimensions(new Dictionary + { + { "Default", "SingleMetric" } + }); + Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions ); + } + [Metrics] public void PushSingleMetricWithEnvNamespace() { @@ -218,4 +234,10 @@ public void HandlerRaiseOnEmptyMetrics() { } + + [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true)] + public void HandleOnlyDimensionsInColdStart(ILambdaContext context) + { + Metrics.AddMetric("MyMetric", 1); + } } \ 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 1f89a208..2879cac7 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs @@ -270,7 +270,7 @@ public void When_RaiseOnEmptyMetrics_And_NoMetrics_Should_ThrowException() var exception = Assert.Throws(() => _handler.HandlerRaiseOnEmptyMetrics()); Assert.Equal("No metrics have been provided.", exception.Message); } - + [Fact] public void Handler_With_Builder_Should_Raise_Empty_Metrics() { @@ -282,6 +282,66 @@ public void Handler_With_Builder_Should_Raise_Empty_Metrics() Assert.Equal("No metrics have been provided.", exception.Message); } + [Fact] + public void Handler_With_Builder_Push_Single_Metric_No_Dimensions() + { + // Arrange + var handler = new MetricsnBuilderHandler(); + + // Act + handler.HandlerSingleMetric(); + + // Get the output and parse it + var metricsOutput = _consoleOut.ToString(); + + Assert.Contains( + "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"SuccessfulBooking\":1}", + metricsOutput); + } + + [Fact] + public void Handler_With_Builder_Push_Single_Metric_Dimensions() + { + // Arrange + var handler = new MetricsnBuilderHandler(); + + // Act + handler.HandlerSingleMetricDimensions(); + + // Get the output and parse it + var metricsOutput = _consoleOut.ToString(); + + Assert.Contains( + "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"SuccessfulBooking\":1}", + metricsOutput); + } + + [Fact] + public void Dimension_Only_Set_In_Cold_Start() + { + // Arrange + var handler = new FunctionHandler(); + + // Act + handler.HandleOnlyDimensionsInColdStart(new TestLambdaContext + { + FunctionName = "My_Function_Name" + }); + + // Get the output and parse it + var metricsOutput = _consoleOut.ToString(); + + // Assert cold start + Assert.Contains( + "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", + metricsOutput); + + // Assert successful add metric without dimensions + Assert.Contains( + "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", + metricsOutput); + } + public void Dispose() { Metrics.ResetForTest(); 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 83cc0e89..eec4c65b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs @@ -32,4 +32,14 @@ public void Handler(ILambdaContext context) public void HandlerEmpty() { } + + public void HandlerSingleMetric() + { + _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count); + } + + public void HandlerSingleMetricDimensions() + { + _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions); + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs index 36039756..ecdd94d6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs @@ -243,4 +243,82 @@ public void When_ColdStart_And_DefaultDimensions_Is_Null_Should_Only_Add_Service Arg.Is(s => s.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) ); } + + [Fact] + public void Namespace_Should_Return_OptionsNamespace() + { + // Arrange + Metrics.ResetForTest(); + var metricsMock = Substitute.For(); + var optionsMock = new MetricsOptions + { + Namespace = "TestNamespace" + }; + + metricsMock.Options.Returns(optionsMock); + Metrics.UseMetricsForTests(metricsMock); + + // Act + var result = Metrics.Namespace; + + // Assert + Assert.Equal("TestNamespace", result); + } + + [Fact] + public void Service_Should_Return_OptionsService() + { + // Arrange + Metrics.ResetForTest(); + var metricsMock = Substitute.For(); + var optionsMock = new MetricsOptions + { + Service = "TestService" + }; + + metricsMock.Options.Returns(optionsMock); + Metrics.UseMetricsForTests(metricsMock); + + // Act + var result = Metrics.Service; + + // Assert + Assert.Equal("TestService", result); + } + + [Fact] + public void Namespace_Should_Return_Null_When_Not_Set() + { + // Arrange + Metrics.ResetForTest(); + var metricsMock = Substitute.For(); + var optionsMock = new MetricsOptions(); + + metricsMock.Options.Returns(optionsMock); + Metrics.UseMetricsForTests(metricsMock); + + // Act + var result = Metrics.Namespace; + + // Assert + Assert.Null(result); + } + + [Fact] + public void Service_Should_Return_Null_When_Not_Set() + { + // Arrange + Metrics.ResetForTest(); + var metricsMock = Substitute.For(); + var optionsMock = new MetricsOptions(); + + metricsMock.Options.Returns(optionsMock); + Metrics.UseMetricsForTests(metricsMock); + + // Act + var result = Metrics.Service; + + // Assert + Assert.Null(result); + } } \ No newline at end of file