Skip to content

Commit

Permalink
Prevent default service to be added to PushSingleMetric
Browse files Browse the repository at this point in the history
  • Loading branch information
hjgraca committed Oct 21, 2024
1 parent 2b6a336 commit 31dad3b
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 45 deletions.
78 changes: 50 additions & 28 deletions libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
*
* http://aws.amazon.com/apache2.0
*
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
Expand All @@ -31,7 +31,7 @@ public class Metrics : IMetrics, IDisposable
/// The instance
/// </summary>
private static IMetrics _instance;

/// <summary>
/// The context
/// </summary>
Expand All @@ -46,7 +46,7 @@ public class Metrics : IMetrics, IDisposable
/// If true, Powertools for AWS Lambda (.NET) will throw an exception on empty metrics when trying to flush
/// </summary>
private readonly bool _raiseOnEmptyMetrics;

/// <summary>
/// The capture cold start enabled
/// </summary>
Expand Down Expand Up @@ -76,9 +76,8 @@ internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string name
_raiseOnEmptyMetrics = raiseOnEmptyMetrics;
_captureColdStartEnabled = captureColdStartEnabled;
_context = InitializeContext(nameSpace, service, null);

_powertoolsConfigurations.SetExecutionEnvironment(this);

}

/// <summary>
Expand All @@ -96,18 +95,20 @@ void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolut
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentNullException(
nameof(key), "'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.");

if (value < 0) {
nameof(key),
"'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.");

Check warning on line 99 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#L98-L99

Added lines #L98 - L99 were not covered by tests

if (value < 0)
{
throw new ArgumentException(
"'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value));
}

lock (_lockObj)
{
var metrics = _context.GetMetrics();
if (metrics.Count > 0 &&

if (metrics.Count > 0 &&
(metrics.Count == PowertoolsConfigurations.MaxMetrics ||
metrics.FirstOrDefault(x => x.Name == key)
?.Values.Count == PowertoolsConfigurations.MaxMetrics))
Expand All @@ -134,7 +135,14 @@ void IMetrics.SetNamespace(string nameSpace)
/// <returns>Namespace identifier</returns>
string IMetrics.GetNamespace()
{
return _context.GetNamespace();
try
{
return _context.GetNamespace();
}
catch
{
return null;
}
}

/// <summary>
Expand All @@ -143,7 +151,14 @@ string IMetrics.GetNamespace()
/// <returns>System.String.</returns>
string IMetrics.GetService()
{
return _context.GetService();
try
{
return _context.GetService();
}
catch
{
return null;

Check warning on line 160 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#L158-L160

Added lines #L158 - L160 were not covered by tests
}
}

/// <summary>
Expand Down Expand Up @@ -226,7 +241,7 @@ void IMetrics.Flush(bool metricsOverflow)
{
if (!_captureColdStartEnabled)
Console.WriteLine(
"##WARNING## Metrics and Metadata have not been specified. No data will be sent to Cloudwatch Metrics.");
"##User-WARNING## No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using 'RaiseOnEmptyMetrics = true'");
}
}

Expand Down Expand Up @@ -283,7 +298,7 @@ public void Dispose()
Dispose(true);
GC.SuppressFinalize(this);
}

/// <summary>
///
/// </summary>
Expand Down Expand Up @@ -356,7 +371,7 @@ public static void SetDefaultDimensions(Dictionary<string, string> defaultDimens
{
_instance.SetDefaultDimensions(defaultDimensions);
}

/// <summary>
/// Clears both default dimensions and dimensions lists
/// </summary>
Expand Down Expand Up @@ -389,14 +404,14 @@ private void Flush(MetricsContext context)
/// <param name="defaultDimensions">Default dimensions list</param>
/// <param name="metricResolution">Metrics resolution</param>
public static void PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace = null,
string service = null, Dictionary<string, string> defaultDimensions = null, MetricResolution metricResolution = MetricResolution.Default)
string service = null, Dictionary<string, string> defaultDimensions = null,
MetricResolution metricResolution = MetricResolution.Default)
{
_instance.PushSingleMetric(metricName, value, unit, nameSpace, service, defaultDimensions, metricResolution);
}

/// <summary>
/// Sets global namespace, service name and default dimensions list. Service name is automatically added as a default
/// dimension
/// Sets global namespace, service name and default dimensions list.
/// </summary>
/// <param name="nameSpace">Metrics namespace</param>
/// <param name="service">Service Name</param>
Expand All @@ -406,19 +421,26 @@ private MetricsContext InitializeContext(string nameSpace, string service,
Dictionary<string, string> defaultDimensions)
{
var context = new MetricsContext();
var defaultDimensionsList = DictionaryToList(defaultDimensions);

context.SetNamespace(!string.IsNullOrWhiteSpace(nameSpace)
? nameSpace
: _powertoolsConfigurations.MetricsNamespace);
: _instance.GetNamespace() ?? _powertoolsConfigurations.MetricsNamespace);

context.SetService(!string.IsNullOrWhiteSpace(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
// TODO: Check if there is a way to get the default dimensions and if it makes sense
var parsedService = !string.IsNullOrWhiteSpace(service)
? service
: _powertoolsConfigurations.Service);

var defaultDimensionsList = DictionaryToList(defaultDimensions);
: _powertoolsConfigurations.Service == "service_undefined"
? null
: _powertoolsConfigurations.Service;

// Add service as a default dimension
defaultDimensionsList.Add(new DimensionSet("Service", context.GetService()));
if (parsedService != null)
{
context.SetService(parsedService);
defaultDimensionsList.Add(new DimensionSet("Service", context.GetService()));
}

context.SetDefaultDimensions(defaultDimensionsList);

Expand Down Expand Up @@ -447,4 +469,4 @@ internal static void ResetForTest()
{
_instance = null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ namespace AWS.Lambda.Powertools.Metrics;
/// </listheader>
/// <item>
/// <term>Service</term>
/// <description>string, service name is used for metric dimension across all metrics, by default service_undefined</description>
/// <description>string, service name is used for metric dimension</description>
/// </item>
/// <item>
/// <term>Namespace</term>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,39 @@ public void When_Multiple_DimensionsAreAdded_MustExistAsMembers()
var metricsOutput = _consoleOut.ToString();

// Assert
Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns1\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Type\",\"Service\"]]}]},\"Type\":\"Start\",\"Service\":\"service_undefined\",\"Lambda Execute\":1}", metricsOutput);

Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns2\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Type\",\"SessionId\",\"Service\"]]}]},\"Type\":\"Start\",\"SessionId\":\"Unset\",\"Service\":\"service_undefined\",\"Lambda Execute\":1}", metricsOutput);
Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"ServiceName\",\"ColdStart\":1}", metricsOutput);
Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SingleMetric1\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default1\"]]}]},\"Default1\":\"SingleMetric1\",\"SingleMetric1\":1}", metricsOutput);
Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns2\",\"Metrics\":[{\"Name\":\"SingleMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default1\",\"Default2\"]]}]},\"Default1\":\"SingleMetric2\",\"Default2\":\"SingleMetric2\",\"SingleMetric2\":1}", metricsOutput);
Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"AddMetric\",\"Unit\":\"Count\",\"StorageResolution\":1},{\"Name\":\"AddMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"ServiceName\",\"AddMetric\":1,\"AddMetric2\":1}", metricsOutput);
}

[Trait("Category", "SchemaValidation")]
[Fact]
public void When_PushSingleMetric_With_Namespace()
{
// Act
_handler.PushSingleMetricWithNamespace();

var metricsOutput = _consoleOut.ToString();

// Assert
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_Env_Namespace()
{
// Arrange
Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", "EnvNamespace");

Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\",\"Default\",\"SessionId\",\"Type\"]]}]},\"Service\":\"testService\",\"Default\":\"Initial\",\"SessionId\":\"MySessionId\",\"Type\":\"Start\",\"Lambda Execute\":1}", metricsOutput);
// Act
_handler.PushSingleMetricWithEnvNamespace();

var metricsOutput = _consoleOut.ToString();

// Assert
Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput);
}

[Trait("Category", "MetricsImplementation")]
Expand Down Expand Up @@ -366,6 +394,7 @@ public void Dispose()
{
// need to reset instance after each test
MetricsAspect.ResetForTest();
Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,39 @@ public void AddDimensions()
Metrics.AddMetric("TestMetric", 1, MetricUnit.Count);
}

[Metrics(Namespace = "dotnet-powertools-test", Service = "testService")]
[Metrics(Namespace = "dotnet-powertools-test", Service = "ServiceName", CaptureColdStart = true)]
public void AddMultipleDimensions()
{
Metrics.SetDefaultDimensions(new Dictionary<string, string> {
{ "Default", "Initial" }
});
Metrics.PushSingleMetric("Lambda Execute", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns1",
Metrics.PushSingleMetric("SingleMetric1", 1, MetricUnit.Count, metricResolution: MetricResolution.High,
defaultDimensions: new Dictionary<string, string> {
{ "Type", "Start" }
{ "Default1", "SingleMetric1" }
});

Metrics.PushSingleMetric("Lambda Execute", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns2",
Metrics.PushSingleMetric("SingleMetric2", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns2",
defaultDimensions: new Dictionary<string, string> {
{ "Default1", "SingleMetric2" },
{ "Default2", "SingleMetric2" }
});
Metrics.AddMetric("AddMetric", 1, MetricUnit.Count, MetricResolution.High);
Metrics.AddMetric("AddMetric2", 1, MetricUnit.Count, MetricResolution.High);
}

[Metrics(Namespace = "ExampleApplication")]
public void PushSingleMetricWithNamespace()
{
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, metricResolution: MetricResolution.High,
defaultDimensions: new Dictionary<string, string> {
{ "Default", "SingleMetric" }
});
}

[Metrics]
public void PushSingleMetricWithEnvNamespace()
{
Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, metricResolution: MetricResolution.High,
defaultDimensions: new Dictionary<string, string> {
{ "Type", "Start" },
{ "SessionId", "Unset" }
{ "Default", "SingleMetric" }
});
Metrics.AddMetric("Lambda Execute", 1, MetricUnit.Count, MetricResolution.High);
Metrics.AddDimension("SessionId", "MySessionId");
Metrics.AddDimension("Type", "Start");
}

[Metrics(Namespace = "dotnet-powertools-test", Service = "testService")]
Expand Down

0 comments on commit 31dad3b

Please sign in to comment.