Skip to content

Bug: Metrics throws exception when decorating non handler methods #498

@hjgraca

Description

@hjgraca

Expected Behaviour

Even though Metrics decorator should be used exclusively to decorate the handler, utilities should not throw exceptions when used incorrectly.

Current Behaviour

Currently Metrics throws NullReferenceException when decorating non handler (no Lambda context) methods.

System.NullReferenceException: Object reference not set to an instance of an object.
   at AWS.Lambda.Powertools.Metrics.Metrics.AWS.Lambda.Powertools.Metrics.IMetrics.Flush(Boolean metricsOverflow) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs:line 204
   at AWS.Lambda.Powertools.Metrics.MetricsAspectHandler.OnExit(AspectEventArgs eventArgs) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspectHandler.cs:line 71
   at AWS.Lambda.Powertools.Common.MethodAspectAttribute.WrapSync[T](Func`2 target, Object[] args, AspectEventArgs eventArgs) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Common/Aspects/MethodAspectAttribute.cs:line 72
   at AWS.Lambda.Powertools.Common.UniversalWrapperAspect.Handle(Object instance, Type type, MethodBase method, Func`2 target, String name, Object[] args, Type returnType, Attribute[] triggers) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs:line 87
   at AWS.Lambda.Powertools.Metrics.Tests.Handlers.ExceptionFunctionHandler.__a$_around_MethodDecorated_100663324_w_0(Object[] )

Flush method has a null _context

void IMetrics.Flush(bool metricsOverflow)
    {
        if (_context.GetMetrics().Count == 0
            && _raiseOnEmptyMetrics)
            throw new SchemaValidationException(true);

        if (_context.IsSerializable)
        {
            var emfPayload = _context.Serialize();

            Console.WriteLine(emfPayload);

            _context.ClearMetrics();

            if (!metricsOverflow) _context.ClearNonDefaultDimensions();
        }
        else
        {
            if (!_captureColdStartEnabled)
                Console.WriteLine(
                    "##WARNING## Metrics and Metadata have not been specified. No data will be sent to Cloudwatch Metrics.");
        }
    }

Code snippet

// Any method with Metrics

    [Metrics(Namespace = "ns", Service = "svc")]
    private void MethodDecorated()
    {
        // NOOP
        Metrics.AddMetric($"Metric Name", 1, MetricUnit.Count);
        Metrics.AddMetric($"Metric Name Decorated", 1, MetricUnit.Count);
    }

Possible Solution

In the Metrics constructor

    internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string nameSpace = null, string service = null,
        bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false)
    {
        if (_instance != null) return;

        _instance = this;
        _powertoolsConfigurations = powertoolsConfigurations;
        _raiseOnEmptyMetrics = raiseOnEmptyMetrics;
        _captureColdStartEnabled = captureColdStartEnabled;
        _context = InitializeContext(nameSpace, service, null);
        
        _powertoolsConfigurations.SetExecutionEnvironment(this);
        
    }

If the instance is not null _context is never set

Fix : always set the values, but only set instance if null.

    internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string nameSpace = null, string service = null,
        bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false)
    {
        _instance ??= this;

        _powertoolsConfigurations = powertoolsConfigurations;
        _raiseOnEmptyMetrics = raiseOnEmptyMetrics;
        _captureColdStartEnabled = captureColdStartEnabled;
        _context = InitializeContext(nameSpace, service, null);
        
        _powertoolsConfigurations.SetExecutionEnvironment(this);
    }

Steps to Reproduce

Decorate any method with [Metrics] that is not the Handler

Powertools for AWS Lambda (.NET) version

all

AWS Lambda function runtime

dotnet6

Debugging logs

System.NullReferenceException: Object reference not set to an instance of an object.
   at AWS.Lambda.Powertools.Metrics.Metrics.AWS.Lambda.Powertools.Metrics.IMetrics.Flush(Boolean metricsOverflow) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs:line 204
   at AWS.Lambda.Powertools.Metrics.MetricsAspectHandler.OnExit(AspectEventArgs eventArgs) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspectHandler.cs:line 71
   at AWS.Lambda.Powertools.Common.MethodAspectAttribute.WrapSync[T](Func`2 target, Object[] args, AspectEventArgs eventArgs) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Common/Aspects/MethodAspectAttribute.cs:line 72
   at AWS.Lambda.Powertools.Common.UniversalWrapperAspect.Handle(Object instance, Type type, MethodBase method, Func`2 target, String name, Object[] args, Type returnType, Attribute[] triggers) in /aws-lambda-powertools-dotnet/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs:line 87
   at AWS.Lambda.Powertools.Metrics.Tests.Handlers.ExceptionFunctionHandler.__a$_around_MethodDecorated_100663324_w_0(Object[] )

Metadata

Metadata

Assignees

Labels

area/loggingCore logging utilitybugUnexpected, reproducible and unintended software behaviour

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions