diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs
index d49c9b99..bf6ebac1 100644
--- a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs
@@ -119,4 +119,10 @@ void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpa
///
///
void CaptureColdStartMetric(ILambdaContext context);
+
+ ///
+ /// Adds multiple dimensions at once.
+ ///
+ /// Array of key-value tuples representing dimensions.
+ void AddDimensions(params (string key, string value)[] dimensions);
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
index d66f0fa0..b78630d3 100644
--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
@@ -317,7 +317,7 @@ void IMetrics.ClearDefaultDimensions()
}
///
- public void SetService(string service)
+ void IMetrics.SetService(string service)
{
// this needs to check if service is set through code or env variables
// the default value service_undefined has to be ignored and return null so it is not added as default
@@ -433,6 +433,15 @@ public static void SetNamespace(string nameSpace)
{
Instance.SetNamespace(nameSpace);
}
+
+ ///
+ /// Sets the service name for the metrics.
+ ///
+ /// The service name.
+ public static void SetService(string service)
+ {
+ Instance.SetService(service);
+ }
///
/// Retrieves namespace identifier.
@@ -576,6 +585,55 @@ void IMetrics.CaptureColdStartMetric(ILambdaContext context)
dimensions
);
}
+
+ ///
+ void IMetrics.AddDimensions(params (string key, string value)[] dimensions)
+ {
+ if (dimensions == null || dimensions.Length == 0)
+ return;
+
+ // Validate all dimensions first
+ foreach (var (key, value) in dimensions)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ throw new ArgumentNullException(nameof(dimensions),
+ "'AddDimensions' method requires valid dimension keys. 'Null' or empty values are not allowed.");
+
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentNullException(nameof(dimensions),
+ "'AddDimensions' method requires valid dimension values. 'Null' or empty values are not allowed.");
+ }
+
+ // Create a new dimension set with all dimensions
+ var dimensionSet = new DimensionSet(dimensions[0].key, dimensions[0].value);
+
+ // Add remaining dimensions to the same set
+ for (var i = 1; i < dimensions.Length; i++)
+ {
+ dimensionSet.Dimensions.Add(dimensions[i].key, dimensions[i].value);
+ }
+
+ // Add the dimensionSet to a list and pass it to AddDimensions
+ _context.AddDimensions([dimensionSet]);
+ }
+
+ ///
+ /// Adds multiple dimensions at once.
+ ///
+ /// Array of key-value tuples representing dimensions.
+ public static void AddDimensions(params (string key, string value)[] dimensions)
+ {
+ Instance.AddDimensions(dimensions);
+ }
+
+ ///
+ /// Flushes the metrics.
+ ///
+ /// If set to true, indicates a metrics overflow.
+ public static void Flush(bool metricsOverflow = false)
+ {
+ Instance.Flush(metricsOverflow);
+ }
///
/// Helper method for testing purposes. Clears static instance between test execution
diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs
index 2119dd93..c66596a4 100644
--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs
@@ -129,10 +129,19 @@ internal string GetService()
/// Adds new Dimension
///
/// Dimension to add
- internal void AddDimensionSet(DimensionSet dimension)
+ internal void AddDimension(DimensionSet dimension)
{
_metricDirective.AddDimension(dimension);
}
+
+ ///
+ /// Adds new List of Dimensions
+ ///
+ /// Dimensions to add
+ internal void AddDimensionSet(List dimension)
+ {
+ _metricDirective.AddDimensionSet(dimension);
+ }
///
/// Sets default dimensions list
diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs
index 9047dca0..0d300d5e 100644
--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs
@@ -109,22 +109,35 @@ public List> AllDimensionKeys
{
get
{
- var defaultKeys = DefaultDimensions
- .Where(d => d.DimensionKeys.Any())
- .SelectMany(s => s.DimensionKeys)
- .ToList();
+ var result = new List>();
+ var allDimKeys = new List();
- var keys = Dimensions
- .Where(d => d.DimensionKeys.Any())
- .SelectMany(s => s.DimensionKeys)
- .ToList();
+ // Add default dimensions keys
+ if (DefaultDimensions.Any())
+ {
+ foreach (var dimensionSet in DefaultDimensions)
+ {
+ foreach (var key in dimensionSet.DimensionKeys.Where(key => !allDimKeys.Contains(key)))
+ {
+ allDimKeys.Add(key);
+ }
+ }
+ }
- defaultKeys.AddRange(keys);
+ // Add all regular dimensions to the same array
+ foreach (var dimensionSet in Dimensions)
+ {
+ foreach (var key in dimensionSet.DimensionKeys.Where(key => !allDimKeys.Contains(key)))
+ {
+ allDimKeys.Add(key);
+ }
+ }
- if (defaultKeys.Count == 0) defaultKeys = new List();
+ // Add non-empty dimension arrays
+ // When no dimensions exist, add an empty array
+ result.Add(allDimKeys.Any() ? allDimKeys : []);
- // Wrap the list of strings in another list
- return new List> { defaultKeys };
+ return result;
}
}
@@ -192,19 +205,37 @@ internal void SetService(string service)
/// Dimensions - Cannot add more than 9 dimensions at the same time.
internal void AddDimension(DimensionSet dimension)
{
- if (Dimensions.Count < PowertoolsConfigurations.MaxDimensions)
+ // Check if we already have any dimensions
+ if (Dimensions.Count > 0)
{
- var matchingKeys = AllDimensionKeys.Where(x => x.Contains(dimension.DimensionKeys[0]));
- if (!matchingKeys.Any())
- Dimensions.Add(dimension);
- else
- Console.WriteLine(
- $"##WARNING##: Failed to Add dimension '{dimension.DimensionKeys[0]}'. Dimension already exists.");
+ // Get the first dimension set where we now store all dimensions
+ var firstDimensionSet = Dimensions[0];
+
+ // Check the actual dimension count inside the first dimension set
+ if (firstDimensionSet.Dimensions.Count >= PowertoolsConfigurations.MaxDimensions)
+ {
+ throw new ArgumentOutOfRangeException(nameof(dimension),
+ $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time.");
+ }
+
+ // Add to the first dimension set instead of creating a new one
+ foreach (var pair in dimension.Dimensions)
+ {
+ if (!firstDimensionSet.Dimensions.ContainsKey(pair.Key))
+ {
+ firstDimensionSet.Dimensions.Add(pair.Key, pair.Value);
+ }
+ else
+ {
+ Console.WriteLine(
+ $"##WARNING##: Failed to Add dimension '{pair.Key}'. Dimension already exists.");
+ }
+ }
}
else
{
- throw new ArgumentOutOfRangeException(nameof(Dimensions),
- $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time.");
+ // No dimensions yet, add the new one
+ Dimensions.Add(dimension);
}
}
@@ -228,18 +259,44 @@ internal void SetDefaultDimensions(List defaultDimensions)
/// Dictionary with dimension and default dimension list appended
internal Dictionary ExpandAllDimensionSets()
{
+ // if a key appears multiple times, the last value will be the one that's used in the output.
var dimensions = new Dictionary();
foreach (var dimensionSet in DefaultDimensions)
foreach (var (key, value) in dimensionSet.Dimensions)
- dimensions.TryAdd(key, value);
+ dimensions[key] = value;
foreach (var dimensionSet in Dimensions)
foreach (var (key, value) in dimensionSet.Dimensions)
- dimensions.TryAdd(key, value);
+ dimensions[key] = value;
return dimensions;
}
+
+ ///
+ /// Adds multiple dimensions as a complete dimension set to memory.
+ ///
+ /// List of dimension sets to add
+ internal void AddDimensionSet(List dimensionSets)
+ {
+ if (dimensionSets == null || !dimensionSets.Any())
+ return;
+
+ if (Dimensions.Count + dimensionSets.Count <= PowertoolsConfigurations.MaxDimensions)
+ {
+ // Simply add the dimension sets without checking for existing keys
+ // This ensures dimensions added together stay together
+ foreach (var dimensionSet in dimensionSets.Where(dimensionSet => dimensionSet.DimensionKeys.Any()))
+ {
+ Dimensions.Add(dimensionSet);
+ }
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(Dimensions),
+ $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time.");
+ }
+ }
///
/// Clears both default dimensions and dimensions lists
diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs
index 759cdb9e..d43d059b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs
@@ -132,7 +132,7 @@ internal string GetService()
/// Dimension value
public void AddDimension(string key, string value)
{
- _rootNode.AWS.AddDimensionSet(new DimensionSet(key, value));
+ _rootNode.AWS.AddDimension(new DimensionSet(key, value));
}
///
@@ -141,10 +141,8 @@ public void AddDimension(string key, string value)
/// List of dimensions
public void AddDimensions(List dimensions)
{
- foreach (var dimension in dimensions)
- {
- _rootNode.AWS.AddDimensionSet(dimension);
- }
+ // Call the AddDimensionSet method on the MetricDirective to add as a set
+ _rootNode.AWS.AddDimensionSet(dimensions);
}
///
diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs
index 7d0a3e4a..41ef01f0 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs
@@ -110,7 +110,7 @@ public void WhenMaxDataPointsAreAddedToTheSameMetric_FlushAutomatically()
[Trait("Category", "EMFLimits")]
[Fact]
- public void WhenMoreThan9DimensionsAdded_ThrowArgumentOutOfRangeException()
+ public void WhenMoreThan29DimensionsAdded_ThrowArgumentOutOfRangeException()
{
// Act
var act = () => { _handler.MaxDimensions(29); };
@@ -400,6 +400,96 @@ public async Task WhenMetricsAsyncRaceConditionItemSameKeyExists_ValidateLock()
"{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]",
metricsOutput);
}
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDimensions_WithMultipleValues_AddsDimensionsToSameDimensionSet()
+ {
+ // Act
+ _handler.AddMultipleDimensionsInSameSet();
+
+ var result = _consoleOut.ToString();
+
+ // Assert
+ Assert.Contains("\"Dimensions\":[[\"Service\",\"Environment\",\"Region\"]]", result);
+ Assert.Contains("\"Service\":\"testService\",\"Environment\":\"test\",\"Region\":\"us-west-2\"", result);
+ }
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDimensions_WithEmptyArray_DoesNotAddAnyDimensions()
+ {
+ // Act
+ _handler.AddEmptyDimensions();
+
+ var result = _consoleOut.ToString();
+
+ // Assert
+ Assert.Contains("\"Dimensions\":[[\"Service\"]]", result);
+ Assert.DoesNotContain("\"Environment\":", result);
+ }
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDimensions_WithNullOrEmptyKey_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.Throws(() => _handler.AddDimensionsWithInvalidKey());
+ }
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDimensions_WithNullOrEmptyValue_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.Throws(() => _handler.AddDimensionsWithInvalidValue());
+ }
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDimensions_OverwritesExistingDimensions_LastValueWins()
+ {
+ // Act
+ _handler.AddDimensionsWithOverwrite();
+
+ var result = _consoleOut.ToString();
+
+ // Assert
+ Assert.Contains("\"Service\":\"testService\",\"dimension1\":\"B\",\"dimension2\":\"2\"", result);
+ Assert.DoesNotContain("\"dimension1\":\"A\"", result);
+ }
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDimensions_IncludesDefaultDimensions()
+ {
+ // Act
+ _handler.AddDimensionsWithDefaultDimensions();
+
+ var result = _consoleOut.ToString();
+
+ // Assert
+ Assert.Contains("\"Dimensions\":[[\"Service\",\"environment\",\"dimension1\",\"dimension2\"]]", result);
+ Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"dimension1\":\"1\",\"dimension2\":\"2\"", result);
+ }
+
+ [Trait("Category", "MetricsImplementation")]
+ [Fact]
+ public void AddDefaultDimensionsAtRuntime_OnlyAppliedToNewDimensionSets()
+ {
+ // Act
+ _handler.AddDefaultDimensionsAtRuntime();
+
+ var result = _consoleOut.ToString();
+
+ // First metric output should have original default dimensions
+ Assert.Contains("\"Metrics\":[{\"Name\":\"FirstMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"environment\",\"dimension1\",\"dimension2\"]]", result);
+ Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"dimension1\":\"1\",\"dimension2\":\"2\",\"FirstMetric\":1", result);
+
+ // Second metric output should have additional default dimensions
+ Assert.Contains("\"Metrics\":[{\"Name\":\"SecondMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"environment\",\"tenantId\",\"foo\",\"bar\"]]", result);
+ Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"tenantId\":\"1\",\"foo\":\"1\",\"bar\":\"2\",\"SecondMetric\":1", result);
+ }
#region Helpers
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 5743c09d..992f0697 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs
@@ -264,4 +264,114 @@ public void HandleFunctionNameNoContext()
{
}
+
+ [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)]
+ public void AddMultipleDimensionsInSameSet()
+ {
+ // Add multiple dimensions at once
+ Metrics.AddDimensions(
+ ("Environment", "test"),
+ ("Region", "us-west-2")
+ );
+
+ Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count);
+ }
+
+ [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)]
+ public void AddEmptyDimensions()
+ {
+ // Add empty dimensions array
+ Metrics.AddDimensions();
+
+ Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count);
+ }
+
+ [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)]
+ public void AddDimensionsWithInvalidKey()
+ {
+ // Add dimension with null key
+ Metrics.AddDimensions(("", "value"));
+ }
+
+ [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)]
+ public void AddDimensionsWithInvalidValue()
+ {
+ // Add dimension with null value
+ Metrics.AddDimensions(("key", ""));
+ }
+
+ public void AddDimensionsWithOverwrite()
+ {
+ Metrics.SetNamespace("dotnet-powertools-test");
+ Metrics.SetService("testService");
+
+ // Add single dimension
+ Metrics.AddDimension("dimension1", "A");
+
+ // Then add multiple dimensions, including the same key
+ Metrics.AddDimensions(
+ ("dimension1", "B"),
+ ("dimension2", "2")
+ );
+
+ Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count);
+ Metrics.Flush();
+ }
+
+ public void AddDimensionsWithDefaultDimensions()
+ {
+ Metrics.SetNamespace("dotnet-powertools-test");
+ Metrics.SetService("testService");
+
+ // Set default dimensions
+ Metrics.SetDefaultDimensions(new Dictionary
+ {
+ { "environment", "prod" }
+ });
+
+ // Add multiple dimensions
+ Metrics.AddDimensions(
+ ("dimension1", "1"),
+ ("dimension2", "2")
+ );
+
+ Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count);
+ Metrics.Flush();
+ }
+
+ public void AddDefaultDimensionsAtRuntime()
+ {
+ Metrics.SetNamespace("dotnet-powertools-test");
+ Metrics.SetService("testService");
+
+ // Set initial default dimensions
+ Metrics.SetDefaultDimensions(new Dictionary
+ {
+ { "environment", "prod" }
+ });
+
+ // Add first set of dimensions
+ Metrics.AddDimensions(
+ ("dimension1", "1"),
+ ("dimension2", "2")
+ );
+ Metrics.AddMetric("FirstMetric", 1.0, MetricUnit.Count);
+ Metrics.Flush();
+
+ // Add more default dimensions
+ Metrics.SetDefaultDimensions(new Dictionary
+ {
+ { "environment", "prod" },
+ { "tenantId", "1" }
+ });
+
+ // Add second set of dimensions
+ Metrics.AddDimensions(
+ ("foo", "1"),
+ ("bar", "2")
+ );
+ Metrics.AddMetric("SecondMetric", 1.0, MetricUnit.Count);
+
+ Metrics.Flush();
+ }
}
\ No newline at end of file