diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs index 5d33f484..6f0b868a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs @@ -23,7 +23,7 @@ namespace AWS.Lambda.Powertools.Metrics; /// Implements the /// /// -public interface IMetrics : IDisposable +public interface IMetrics { /// /// Adds metric diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index 9d55b06a..af72bd89 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -25,13 +25,13 @@ namespace AWS.Lambda.Powertools.Metrics; /// Implements the /// /// -public class Metrics : IMetrics +public class Metrics : IMetrics, IDisposable { /// /// The instance /// private static IMetrics _instance; - + /// /// The context /// @@ -65,15 +65,15 @@ public class Metrics : IMetrics internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string nameSpace = null, string service = null, bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false) { - if (_instance != null) return; + _instance ??= this; - _instance = this; _powertoolsConfigurations = powertoolsConfigurations; _raiseOnEmptyMetrics = raiseOnEmptyMetrics; _captureColdStartEnabled = captureColdStartEnabled; _context = InitializeContext(nameSpace, service, null); _powertoolsConfigurations.SetExecutionEnvironment(this); + } /// @@ -91,11 +91,11 @@ void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolut { if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException( - $"'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); + nameof(key), "'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); if (value < 0) { throw new ArgumentException( - "'AddMetric' method requires a valid metrics value. Value must be >= 0."); + "'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value)); } var metrics = _context.GetMetrics(); @@ -150,8 +150,8 @@ string IMetrics.GetService() void IMetrics.AddDimension(string key, string value) { if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentNullException( - $"'AddDimension' method requires a valid dimension key. 'Null' or empty values are not allowed."); + throw new ArgumentNullException(nameof(key), + "'AddDimension' method requires a valid dimension key. 'Null' or empty values are not allowed."); _context.AddDimension(key, value); } @@ -168,8 +168,8 @@ void IMetrics.AddDimension(string key, string value) void IMetrics.AddMetadata(string key, object value) { if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentNullException( - $"'AddMetadata' method requires a valid metadata key. 'Null' or empty values are not allowed."); + throw new ArgumentNullException(nameof(key), + "'AddMetadata' method requires a valid metadata key. 'Null' or empty values are not allowed."); _context.AddMetadata(key, value); } @@ -177,19 +177,19 @@ void IMetrics.AddMetadata(string key, object value) /// /// Implements interface that sets default dimension list /// - /// Default Dimension List + /// Default Dimension List /// /// 'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty /// values are not allowed. /// - void IMetrics.SetDefaultDimensions(Dictionary defaultDimensions) + void IMetrics.SetDefaultDimensions(Dictionary defaultDimension) { - foreach (var item in defaultDimensions) + foreach (var item in defaultDimension) if (string.IsNullOrWhiteSpace(item.Key) || string.IsNullOrWhiteSpace(item.Value)) - throw new ArgumentNullException( - $"'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty values are not allowed."); + throw new ArgumentNullException(nameof(item.Key), + "'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty values are not allowed."); - _context.SetDefaultDimensions(DictionaryToList(defaultDimensions)); + _context.SetDefaultDimensions(DictionaryToList(defaultDimension)); } /// @@ -258,8 +258,8 @@ void IMetrics.PushSingleMetric(string metricName, double value, MetricUnit unit, Dictionary defaultDimensions, MetricResolution metricResolution) { if (string.IsNullOrWhiteSpace(metricName)) - throw new ArgumentNullException( - $"'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); + throw new ArgumentNullException(nameof(metricName), + "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); using var context = InitializeContext(nameSpace, service, defaultDimensions); context.AddMetric(metricName, value, unit, metricResolution); @@ -272,7 +272,21 @@ void IMetrics.PushSingleMetric(string metricName, double value, MetricUnit unit, /// public void Dispose() { - _instance.Flush(); + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// + /// + /// + protected virtual void Dispose(bool disposing) + { + // Cleanup + if (disposing) + { + _instance.Flush(); + } } /// diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs index 4072a1d7..d08b598d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs @@ -467,7 +467,7 @@ public void WhenMetricIsNegativeValue_ThrowException() // Assert var exception = Assert.Throws(act); - Assert.Equal("'AddMetric' method requires a valid metrics value. Value must be >= 0.", exception.Message); + Assert.Equal("'AddMetric' method requires a valid metrics value. Value must be >= 0. (Parameter 'value')", exception.Message); // RESET handler.ResetForTest(); diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandler.cs index 61ba60c8..8a9e9204 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandler.cs @@ -7,17 +7,49 @@ namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; public class ExceptionFunctionHandler { [Metrics(Namespace = "ns", Service = "svc")] - public async Task Handle(string input) + public Task Handle(string input) { ThisThrows(); + return Task.FromResult(input.ToUpper(CultureInfo.InvariantCulture)); + } + + [Metrics(Namespace = "ns", Service = "svc")] + public Task HandleDecoratorOutsideHandler(string input) + { + MethodDecorated(); + + Metrics.AddMetric($"Metric Name", 1, MetricUnit.Count); - await Task.Delay(1); + return Task.FromResult(input.ToUpper(CultureInfo.InvariantCulture)); + } + + [Metrics(Namespace = "ns", Service = "svc")] + public Task HandleDecoratorOutsideHandlerException(string input) + { + MethodDecorated(); + + Metrics.AddMetric($"Metric Name", 1, MetricUnit.Count); + + ThisThrowsDecorated(); + + return Task.FromResult(input.ToUpper(CultureInfo.InvariantCulture)); + } - return input.ToUpper(CultureInfo.InvariantCulture); + [Metrics(Namespace = "ns", Service = "svc")] + private void MethodDecorated() + { + Metrics.AddMetric($"Metric Name", 1, MetricUnit.Count); + Metrics.AddMetric($"Metric Name Decorated", 1, MetricUnit.Count); } private void ThisThrows() { throw new NullReferenceException(); } + + [Metrics(Namespace = "ns", Service = "svc")] + private void ThisThrowsDecorated() + { + throw new NullReferenceException(); + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandlerTests.cs index 011cd1e5..0a1aad17 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/ExceptionFunctionHandlerTests.cs @@ -20,6 +20,35 @@ public async Task Stack_Trace_Included_When_Decorator_Present() // Assert var tracedException = await Assert.ThrowsAsync(Handle); Assert.StartsWith("at AWS.Lambda.Powertools.Metrics.Tests.Handlers.ExceptionFunctionHandler.ThisThrows()", tracedException.StackTrace?.TrimStart()); + } + + [Fact] + public async Task Stack_Trace_Included_When_Decorator_Present_In_Method() + { + // Arrange + Metrics.ResetForTest(); + var handler = new ExceptionFunctionHandler(); + + // Act + Task Handle() => handler.HandleDecoratorOutsideHandlerException("whatever"); + + // Assert + var tracedException = await Assert.ThrowsAsync(Handle); + Assert.StartsWith("at AWS.Lambda.Powertools.Metrics.Tests.Handlers.ExceptionFunctionHandler.__a$_around_ThisThrows", tracedException.StackTrace?.TrimStart()); + } + + [Fact] + public async Task Decorator_In_Non_Handler_Method_Does_Not_Throw_Exception() + { + // Arrange + Metrics.ResetForTest(); + var handler = new ExceptionFunctionHandler(); + // Act + Task Handle() => handler.HandleDecoratorOutsideHandler("whatever"); + + // Assert + var tracedException = await Record.ExceptionAsync(Handle); + Assert.Null(tracedException); } } \ No newline at end of file