Skip to content

Commit

Permalink
feat(metrics): add function name support for metrics dimensions
Browse files Browse the repository at this point in the history
  • Loading branch information
hjgraca committed Feb 25, 2025
1 parent 9e71bec commit 97dc991
Show file tree
Hide file tree
Showing 10 changed files with 172 additions and 28 deletions.
7 changes: 5 additions & 2 deletions libraries/AWS.Lambda.Powertools.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,10 @@ void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpa
/// </summary>
/// <value>The metrics options.</value>
public MetricsOptions Options { get; }

/// <summary>
/// Sets the function name.
/// </summary>
/// <param name="functionName"></param>
void SetFunctionName(string functionName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand Down
40 changes: 23 additions & 17 deletions libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -62,7 +61,8 @@ internal static IMetrics Instance
Namespace = GetNamespace(),
Service = GetService(),
RaiseOnEmptyMetrics = _raiseOnEmptyMetrics,
DefaultDimensions = GetDefaultDimensions()
DefaultDimensions = GetDefaultDimensions(),
FunctionName = _functionName
};

/// <summary>
Expand Down Expand Up @@ -94,7 +94,11 @@ internal static IMetrics Instance
// Shared synchronization object
// </summary>
private readonly object _lockObj = new();


/// <summary>
/// Function name is used for metric dimension across all metrics.
/// </summary>
private string _functionName;

/// <summary>
/// Initializes a new instance of the <see cref="Metrics" /> class.
Expand All @@ -120,9 +124,21 @@ public static IMetrics Configure(Action<MetricsOptions> configure)
if (options.DefaultDimensions != null)
SetDefaultDimensions(options.DefaultDimensions);

if (!string.IsNullOrEmpty(options.FunctionName))
Instance.SetFunctionName(options.FunctionName);

return Instance;
}

/// <summary>
/// Sets the function name.
/// </summary>
/// <param name="functionName"></param>
void IMetrics.SetFunctionName(string functionName)
{
_functionName = functionName;
}

/// <summary>
/// Creates a Metrics object that provides features to send metrics to Amazon Cloudwatch using the Embedded metric
/// format (EMF). See
Expand All @@ -140,7 +156,7 @@ internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string name
_context = new MetricsContext();
_raiseOnEmptyMetrics = raiseOnEmptyMetrics;
_captureColdStartEnabled = captureColdStartEnabled;

Instance = this;
_powertoolsConfigurations.SetExecutionEnvironment(this);

Expand Down Expand Up @@ -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);
}


Expand Down Expand Up @@ -438,18 +456,6 @@ public static void ClearDefaultDimensions()
}

Check warning on line 456 in libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs#L453-L456

Added lines #L453 - L456 were not covered by tests
}

/// <summary>
/// Flushes metrics in Embedded Metric Format (EMF) to Standard Output. In Lambda, this output is collected
/// automatically and sent to Cloudwatch.
/// </summary>
/// <param name="context">If context is provided it is serialized instead of the global context object</param>
private void Flush(MetricsContext context)
{
var emfPayload = context.Serialize();

Console.WriteLine(emfPayload);
}

/// <summary>
/// Pushes single metric to CloudWatch using Embedded Metric Format. This can be used to push metrics with a different
/// context.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ public class MetricsAttribute : Attribute
/// <value>The namespace.</value>
public string Namespace { get; set; }

/// <summary>
/// Function name is used for metric dimension across all metrics.
/// This can be also set using the environment variable <c>LAMBDA_FUNCTION_NAME</c>.
/// If not set, the function name will be automatically set to the Lambda function name.
/// </summary>
public string FunctionName { get; set; }

/// <summary>
/// Service name is used for metric dimension across all metrics.
/// This can be also set using the environment variable <c>POWERTOOLS_SERVICE_NAME</c>.
Expand Down
12 changes: 12 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ public MetricsBuilder WithDefaultDimensions(Dictionary<string, string> defaultDi
return this;
}

/// <summary>
/// Sets the function name for the metrics dimension.
/// </summary>
/// <param name="functionName"></param>
/// <returns></returns>
public MetricsBuilder WithFunctionName(string functionName)
{
_options.FunctionName = functionName;
return this;
}

Check warning on line 91 in libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs

View check run for this annotation

Codecov / codecov/patch

libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs#L88-L91

Added lines #L88 - L91 were not covered by tests

/// <summary>
/// Builds and configures the metrics instance.
/// </summary>
Expand All @@ -92,6 +103,7 @@ public IMetrics Build()
opt.RaiseOnEmptyMetrics = _options.RaiseOnEmptyMetrics;
opt.CaptureColdStart = _options.CaptureColdStart;
opt.DefaultDimensions = _options.DefaultDimensions;
opt.FunctionName = _options.FunctionName;
});
}
}
5 changes: 5 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ public class MetricsOptions
/// Gets or sets the default dimensions to be added to all metrics.
/// </summary>
public Dictionary<string, string> DefaultDimensions { get; set; }

/// <summary>
/// Gets or sets the function name to be used as a metric dimension.
/// </summary>
public string FunctionName { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,18 @@ public void Handler_WithMockedMetrics_ShouldCallAddMetric()

Metrics.UseMetricsForTests(metricsMock);


var sut = new MetricsDependencyInjectionOptionsHandler(metricsMock);

// Act
sut.Handler();

// Assert
metricsMock.Received(1).PushSingleMetric("ColdStart", 1, MetricUnit.Count, "dotnet-powertools-test",
service: "testService", Arg.Any<Dictionary<string, string>>());
service: "testService",
Arg.Is<Dictionary<string, string>>(x =>
x.ContainsKey("Environment") && x["Environment"] == "Prod"
&& x.ContainsKey("Another") && x["Another"] == "One"));

metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count);
}

Expand Down Expand Up @@ -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<Dictionary<string, string>>());
service: "testService",
Arg.Is<Dictionary<string, string>>(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<IMetrics>();

metricsMock.Options.Returns(new MetricsOptions
{
CaptureColdStart = true,
Namespace = "dotnet-powertools-test",
Service = "testService",
FunctionName = "My_Function_Custome_Name",
DefaultDimensions = new Dictionary<string, string>
{
{ "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<Dictionary<string, string>>(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);
}

Expand Down Expand Up @@ -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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public void HandlerSingleMetricDimensions()
{
_metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, defaultDimensions: _metrics.Options.DefaultDimensions);
}

}

0 comments on commit 97dc991

Please sign in to comment.