Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Metrics PushSingleMetric default dimensions #780

Merged
65 changes: 65 additions & 0 deletions docs/core/metrics-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<APIGatewayProxyResponse> 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;

Expand All @@ -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<APIGatewayProxyResponse> FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)
{
Metrics.SetDefaultDimensions(new Dictionary<string, string>
{
{ "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<string, string>
{
{ "Environment", "Prod1" },
{ "Another", "One" }
}).Build();
}

public void HandlerSingleMetricDimensions()
{
_metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions);
}
...
```

## AspNetCore

Expand Down
15 changes: 15 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ public static IMetrics Instance
get => _instance ?? new Metrics(PowertoolsConfigurations.Instance, consoleWrapper: new ConsoleWrapper());
private set => _instance = value;
}

/// <summary>
/// Gets DefaultDimensions
/// </summary>
public static Dictionary<string, string> DefaultDimensions => Instance.Options.DefaultDimensions;

/// <summary>
/// Gets Namespace
/// </summary>
public static string Namespace => Instance.Options.Namespace;

/// <summary>
/// Gets Service
/// </summary>
public static string Service => Instance.Options.Service;

/// <inheritdoc />
public MetricsOptions Options => _options ??
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>
{
{ "Default", "SingleMetric" }
});
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions );
}

[Metrics]
public void PushSingleMetricWithEnvNamespace()
{
Expand Down Expand Up @@ -218,4 +234,10 @@ public void HandlerRaiseOnEmptyMetrics()
{

}

[Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true)]
public void HandleOnlyDimensionsInColdStart(ILambdaContext context)
{
Metrics.AddMetric("MyMetric", 1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ public void When_RaiseOnEmptyMetrics_And_NoMetrics_Should_ThrowException()
var exception = Assert.Throws<SchemaValidationException>(() => _handler.HandlerRaiseOnEmptyMetrics());
Assert.Equal("No metrics have been provided.", exception.Message);
}

[Fact]
public void Handler_With_Builder_Should_Raise_Empty_Metrics()
{
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,82 @@ public void When_ColdStart_And_DefaultDimensions_Is_Null_Should_Only_Add_Service
Arg.Is<string>(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<IMetrics>();
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<IMetrics>();
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<IMetrics>();
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<IMetrics>();
var optionsMock = new MetricsOptions();

metricsMock.Options.Returns(optionsMock);
Metrics.UseMetricsForTests(metricsMock);

// Act
var result = Metrics.Service;

// Assert
Assert.Null(result);
}
}
Loading