From a35555a51daae6082b5ddbbd2f1fa90f67929a67 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:17:02 +0100 Subject: [PATCH 01/10] make project trimmable and aot ready --- libraries/src/Directory.Build.props | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/src/Directory.Build.props b/libraries/src/Directory.Build.props index 0260344d..8f412fa4 100644 --- a/libraries/src/Directory.Build.props +++ b/libraries/src/Directory.Build.props @@ -19,6 +19,14 @@ + + + + true + true + true + + From 4c59bb60a0faf8c031cbf506e45d139f0657638d Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:09:48 +0100 Subject: [PATCH 02/10] remove dependency on universal aspect wrapper, create a new aspect and remove aspecthandler. refacor tests, still need to fix test runner issues --- ...acingAspectHandler.cs => TracingAspect.cs} | 279 +++--- .../TracingAttribute.cs | 21 +- .../Handlers/HandlerTests.cs | 29 +- .../Handlers/Handlers.cs | 78 ++ .../TracingAttributeTest.cs | 894 ++++++++---------- .../XRayRecorderTests.cs | 2 +- 6 files changed, 605 insertions(+), 698 deletions(-) rename libraries/src/AWS.Lambda.Powertools.Tracing/Internal/{TracingAspectHandler.cs => TracingAspect.cs} (50%) create mode 100644 libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs similarity index 50% rename from libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectHandler.cs rename to libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs index e445ce8f..b7f4439b 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs @@ -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 @@ -14,152 +14,131 @@ */ using System; +using System.Linq; +using System.Reflection; using System.Runtime.ExceptionServices; using System.Text; +using AspectInjector.Broker; using AWS.Lambda.Powertools.Common; namespace AWS.Lambda.Powertools.Tracing.Internal; -/// -/// Class TracingAspectHandler. -/// Implements the -/// -/// -internal class TracingAspectHandler : IMethodAspectHandler +[Aspect(Scope.Global)] +public class TracingAspect { - /// - /// If true, then is cold start - /// - private static bool _isColdStart = true; - - /// - /// If true, capture annotations - /// - private static bool _captureAnnotations = true; /// - /// If true, tracing is disabled - /// - private static bool? _isTracingDisabled; - - /// - /// The capture mode - /// - private readonly TracingCaptureMode _captureMode; - - /// - /// Tracing namespace + /// The Powertools for AWS Lambda (.NET) configurations /// - private readonly string _namespace; - + private static readonly IPowertoolsConfigurations _powertoolsConfigurations = _powertoolsConfigurations ?? PowertoolsConfigurations.Instance; + /// - /// The Powertools for AWS Lambda (.NET) configurations + /// X-Ray Recorder /// - private readonly IPowertoolsConfigurations _powertoolsConfigurations; - + private static readonly IXRayRecorder _xRayRecorder = _xRayRecorder ?? XRayRecorder.Instance; + /// - /// The segment name + /// If true, then is cold start /// - private readonly string _segmentName; + private static bool _isColdStart = true; /// - /// X-Ray Recorder + /// If true, capture annotations /// - private readonly IXRayRecorder _xRayRecorder; - + private static bool _captureAnnotations = true; + /// /// If true, annotations have been captured /// private bool _isAnnotationsCaptured; - + /// - /// Initializes a new instance of the class. + /// Tracing namespace /// - /// Name of the segment. - /// The namespace. - /// The capture mode. - /// The Powertools for AWS Lambda (.NET) configurations. - /// The X-Ray recorder. - internal TracingAspectHandler - ( - string segmentName, - string nameSpace, - TracingCaptureMode captureMode, - IPowertoolsConfigurations powertoolsConfigurations, - IXRayRecorder xRayRecorder - ) - { - _segmentName = segmentName; - _namespace = nameSpace; - _captureMode = captureMode; - _powertoolsConfigurations = powertoolsConfigurations; - _xRayRecorder = xRayRecorder; - } - + private string _namespace; + /// - /// Handles the event. + /// The capture mode /// - /// - /// The instance containing the - /// event data. - /// - public void OnEntry(AspectEventArgs eventArgs) + private TracingCaptureMode _captureMode; + + [Advice(Kind.Around)] + public object Around( + [Argument(Source.Instance)] object instance, + [Argument(Source.Name)] string name, + [Argument(Source.Arguments)] object[] args, + [Argument(Source.Type)] Type hostType, + [Argument(Source.Metadata)] MethodBase method, + [Argument(Source.ReturnType)] Type returnType, + [Argument(Source.Target)] Func target, + [Argument(Source.Triggers)] Attribute[] triggers) { - if(TracingDisabled()) - return; + // Before running Function - var segmentName = !string.IsNullOrWhiteSpace(_segmentName) ? _segmentName : $"## {eventArgs.Name}"; - var nameSpace = GetNamespace(); + try + { + var trigger = triggers.OfType().First(); + + if(TracingDisabled()) + return target(args); + + _namespace = trigger.Namespace; + _captureMode = trigger.CaptureMode; + + var segmentName = !string.IsNullOrWhiteSpace(trigger.SegmentName) ? trigger.SegmentName : $"## {name}"; + var nameSpace = GetNamespace(); - _xRayRecorder.BeginSubsegment(segmentName); - _xRayRecorder.SetNamespace(nameSpace); + _xRayRecorder.BeginSubsegment(segmentName); + _xRayRecorder.SetNamespace(nameSpace); - if (_captureAnnotations) - { - _xRayRecorder.AddAnnotation("ColdStart", _isColdStart); + if (_captureAnnotations) + { + _xRayRecorder.AddAnnotation("ColdStart", _isColdStart); - _captureAnnotations = false; - _isAnnotationsCaptured = true; + _captureAnnotations = false; + _isAnnotationsCaptured = true; - if (_powertoolsConfigurations.IsServiceDefined) - _xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service); - } + if (_powertoolsConfigurations.IsServiceDefined) + _xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service); + } - _isColdStart = false; - } - - /// - /// Called when [success]. - /// - /// - /// The instance containing the - /// event data. - /// - /// The result. - public void OnSuccess(AspectEventArgs eventArgs, object result) - { - if (CaptureResponse()) + _isColdStart = false; + + // return of the handler + var result = target(args); + + if (CaptureResponse()) + { + _xRayRecorder.AddMetadata + ( + nameSpace, + $"{name} response", + result + ); + } + + // after + return result; + } + catch (Exception e) { - var nameSpace = GetNamespace(); - - _xRayRecorder.AddMetadata - ( - nameSpace, - $"{eventArgs.Name} response", - result - ); + HandleException(e, name); + throw; } } - /// - /// Called when [exception]. - /// - /// - /// The instance containing the - /// event data. - /// - /// The exception. - public void OnException(AspectEventArgs eventArgs, Exception exception) + [Advice(Kind.After)] + public void OnExit() { + if(TracingDisabled()) + return; + + if (_isAnnotationsCaptured) + _captureAnnotations = true; + + _xRayRecorder.EndSubsegment(); + } + + private void HandleException(Exception exception, string name) { if (CaptureError()) { @@ -182,32 +161,14 @@ public void OnException(AspectEventArgs eventArgs, Exception exception) _xRayRecorder.AddMetadata ( nameSpace, - $"{eventArgs.Name} error", + $"{name} error", sb.ToString() ); } - // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: - // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later - ExceptionDispatchInfo.Capture(exception).Throw(); - } - - /// - /// Handles the event. - /// - /// - /// The instance containing the - /// event data. - /// - public void OnExit(AspectEventArgs eventArgs) - { - if(TracingDisabled()) - return; - - if (_isAnnotationsCaptured) - _captureAnnotations = true; - - _xRayRecorder.EndSubsegment(); + // // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: + // // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later + // ExceptionDispatchInfo.Capture(exception).Throw(); } /// @@ -218,7 +179,24 @@ private string GetNamespace() { return !string.IsNullOrWhiteSpace(_namespace) ? _namespace : _powertoolsConfigurations.Service; } + + private bool TracingDisabled() + { + if (_powertoolsConfigurations.TracingDisabled) + { + Console.WriteLine("Tracing has been disabled via env var POWERTOOLS_TRACE_DISABLED"); + return true; + } + if (!_powertoolsConfigurations.IsLambdaEnvironment) + { + Console.WriteLine("Running outside Lambda environment; disabling Tracing"); + return true; + } + + return false; + } + /// /// Captures the response. /// @@ -241,7 +219,7 @@ private bool CaptureResponse() return false; } } - + /// /// Captures the error. /// @@ -265,32 +243,6 @@ private bool CaptureError() } } - /// - /// Tracing disabled. - /// - /// true if tracing is disabled, false otherwise. - private bool TracingDisabled() - { - if (_isTracingDisabled.HasValue) - return _isTracingDisabled.Value; - - if (_powertoolsConfigurations.TracingDisabled) - { - _isTracingDisabled = true; - Console.WriteLine("Tracing has been disabled via env var POWERTOOLS_TRACE_DISABLED"); - } - else if (!_powertoolsConfigurations.IsLambdaEnvironment) - { - _isTracingDisabled = true; - Console.WriteLine("Running outside Lambda environment; disabling Tracing"); - } - else - { - _isTracingDisabled = false; - } - return _isTracingDisabled.Value; - } - /// /// Resets static variables for test. /// @@ -298,6 +250,5 @@ internal static void ResetForTest() { _isColdStart = true; _captureAnnotations = true; - _isTracingDisabled = null; } -} +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs index 0d9d6727..4ac0db2d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs @@ -13,6 +13,8 @@ * permissions and limitations under the License. */ +using System; +using AspectInjector.Broker; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Tracing.Internal; @@ -105,7 +107,8 @@ namespace AWS.Lambda.Powertools.Tracing; /// } /// /// -public class TracingAttribute : MethodAspectAttribute +[Injection(typeof(TracingAspect))] +public class TracingAttribute : Attribute { /// /// Set custom segment name for the operation. @@ -128,20 +131,4 @@ public class TracingAttribute : MethodAspectAttribute /// /// The capture mode. public TracingCaptureMode CaptureMode { get; set; } = TracingCaptureMode.EnvironmentVariable; - - /// - /// Creates the handler. - /// - /// IMethodAspectHandler. - protected override IMethodAspectHandler CreateHandler() - { - return new TracingAspectHandler - ( - SegmentName, - Namespace, - CaptureMode, - PowertoolsConfigurations.Instance, - XRayRecorder.Instance - ); - } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs index 066d0743..5c6fb2cc 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs @@ -1,10 +1,13 @@ using System; +using System.Linq; using System.Threading.Tasks; +using Amazon.XRay.Recorder.Core; +using AWS.Lambda.Powertools.Tracing.Internal; using Xunit; namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; -public sealed class HandlerTests +public sealed class HandlerTests : IDisposable { [Fact] public async Task Stack_Trace_Included_When_Decorator_Present() @@ -31,6 +34,30 @@ public async Task When_Decorator_Present_In_Generic_Method_Should_Not_Throw_When await handler.Handle("whatever"); // Assert + } + + [Fact] + public void When_Handler_Is_Decorated() + { + // Arrange + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + var handler = new Handlers(); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + // Act + handler.Handle(); + var subSegment = segment.Subsegments[0]; + + // Assert + Assert.True(segment.IsSubsegmentsAdded); + Assert.True(subSegment.IsAnnotationsAdded); + Assert.True(subSegment.Annotations.Any()); + Assert.Equal("ColdStart", subSegment.Annotations.First().Key); + Assert.Equal(true, subSegment.Annotations.First().Value); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", ""); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs new file mode 100644 index 00000000..a6462e66 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs @@ -0,0 +1,78 @@ +/* + * 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 + * permissions and limitations under the License. + */ + +using System; + +namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; + +public class Handlers +{ + [Tracing()] + public string[] Handle() + { + return new[] { "A", "B" }; + } + + [Tracing(SegmentName = "SegmentName")] + public void HandleWithSegmentName() + { + + } + + [Tracing(Namespace = "Namespace Defined")] + public void HandleWithNamespace() + { + + } + + [Tracing()] + public void HandleThrowsException(string exception) + { + throw new Exception(exception); + } + + [Tracing(CaptureMode = TracingCaptureMode.Response)] + public string[] HandleWithCaptureModeResponse(bool exception = false) + { + if (exception) + throw new Exception("Failed"); + + return new[] { "A", "B" }; + } + + [Tracing(CaptureMode = TracingCaptureMode.ResponseAndError)] + public string[] HandleWithCaptureModeResponseAndError(bool exception = false) + { + if (exception) + throw new Exception("Failed"); + return new[] { "A", "B" }; + } + + [Tracing(CaptureMode = TracingCaptureMode.Error)] + public string[] HandleWithCaptureModeError(bool exception = false) + { + if (exception) + throw new Exception("Failed"); + return new[] { "A", "B" }; + } + + [Tracing(CaptureMode = TracingCaptureMode.Disabled)] + public string[] HandleWithCaptureModeDisabled(bool exception = false) + { + if (exception) + throw new Exception("Failed"); + return new[] { "A", "B" }; + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index 34031725..bc31de7a 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -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 @@ -16,9 +16,8 @@ using System; using System.Linq; using System.Text; -using AWS.Lambda.Powertools.Common; +using Amazon.XRay.Recorder.Core; using AWS.Lambda.Powertools.Tracing.Internal; -using NSubstitute; using Xunit; [assembly: CollectionBehavior(DisableTestParallelization = true)] @@ -26,350 +25,271 @@ namespace AWS.Lambda.Powertools.Tracing.Tests { [Collection("Sequential")] - public class TracingAttributeColdStartTest + public class TracingAttributeColdStartTest : IDisposable { + private readonly Handlers.Handlers _handler; + + public TracingAttributeColdStartTest() + { + _handler = new Handlers.Handlers(); + } + [Fact] public void OnEntry_WhenFirstCall_CapturesColdStart() { // Arrange - const bool isColdStart = true; - var methodName = Guid.NewGuid().ToString(); - var service = Guid.NewGuid().ToString(); - - var configurations1 = Substitute.For(); - configurations1.TracingDisabled.Returns(false); - configurations1.IsLambdaEnvironment.Returns(true); - configurations1.IsServiceDefined.Returns(true); - configurations1.Service.Returns(service); - - var configurations2 = Substitute.For(); - configurations2.TracingDisabled.Returns(false); - configurations2.IsLambdaEnvironment.Returns(true); - configurations2.IsServiceDefined.Returns(false); - - var recorder1 = Substitute.For(); - var recorder2 = Substitute.For(); - var recorder3 = Substitute.For(); - var recorder4 = Substitute.For(); - - TracingAspectHandler.ResetForTest(); - var handler1 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations1, recorder1); - var handler2 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations1, recorder2); - var handler3 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations2, recorder3); - var handler4 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations2, recorder4); - - var eventArgs = new AspectEventArgs { Name = methodName }; + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); // Act // Cold Start Execution - handler1.OnEntry(eventArgs); - handler2.OnEntry(eventArgs); - handler2.OnExit(eventArgs); - handler1.OnExit(eventArgs); + // Start segment + var segmentCold = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + + var subSegmentCold = segmentCold.Subsegments[0]; // Warm Start Execution - handler3.OnEntry(eventArgs); - handler4.OnEntry(eventArgs); - handler4.OnExit(eventArgs); - handler3.OnExit(eventArgs); + // Start segment + var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegmentWarm = segmentWarm.Subsegments[0]; // Assert - recorder1.Received(1).AddAnnotation( - Arg.Is(i => i == "ColdStart"), - Arg.Is(i => i == isColdStart) - ); - - recorder1.Received(1).AddAnnotation( - Arg.Is(i => i == "Service"), - Arg.Is(i => i == service) - ); - - recorder2.DidNotReceive().AddAnnotation( - Arg.Any(), - Arg.Any() - ); - - recorder2.DidNotReceive().AddAnnotation( - Arg.Any(), - Arg.Any() - ); - - recorder3.Received(1).AddAnnotation( - Arg.Is(i => i == "ColdStart"), - Arg.Is(i => i == !isColdStart) - ); - - recorder3.DidNotReceive().AddAnnotation( - Arg.Any(), - Arg.Any() - ); - - recorder4.DidNotReceive().AddAnnotation( - Arg.Any(), - Arg.Any() - ); - - recorder4.DidNotReceive().AddAnnotation( - Arg.Any(), - Arg.Any() - ); + // Cold + Assert.True(segmentCold.IsSubsegmentsAdded); + Assert.Single(segmentCold.Subsegments); + Assert.True(subSegmentCold.IsAnnotationsAdded); + Assert.Equal(2, subSegmentCold.Annotations.Count()); + Assert.Equal(true, subSegmentCold.Annotations.Single(x => x.Key == "ColdStart").Value); + Assert.Equal("POWERTOOLS", subSegmentCold.Annotations.Single(x => x.Key == "Service").Value); + + // Warm + Assert.True(segmentWarm.IsSubsegmentsAdded); + Assert.Single(segmentWarm.Subsegments); + Assert.True(subSegmentWarm.IsAnnotationsAdded); + Assert.Equal(2, subSegmentWarm.Annotations.Count()); + Assert.Equal(false, subSegmentWarm.Annotations.Single(x => x.Key == "ColdStart").Value); + Assert.Equal("POWERTOOLS", subSegmentWarm.Annotations.Single(x => x.Key == "Service").Value); } - } - [Collection("Sequential")] - public class TracingAttributeDisableTest - { [Fact] - public void Tracing_WhenTracerDisabled_DisablesTracing() + public void OnEntry_WhenFirstCall_And_Service_Not_Set_CapturesColdStart() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var configurations1 = Substitute.For(); - configurations1.TracingDisabled.Returns(true); - configurations1.IsLambdaEnvironment.Returns(true); + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + + // Act + // Cold Start Execution + // Start segment + var segmentCold = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegmentCold = segmentCold.Subsegments[0]; - var configurations2 = Substitute.For(); - configurations2.TracingDisabled.Returns(true); - configurations2.IsLambdaEnvironment.Returns(true); + // Warm Start Execution + // Start segment + var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegmentWarm = segmentWarm.Subsegments[0]; - var recorder1 = Substitute.For(); - var recorder2 = Substitute.For(); + // Assert + // Cold + Assert.True(segmentCold.IsSubsegmentsAdded); + Assert.Single(segmentCold.Subsegments); + Assert.True(subSegmentCold.IsAnnotationsAdded); + Assert.Single(subSegmentCold.Annotations); + Assert.Equal(true, subSegmentCold.Annotations.Single(x => x.Key == "ColdStart").Value); + + // Warm + Assert.True(segmentWarm.IsSubsegmentsAdded); + Assert.Single(segmentWarm.Subsegments); + Assert.True(subSegmentWarm.IsAnnotationsAdded); + Assert.Single(subSegmentWarm.Annotations); + Assert.Equal(false, subSegmentWarm.Annotations.Single(x => x.Key == "ColdStart").Value); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACE_DISABLED", ""); + TracingAspect.ResetForTest(); + } + } - TracingAspectHandler.ResetForTest(); - var handler1 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations1, recorder1); - var handler2 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations2, recorder2); + [Collection("Sequential")] + public class TracingAttributeDisableTest : IDisposable + { + private readonly Handlers.Handlers _handler; - var results = new[] { "A", "B" }; - var exception = new Exception("Test Exception"); - var eventArgs = new AspectEventArgs { Name = methodName }; + public TracingAttributeDisableTest() + { + _handler = new Handlers.Handlers(); + } + + [Fact] + public void Tracing_WhenTracerDisabled_DisablesTracing() + { + // Arrange + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACE_DISABLED", "true"); // Act // Cold Start Execution - handler1.OnEntry(eventArgs); - handler1.OnSuccess(eventArgs, results); - void Act1() => handler1.OnException(eventArgs, exception); - handler1.OnExit(eventArgs); + // Start segment + var segmentCold = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); // Warm Start Execution - handler2.OnEntry(eventArgs); - handler2.OnSuccess(eventArgs, results); - void Act2() => handler2.OnException(eventArgs, exception); - handler2.OnExit(eventArgs); + // Start segment + var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + Assert.Throws(() => _handler.HandleThrowsException("My Exception")); // Assert - recorder1.DidNotReceive().BeginSubsegment(Arg.Any()); - recorder1.DidNotReceive().EndSubsegment(); - recorder1.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); - Assert.Throws(Act1); - recorder1.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); - - recorder2.DidNotReceive().BeginSubsegment(Arg.Any()); - recorder2.DidNotReceive().EndSubsegment(); - recorder2.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); - Assert.Throws(Act2); - recorder2.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.False(segmentCold.IsAnnotationsAdded); + Assert.Empty(segmentCold.Annotations); + Assert.False(segmentCold.IsSubsegmentsAdded); + Assert.False(segmentCold.IsMetadataAdded); + + Assert.False(segmentWarm.IsAnnotationsAdded); + Assert.Empty(segmentWarm.Annotations); + Assert.False(segmentWarm.IsSubsegmentsAdded); + Assert.False(segmentWarm.IsMetadataAdded); + } + + public void Dispose() + { + ClearEnvironment(); + } + + private static void ClearEnvironment() + { + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACE_DISABLED", ""); + TracingAspect.ResetForTest(); } } [Collection("Sequential")] public class TracingAttributeLambdaEnvironmentTest { + private readonly Handlers.Handlers _handler; + + public TracingAttributeLambdaEnvironmentTest() + { + _handler = new Handlers.Handlers(); + } + [Fact] public void Tracing_WhenOutsideOfLambdaEnv_DisablesTracing() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var configurations1 = Substitute.For(); - configurations1.TracingDisabled.Returns(false); - configurations1.IsLambdaEnvironment.Returns(false); - - var configurations2 = Substitute.For(); - configurations2.TracingDisabled.Returns(true); - configurations2.IsLambdaEnvironment.Returns(true); - - var recorder1 = Substitute.For(); - var recorder2 = Substitute.For(); - - TracingAspectHandler.ResetForTest(); - var handler1 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations1, recorder1); - var handler2 = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations1, recorder2); - - var results = new[] { "A", "B" }; - var exception = new Exception("Test Exception"); - var eventArgs = new AspectEventArgs { Name = methodName }; + + // Need to manually create the initial segment + AWSXRayRecorder.Instance.BeginSegment("foo"); // Act // Cold Start Execution - handler1.OnEntry(eventArgs); - handler1.OnSuccess(eventArgs, results); - void Act1() => handler1.OnException(eventArgs, exception); - handler1.OnExit(eventArgs); - - // Warm Start Execution - handler2.OnEntry(eventArgs); - handler2.OnSuccess(eventArgs, results); - void Act2() => handler2.OnException(eventArgs, exception); - handler2.OnExit(eventArgs); + _handler.Handle(); + var segmentCold = AWSXRayRecorder.Instance.TraceContext.GetEntity(); // Assert - recorder1.DidNotReceive().BeginSubsegment(Arg.Any()); - recorder1.DidNotReceive().EndSubsegment(); - recorder1.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); - Assert.Throws(Act1); - recorder1.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); - - recorder2.DidNotReceive().BeginSubsegment(Arg.Any()); - recorder2.DidNotReceive().EndSubsegment(); - recorder2.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); - Assert.Throws(Act2); - recorder2.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.False(AWSXRayRecorder.IsLambda()); + Assert.False(segmentCold.IsAnnotationsAdded); + Assert.Empty(segmentCold.Annotations); + Assert.False(segmentCold.IsSubsegmentsAdded); + Assert.False(segmentCold.IsMetadataAdded); } } [Collection("Sequential")] - public class TracingAttributeTest + public class TracingAttributeTest : IDisposable { + private readonly Handlers.Handlers _handler; + public TracingAttributeTest() { - TracingAspectHandler.ResetForTest(); + _handler = new Handlers.Handlers(); } - + #region OnEntry Tests [Fact] public void OnEntry_WhenSegmentNameIsNull_BeginSubsegmentWithMethodName() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - - var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - handler.OnEntry(eventArgs); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).BeginSubsegment( - Arg.Is(i => i == $"## {methodName}") - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.Equal("## Handle", subSegment.Name); } [Fact] public void OnEntry_WhenSegmentNameHasValue_BeginSubsegmentWithValue() { // Arrange - var segmentName = Guid.NewGuid().ToString(); - var methodName = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - - var handler = new TracingAspectHandler(segmentName, null, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); // Act - handler.OnEntry(eventArgs); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithSegmentName(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).BeginSubsegment( - Arg.Is(i => i == segmentName) - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.Equal("SegmentName", subSegment.Name); } [Fact] public void OnEntry_WhenNamespaceIsNull_SetNamespaceWithService() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var service = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.Service.Returns(service); - var recorder = Substitute.For(); - - var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; + var serviceName = "POWERTOOLS"; + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", serviceName); // Act - handler.OnEntry(eventArgs); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).SetNamespace( - Arg.Is(i => i == service) - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.Equal(serviceName, subSegment.Namespace); } [Fact] public void OnEntry_WhenNamespaceHasValue_SetNamespaceWithValue() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); // Act - handler.OnEntry(eventArgs); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithNamespace(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).SetNamespace( - Arg.Is(i => i == nameSpace) - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.Equal("Namespace Defined", subSegment.Namespace); } #endregion @@ -380,173 +300,132 @@ public void OnEntry_WhenNamespaceHasValue_SetNamespaceWithValue() public void OnSuccess_WhenTracerCaptureResponseEnvironmentVariableIsTrue_CapturesResponse() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.TracerCaptureResponse.Returns(true); - var recorder = Substitute.For(); - var results = new[] { "A", "B" }; - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE", "true"); + // Act - handler.OnSuccess(eventArgs, results); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).AddMetadata( - Arg.Is(i => i == nameSpace), - Arg.Is(i => i == $"{methodName} response"), - Arg.Is(i => - i.First() == results.First() && - i.Last() == results.Last() - ) - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("Handle response", metadata.Keys.Cast().First()); + var handlerResponse = metadata.Values.Cast().First(); + Assert.Equal("A", handlerResponse[0]); + Assert.Equal("B", handlerResponse[1]); } [Fact] public void OnSuccess_WhenTracerCaptureResponseEnvironmentVariableIsFalse_DoesNotCaptureResponse() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.TracerCaptureResponse.Returns(false); - var recorder = Substitute.For(); - var results = new[] { "A", "B" }; - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE", "false"); + // Act - handler.OnSuccess(eventArgs, results); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.False(subSegment.IsMetadataAdded); + Assert.Empty(subSegment.Metadata); } [Fact] public void OnSuccess_WhenTracerCaptureModeIsResponse_CapturesResponse() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - var results = new[] { "A", "B" }; - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Response, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - handler.OnSuccess(eventArgs, results); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithCaptureModeResponse(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).AddMetadata( - Arg.Is(i => i == nameSpace), - Arg.Is(i => i == $"{methodName} response"), - Arg.Is(i => - i.First() == results.First() && - i.Last() == results.Last() - ) - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("HandleWithCaptureModeResponse response", metadata.Keys.Cast().First()); + var handlerResponse = metadata.Values.Cast().First(); + Assert.Equal("A", handlerResponse[0]); + Assert.Equal("B", handlerResponse[1]); } [Fact] public void OnSuccess_WhenTracerCaptureModeIsResponseAndError_CapturesResponse() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - var results = new[] { "A", "B" }; - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.ResponseAndError, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - handler.OnSuccess(eventArgs, results); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithCaptureModeResponseAndError(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.Received(1).AddMetadata( - Arg.Is(i => i == nameSpace), - Arg.Is(i => i == $"{methodName} response"), - Arg.Is(i => - i.First() == results.First() && - i.Last() == results.Last() - ) - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("HandleWithCaptureModeResponseAndError response", metadata.Keys.Cast().First()); + var handlerResponse = metadata.Values.Cast().First(); + Assert.Equal("A", handlerResponse[0]); + Assert.Equal("B", handlerResponse[1]); } [Fact] public void OnSuccess_WhenTracerCaptureModeIsError_DoesNotCaptureResponse() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - var results = new[] { "A", "B" }; - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Error, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - handler.OnSuccess(eventArgs, results); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithCaptureModeError(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.False(subSegment.IsMetadataAdded); // does not add metadata } [Fact] public void OnSuccess_WhenTracerCaptureModeIsDisabled_DoesNotCaptureResponse() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - var results = new[] { "A", "B" }; - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Disabled, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - handler.OnSuccess(eventArgs, results); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.HandleWithCaptureModeDisabled(); + var subSegment = segment.Subsegments[0]; // Assert - recorder.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.False(subSegment.IsMetadataAdded); // does not add metadata } #endregion @@ -557,176 +436,155 @@ public void OnSuccess_WhenTracerCaptureModeIsDisabled_DoesNotCaptureResponse() public void OnException_WhenTracerCaptureErrorEnvironmentVariableIsTrue_CapturesError() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.TracerCaptureError.Returns(true); - var recorder = Substitute.For(); - var exception = new Exception("Test Exception"); - var message = GetException(exception); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR", "true"); + // Act - void Act() => handler.OnException(eventArgs, exception); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + var exception = Record.Exception(() => + { + _handler.HandleThrowsException("My Exception"); + }); + var subSegment = segment.Subsegments[0]; + // Assert - Assert.Throws((Action)Act); - recorder.Received(1).AddMetadata( - Arg.Is(i => i == nameSpace), - Arg.Is(i => i == $"{methodName} error"), - Arg.Is(i => i == message) - ); + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("HandleThrowsException error", metadata.Keys.Cast().First()); + var handlerErrorMessage = metadata.Values.Cast().First(); + Assert.Contains(handlerErrorMessage, GetException(exception)); } [Fact] - public void OnException_WhenTracerCaptureErrorEnvironmentVariableIsTrueFalse_DoesNotCaptureError() + public void OnException_WhenTracerCaptureErrorEnvironmentVariableIsFalse_DoesNotCaptureError() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.TracerCaptureError.Returns(false); - var recorder = Substitute.For(); - var exception = new Exception("Test Exception"); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR", "false"); + // Act - void Act() => handler.OnException(eventArgs, exception); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + var exception = Record.Exception(() => + { + _handler.HandleThrowsException("My Exception"); + }); + var subSegment = segment.Subsegments[0]; + // Assert - Assert.Throws((Action)Act); - recorder.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.False(subSegment.IsMetadataAdded); // no metadata for errors added } [Fact] public void OnException_WhenTracerCaptureModeIsError_CapturesError() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - var exception = new Exception("Test Exception"); - var message = GetException(exception); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Error, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - void Act() => handler.OnException(eventArgs, exception); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + var exception = Record.Exception(() => + { + _handler.HandleWithCaptureModeError(true); + }); + var subSegment = segment.Subsegments[0]; + // Assert - Assert.Throws((Action)Act); - recorder.Received(1).AddMetadata( - Arg.Is(i => i == nameSpace), - Arg.Is(i => i == $"{methodName} error"), - Arg.Is(i => i == message) - ); + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("HandleWithCaptureModeError error", metadata.Keys.Cast().First()); + var handlerErrorMessage = metadata.Values.Cast().First(); + Assert.Contains(handlerErrorMessage, GetException(exception)); } [Fact] public void OnException_WhenTracerCaptureModeIsResponseAndError_CapturesError() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - var recorder = Substitute.For(); - var exception = new Exception("Test Exception"); - var message = GetException(exception); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.ResponseAndError, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - void Act() => handler.OnException(eventArgs, exception); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + var exception = Record.Exception(() => + { + _handler.HandleWithCaptureModeResponseAndError(true); + }); + var subSegment = segment.Subsegments[0]; + // Assert - Assert.Throws((Action)Act); - recorder.Received(1).AddMetadata( - Arg.Is(i => i == nameSpace), - Arg.Is(i => i == $"{methodName} error"), - Arg.Is(i => i == message) - ); + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("HandleWithCaptureModeResponseAndError error", metadata.Keys.Cast().First()); + var handlerErrorMessage = metadata.Values.Cast().First(); + Assert.Contains(handlerErrorMessage, GetException(exception)); } - [Fact] public void OnException_WhenTracerCaptureModeIsResponse_DoesNotCaptureError() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.TracerCaptureError.Returns(false); - var recorder = Substitute.For(); - var exception = new Exception("Test Exception"); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Response, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - void Act() => handler.OnException(eventArgs, exception); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + var exception = Record.Exception(() => + { + _handler.HandleWithCaptureModeResponse(true); + }); + var subSegment = segment.Subsegments[0]; + // Assert - Assert.Throws((Action)Act); - recorder.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.False(subSegment.IsMetadataAdded); // no metadata for errors added } [Fact] public void OnException_WhenTracerCaptureModeIsDisabled_DoesNotCaptureError() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var nameSpace = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(true); - configurations.TracingDisabled.Returns(false); - configurations.TracerCaptureError.Returns(false); - var recorder = Substitute.For(); - var exception = new Exception("Test Exception"); - - var handler = new TracingAspectHandler(null, nameSpace, TracingCaptureMode.Disabled, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + // Act - void Act() => handler.OnException(eventArgs, exception); + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + var exception = Record.Exception(() => + { + _handler.HandleWithCaptureModeDisabled(true); + }); + var subSegment = segment.Subsegments[0]; + // Assert - Assert.Throws((Action)Act); - recorder.DidNotReceive().AddMetadata( - Arg.Any(), - Arg.Any(), - Arg.Any() - ); + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.False(subSegment.IsMetadataAdded); // no metadata for errors added } #endregion @@ -760,24 +618,30 @@ static string GetException(Exception exception) public void OnExit_WhenOutsideOfLambdaEnvironment_DoesNotEndSubsegment() { // Arrange - var methodName = Guid.NewGuid().ToString(); - var configurations = Substitute.For(); - configurations.IsLambdaEnvironment.Returns(false); - configurations.TracingDisabled.Returns(true); - configurations.IsSamLocal.Returns(false); - var recorder = Substitute.For(); - - var handler = new TracingAspectHandler(null, null, TracingCaptureMode.EnvironmentVariable, - configurations, recorder); - var eventArgs = new AspectEventArgs { Name = methodName }; - + + AWSXRayRecorder.Instance.BeginSegment("foo"); + // Act - handler.OnExit(eventArgs); - + _handler.Handle(); + + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + // Assert - recorder.DidNotReceive().EndSubsegment(); + Assert.True(segment.IsInProgress); + Assert.False(segment.IsSubsegmentsAdded); + Assert.False(segment.IsAnnotationsAdded); } #endregion + + public void Dispose() + { + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_RESPONSE", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACER_CAPTURE_ERROR", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACE_DISABLED", ""); + TracingAspect.ResetForTest(); + } } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs index ce4a16a3..428c9bb5 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs @@ -54,7 +54,7 @@ public void Tracing_Instance() } [Fact] - public void Tracing_Being_Subsegment() + public void Tracing_Begin_Subsegment() { // Arrange var conf = Substitute.For(); From 308940e82d71e967df2fcf57008948e2c1d5472f Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:57:44 +0100 Subject: [PATCH 03/10] tests green. code comments --- .../Internal/TracingAspect.cs | 51 ++++++++++++++++--- .../Handlers/HandlerTests.cs | 31 +---------- .../TracingAttributeTest.cs | 12 +++-- .../XRayRecorderTests.cs | 1 + 4 files changed, 55 insertions(+), 40 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs index b7f4439b..9b57a864 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs @@ -23,24 +23,27 @@ namespace AWS.Lambda.Powertools.Tracing.Internal; +/// +/// This aspect will automatically trace all function handlers. +/// Scope.Global is singleton +/// [Aspect(Scope.Global)] public class TracingAspect { - /// /// The Powertools for AWS Lambda (.NET) configurations /// - private static readonly IPowertoolsConfigurations _powertoolsConfigurations = _powertoolsConfigurations ?? PowertoolsConfigurations.Instance; - + private IPowertoolsConfigurations _powertoolsConfigurations; + /// /// X-Ray Recorder /// - private static readonly IXRayRecorder _xRayRecorder = _xRayRecorder ?? XRayRecorder.Instance; + private IXRayRecorder _xRayRecorder; /// /// If true, then is cold start /// - private static bool _isColdStart = true; + private static bool _isColdStart; /// /// If true, capture annotations @@ -61,7 +64,31 @@ public class TracingAspect /// The capture mode /// private TracingCaptureMode _captureMode; + + /// + /// Initializes a new instance + /// + public TracingAspect() + { + _isColdStart = true; + _xRayRecorder = XRayRecorder.Instance; + _powertoolsConfigurations = PowertoolsConfigurations.Instance; + } + /// + /// the code is executed instead of the target method. + /// The call to original method is wrapped around the following code + /// the original code is called with var result = target(args); + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// [Advice(Kind.Around)] public object Around( [Argument(Source.Instance)] object instance, @@ -127,6 +154,9 @@ public object Around( } } + /// + /// the code is injected after the method ends. + /// [Advice(Kind.After)] public void OnExit() { if(TracingDisabled()) @@ -138,6 +168,11 @@ public void OnExit() { _xRayRecorder.EndSubsegment(); } + /// + /// Code that handles when exceptions occur in the client method + /// + /// + /// private void HandleException(Exception exception, string name) { if (CaptureError()) @@ -168,7 +203,7 @@ private void HandleException(Exception exception, string name) // // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: // // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later - // ExceptionDispatchInfo.Capture(exception).Throw(); + ExceptionDispatchInfo.Capture(exception).Throw(); } /// @@ -180,6 +215,10 @@ private string GetNamespace() return !string.IsNullOrWhiteSpace(_namespace) ? _namespace : _powertoolsConfigurations.Service; } + /// + /// Method that checks if tracing is disabled + /// + /// private bool TracingDisabled() { if (_powertoolsConfigurations.TracingDisabled) diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs index 5c6fb2cc..3eb5efd6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs @@ -1,13 +1,11 @@ using System; -using System.Linq; using System.Threading.Tasks; -using Amazon.XRay.Recorder.Core; -using AWS.Lambda.Powertools.Tracing.Internal; using Xunit; namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; -public sealed class HandlerTests : IDisposable +[Collection("Sequential")] +public sealed class HandlerTests { [Fact] public async Task Stack_Trace_Included_When_Decorator_Present() @@ -35,29 +33,4 @@ public async Task When_Decorator_Present_In_Generic_Method_Should_Not_Throw_When // Assert } - - [Fact] - public void When_Handler_Is_Decorated() - { - // Arrange - Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); - var handler = new Handlers(); - var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); - - // Act - handler.Handle(); - var subSegment = segment.Subsegments[0]; - - // Assert - Assert.True(segment.IsSubsegmentsAdded); - Assert.True(subSegment.IsAnnotationsAdded); - Assert.True(subSegment.Annotations.Any()); - Assert.Equal("ColdStart", subSegment.Annotations.First().Key); - Assert.Equal(true, subSegment.Annotations.First().Value); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", ""); - } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index bc31de7a..40d8421d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -145,7 +145,7 @@ public void Tracing_WhenTracerDisabled_DisablesTracing() // Warm Start Execution // Start segment var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); - Assert.Throws(() => _handler.HandleThrowsException("My Exception")); + _handler.Handle(); // Assert Assert.False(segmentCold.IsAnnotationsAdded); @@ -179,7 +179,7 @@ private static void ClearEnvironment() public class TracingAttributeLambdaEnvironmentTest { private readonly Handlers.Handlers _handler; - + public TracingAttributeLambdaEnvironmentTest() { _handler = new Handlers.Handlers(); @@ -192,21 +192,23 @@ public void Tracing_WhenOutsideOfLambdaEnv_DisablesTracing() // Need to manually create the initial segment AWSXRayRecorder.Instance.BeginSegment("foo"); - + // Act // Cold Start Execution _handler.Handle(); var segmentCold = AWSXRayRecorder.Instance.TraceContext.GetEntity(); - + // Assert Assert.False(AWSXRayRecorder.IsLambda()); Assert.False(segmentCold.IsAnnotationsAdded); Assert.Empty(segmentCold.Annotations); Assert.False(segmentCold.IsSubsegmentsAdded); Assert.False(segmentCold.IsMetadataAdded); + + AWSXRayRecorder.Instance.EndSegment(); } } - + [Collection("Sequential")] public class TracingAttributeTest : IDisposable { diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs index 428c9bb5..6e168def 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs @@ -8,6 +8,7 @@ namespace AWS.Lambda.Powertools.Tracing.Tests; +[Collection("Sequential")] public class XRayRecorderTests { [Fact] From 6c04101468e5c0de76eb67dd22d0fd7276dc680d Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Wed, 3 Jul 2024 12:44:33 +0100 Subject: [PATCH 04/10] start full example test --- .../Handlers/FullExampleHandler.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs new file mode 100644 index 00000000..05adbcdd --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs @@ -0,0 +1,25 @@ +/* + * 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 + * permissions and limitations under the License. + */ + +namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; + +public class FullExampleHandler +{ + [Tracing()] + public string[] Handle() + { + return new[] { "A", "B" }; + } +} \ No newline at end of file From dcc37054a3152cb6fce5661de96d068119621888 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:21:42 +0100 Subject: [PATCH 05/10] add test for inner exception --- .../Handlers/Handlers.cs | 8 ++++++ .../TracingAttributeTest.cs | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs index a6462e66..81f2f9b3 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs @@ -67,6 +67,14 @@ public string[] HandleWithCaptureModeError(bool exception = false) throw new Exception("Failed"); return new[] { "A", "B" }; } + + [Tracing(CaptureMode = TracingCaptureMode.Error)] + public string[] HandleWithCaptureModeErrorInner(bool exception = false) + { + if (exception) + throw new Exception("Failed", new Exception("Inner Exception!!")); + return new[] { "A", "B" }; + } [Tracing(CaptureMode = TracingCaptureMode.Disabled)] public string[] HandleWithCaptureModeDisabled(bool exception = false) diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index 40d8421d..34aa0ea3 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -514,6 +514,34 @@ public void OnException_WhenTracerCaptureModeIsError_CapturesError() var handlerErrorMessage = metadata.Values.Cast().First(); Assert.Contains(handlerErrorMessage, GetException(exception)); } + + [Fact] + public void OnException_WhenTracerCaptureModeIsError_CapturesError_Inner_Exception() + { + // Arrange + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + + // Act + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + + var exception = Record.Exception(() => + { + _handler.HandleWithCaptureModeErrorInner(true); + }); + var subSegment = segment.Subsegments[0]; + + // Assert + Assert.NotNull(exception); + Assert.True(segment.IsSubsegmentsAdded); + Assert.Single(segment.Subsegments); + Assert.True(subSegment.IsMetadataAdded); + Assert.True(subSegment.Metadata.ContainsKey("POWERTOOLS")); + var metadata = subSegment.Metadata["POWERTOOLS"]; + Assert.Equal("HandleWithCaptureModeErrorInner error", metadata.Keys.Cast().First()); + Assert.NotNull(exception.InnerException); + Assert.Equal("Inner Exception!!",exception.InnerException.Message); + } [Fact] public void OnException_WhenTracerCaptureModeIsResponseAndError_CapturesError() From 48789681fd1d8e6922cd5261d005ad2221adb641 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Thu, 4 Jul 2024 16:39:23 +0100 Subject: [PATCH 06/10] test, tracing disabled exception. remove verification on capturemode because is always false --- .../Internal/TracingAspect.cs | 3 --- .../TracingAttributeTest.cs | 23 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs index 9b57a864..edff3a16 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs @@ -242,9 +242,6 @@ private bool TracingDisabled() /// true if tracing should capture responses, false otherwise. private bool CaptureResponse() { - if(TracingDisabled()) - return false; - switch (_captureMode) { case TracingCaptureMode.EnvironmentVariable: diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index 34aa0ea3..df5aee28 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -542,6 +542,29 @@ public void OnException_WhenTracerCaptureModeIsError_CapturesError_Inner_Excepti Assert.NotNull(exception.InnerException); Assert.Equal("Inner Exception!!",exception.InnerException.Message); } + + [Fact] + public void OnException_When_Tracing_Disabled_Does_Not_CapturesError() + { + // Arrange + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACE_DISABLED", "true"); + + // Act + var segment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + + var exception = Record.Exception(() => + { + _handler.HandleWithCaptureModeError(true); + }); + + // Assert + Assert.NotNull(exception); + Assert.False(segment.IsSubsegmentsAdded); + Assert.Empty(segment.Subsegments); + Assert.False(segment.IsMetadataAdded); + } [Fact] public void OnException_WhenTracerCaptureModeIsResponseAndError_CapturesError() From 37473c8ec18889b4aaf052ebdbcbae91da776f75 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:40:58 +0100 Subject: [PATCH 07/10] add full test scenario. refactor --- ...AWS.Lambda.Powertools.Tracing.Tests.csproj | 2 + .../Handlers/FullExampleHandler.cs | 35 +++++++- .../Handlers/HandlerTests.cs | 81 ++++++++++++++++++- .../Handlers/Handlers.cs | 4 +- .../TracingAttributeTest.cs | 16 ++-- .../XRayRecorderTests.cs | 1 + 6 files changed, 123 insertions(+), 16 deletions(-) diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/AWS.Lambda.Powertools.Tracing.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/AWS.Lambda.Powertools.Tracing.Tests.csproj index ce7a88ba..7c88448a 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/AWS.Lambda.Powertools.Tracing.Tests.csproj +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/AWS.Lambda.Powertools.Tracing.Tests.csproj @@ -13,6 +13,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs index 05adbcdd..5d1e78e5 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs @@ -13,13 +13,42 @@ * permissions and limitations under the License. */ +using System.Threading.Tasks; +using Amazon.Lambda.Core; + namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; public class FullExampleHandler { - [Tracing()] - public string[] Handle() + [Tracing(Namespace = "ns", CaptureMode = TracingCaptureMode.ResponseAndError)] + public Task Handle(string text, ILambdaContext context) + { + Tracing.AddAnnotation("annotation", "value"); + BusinessLogic1().GetAwaiter().GetResult(); + + return Task.FromResult(text.ToUpper()); + } + + [Tracing(SegmentName = "First Call")] + private async Task BusinessLogic1() + { + await BusinessLogic2(); + } + + [Tracing(CaptureMode = TracingCaptureMode.Disabled)] + private async Task BusinessLogic2() + { + Tracing.AddMetadata("metadata", "value"); + + Tracing.WithSubsegment("localNamespace", "GetSomething", (subsegment) => { + GetSomething(); + }); + + await Task.FromResult(0); + } + + private void GetSomething() { - return new[] { "A", "B" }; + Tracing.AddAnnotation("getsomething", "value"); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs index 3eb5efd6..ab6c7293 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs @@ -1,12 +1,22 @@ using System; +using System.Linq; using System.Threading.Tasks; +using Amazon.Lambda.TestUtilities; +using Amazon.XRay.Recorder.Core; +using AWS.Lambda.Powertools.Tracing.Internal; +using AWS.Lambda.Powertools.Tracing.Tests.Handlers; using Xunit; -namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; +namespace AWS.Lambda.Powertools.Tracing.Tests; [Collection("Sequential")] -public sealed class HandlerTests +public sealed class HandlerTests : IDisposable { + public HandlerTests() + { + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + } + [Fact] public async Task Stack_Trace_Included_When_Decorator_Present() { @@ -19,7 +29,6 @@ public async Task Stack_Trace_Included_When_Decorator_Present() // Assert var tracedException = await Assert.ThrowsAsync(Handle); Assert.StartsWith("at AWS.Lambda.Powertools.Tracing.Tests.Handlers.ExceptionFunctionHandler.ThisThrows()", tracedException.StackTrace?.TrimStart()); - } [Fact] @@ -33,4 +42,70 @@ public async Task When_Decorator_Present_In_Generic_Method_Should_Not_Throw_When // Assert } + + [Fact] + public async Task Full_Example() + { + // Arrange + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", "AWS"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "POWERTOOLS"); + + var handler = new FullExampleHandler(); + var context = new TestLambdaContext + { + FunctionName = "FullExampleLambda", + FunctionVersion = "1", + MemoryLimitInMB = 215, + AwsRequestId = Guid.NewGuid().ToString("D") + }; + + // Act + await handler.Handle("Hello World", context); + var handleSegment = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + + // Assert + Assert.True(handleSegment.IsAnnotationsAdded); + Assert.True(handleSegment.IsSubsegmentsAdded); + + Assert.Equal("POWERTOOLS", handleSegment.Annotations["Service"]); + Assert.Equal(true, handleSegment.Annotations["ColdStart"]); + Assert.Equal("value", handleSegment.Annotations["annotation"]); + Assert.Equal("## Handle", handleSegment.Name); + + var firstCallSubsegment = handleSegment.Subsegments[0]; + + Assert.Equal("First Call", firstCallSubsegment.Name); + Assert.False(firstCallSubsegment.IsInProgress); + Assert.False(firstCallSubsegment.IsAnnotationsAdded); + // Assert.True(firstCallSubsegment.IsMetadataAdded); + Assert.True(firstCallSubsegment.IsSubsegmentsAdded); + + var businessLogicSubsegment = firstCallSubsegment.Subsegments[0]; + + Assert.Equal("## BusinessLogic2", businessLogicSubsegment.Name); + Assert.True(businessLogicSubsegment.IsMetadataAdded); + Assert.False(businessLogicSubsegment.IsInProgress); + Assert.Single(businessLogicSubsegment.Metadata); + var metadata = businessLogicSubsegment.Metadata["POWERTOOLS"]; + Assert.Contains("metadata", metadata.Keys.Cast()); + Assert.Contains("value", metadata.Values.Cast()); + Assert.True(businessLogicSubsegment.IsSubsegmentsAdded); + + var getSomethingSubsegment = businessLogicSubsegment.Subsegments[0]; + + Assert.Equal("## GetSomething", getSomethingSubsegment.Name); + Assert.Equal("localNamespace", getSomethingSubsegment.Namespace); + Assert.True(getSomethingSubsegment.IsAnnotationsAdded); + Assert.False(getSomethingSubsegment.IsSubsegmentsAdded); + Assert.False(getSomethingSubsegment.IsInProgress); + Assert.Equal("value", getSomethingSubsegment.Annotations["getsomething"]); + } + + public void Dispose() + { + Environment.SetEnvironmentVariable("LAMBDA_TASK_ROOT", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_TRACE_DISABLED", ""); + TracingAspect.ResetForTest(); + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs index 81f2f9b3..19ecc681 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs @@ -15,9 +15,9 @@ using System; -namespace AWS.Lambda.Powertools.Tracing.Tests.Handlers; +namespace AWS.Lambda.Powertools.Tracing.Tests; -public class Handlers +public class HandlerFunctions { [Tracing()] public string[] Handle() diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index df5aee28..f19afa73 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -27,11 +27,11 @@ namespace AWS.Lambda.Powertools.Tracing.Tests [Collection("Sequential")] public class TracingAttributeColdStartTest : IDisposable { - private readonly Handlers.Handlers _handler; + private readonly HandlerFunctions _handler; public TracingAttributeColdStartTest() { - _handler = new Handlers.Handlers(); + _handler = new HandlerFunctions(); } [Fact] @@ -122,11 +122,11 @@ public void Dispose() [Collection("Sequential")] public class TracingAttributeDisableTest : IDisposable { - private readonly Handlers.Handlers _handler; + private readonly HandlerFunctions _handler; public TracingAttributeDisableTest() { - _handler = new Handlers.Handlers(); + _handler = new HandlerFunctions(); } [Fact] @@ -178,11 +178,11 @@ private static void ClearEnvironment() [Collection("Sequential")] public class TracingAttributeLambdaEnvironmentTest { - private readonly Handlers.Handlers _handler; + private readonly HandlerFunctions _handler; public TracingAttributeLambdaEnvironmentTest() { - _handler = new Handlers.Handlers(); + _handler = new HandlerFunctions(); } [Fact] @@ -212,11 +212,11 @@ public void Tracing_WhenOutsideOfLambdaEnv_DisablesTracing() [Collection("Sequential")] public class TracingAttributeTest : IDisposable { - private readonly Handlers.Handlers _handler; + private readonly HandlerFunctions _handler; public TracingAttributeTest() { - _handler = new Handlers.Handlers(); + _handler = new HandlerFunctions(); } #region OnEntry Tests diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs index 6e168def..a332bbf1 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs @@ -8,6 +8,7 @@ namespace AWS.Lambda.Powertools.Tracing.Tests; +// This has to be the last tests to run otherwise it will keep state and fail other random tests [Collection("Sequential")] public class XRayRecorderTests { From fe0d02f6e38b2c47a10dafc80493a89113007409 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:59:50 +0100 Subject: [PATCH 08/10] docs AOT update --- docs/core/tracing.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/core/tracing.md b/docs/core/tracing.md index 9815b49a..aee48ffb 100644 --- a/docs/core/tracing.md +++ b/docs/core/tracing.md @@ -16,6 +16,7 @@ a provides functionality to reduce the overhead of performing common tracing tas * Better experience when developing with multiple threads. * Auto-patch supported modules by AWS X-Ray * Auto-disable when not running in AWS Lambda environment +* Ahead-of-Time compilation to native code support [AOT](https://docs.aws.amazon.com/lambda/latest/dg/dotnet-native-aot.html) from version 1.5.0 ## Installation @@ -278,3 +279,23 @@ Tracing.Register() This functionality is a thin wrapper for AWS X-Ray .NET SDK. Refer details on [how to instrument SDK client with Xray](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet-sdkclients.html) and [outgoing http calls](https://docs.aws.amazon.com/xray/latest/devguide/xray-sdk-dotnet-httpclients.html). +## AOT Support + +Native AOT trims your application code as part of the compilation to ensure that the binary is as small as possible. .NET 8 for Lambda provides improved trimming support compared to previous versions of .NET. + +These improvements offer the potential to eliminate build-time trimming warnings, but .NET will never be completely trim safe. This means that parts of libraries that your function relies on may be trimmed out as part of the compilation step. You can manage this by defining TrimmerRootAssemblies as part of your `.csproj` file as shown in the following example. + +For the Tracing utility to work correctly and without trim warnings please add the following to your `.csproj` file + +```xaml + + + + + + +``` + +Note that when you receive a trim warning, adding the class that generates the warning to TrimmerRootAssembly might not resolve the issue. A trim warning indicates that the class is trying to access some other class that can't be determined until runtime. To avoid runtime errors, add this second class to TrimmerRootAssembly. + +To learn more about managing trim warnings, see [Introduction to trim warnings](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/fixing-warnings) in the Microsoft .NET documentation. \ No newline at end of file From b0de71cff340c98e412fcfbf244ab416fbcdf9c3 Mon Sep 17 00:00:00 2001 From: Henrique Graca <999396+hjgraca@users.noreply.github.com> Date: Tue, 23 Jul 2024 12:56:52 +0100 Subject: [PATCH 09/10] Update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index ec7a6148..775a89b6 100644 --- a/version.json +++ b/version.json @@ -2,7 +2,7 @@ "Core": { "Logging": "1.5.1", "Metrics": "1.7.0", - "Tracing": "1.4.2" + "Tracing": "1.5.0" }, "Utilities": { "Parameters": "1.3.0", From 6b497b5cf1599f9413aa4fd755e2fec71ad68366 Mon Sep 17 00:00:00 2001 From: Henrique <999396+hjgraca@users.noreply.github.com> Date: Wed, 24 Jul 2024 10:13:07 +0100 Subject: [PATCH 10/10] fixing sonar issues --- .../Internal/TracingAspect.cs | 11 +++-------- .../Handlers/HandlerTests.cs | 2 +- .../TracingAttributeTest.cs | 8 ++++---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs index edff3a16..655e2229 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs @@ -33,17 +33,17 @@ public class TracingAspect /// /// The Powertools for AWS Lambda (.NET) configurations /// - private IPowertoolsConfigurations _powertoolsConfigurations; + private readonly IPowertoolsConfigurations _powertoolsConfigurations; /// /// X-Ray Recorder /// - private IXRayRecorder _xRayRecorder; + private readonly IXRayRecorder _xRayRecorder; /// /// If true, then is cold start /// - private static bool _isColdStart; + private static bool _isColdStart = true; /// /// If true, capture annotations @@ -70,7 +70,6 @@ public class TracingAspect /// public TracingAspect() { - _isColdStart = true; _xRayRecorder = XRayRecorder.Instance; _powertoolsConfigurations = PowertoolsConfigurations.Instance; } @@ -91,12 +90,8 @@ public TracingAspect() /// [Advice(Kind.Around)] public object Around( - [Argument(Source.Instance)] object instance, [Argument(Source.Name)] string name, [Argument(Source.Arguments)] object[] args, - [Argument(Source.Type)] Type hostType, - [Argument(Source.Metadata)] MethodBase method, - [Argument(Source.ReturnType)] Type returnType, [Argument(Source.Target)] Func target, [Argument(Source.Triggers)] Attribute[] triggers) { diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs index ab6c7293..29f444cb 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs @@ -68,7 +68,7 @@ public async Task Full_Example() Assert.True(handleSegment.IsSubsegmentsAdded); Assert.Equal("POWERTOOLS", handleSegment.Annotations["Service"]); - Assert.Equal(true, handleSegment.Annotations["ColdStart"]); + Assert.True((bool)handleSegment.Annotations["ColdStart"]); Assert.Equal("value", handleSegment.Annotations["annotation"]); Assert.Equal("## Handle", handleSegment.Name); diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index f19afa73..b99c41a7 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -61,7 +61,7 @@ public void OnEntry_WhenFirstCall_CapturesColdStart() Assert.Single(segmentCold.Subsegments); Assert.True(subSegmentCold.IsAnnotationsAdded); Assert.Equal(2, subSegmentCold.Annotations.Count()); - Assert.Equal(true, subSegmentCold.Annotations.Single(x => x.Key == "ColdStart").Value); + Assert.True((bool)subSegmentCold.Annotations.Single(x => x.Key == "ColdStart").Value); Assert.Equal("POWERTOOLS", subSegmentCold.Annotations.Single(x => x.Key == "Service").Value); // Warm @@ -69,7 +69,7 @@ public void OnEntry_WhenFirstCall_CapturesColdStart() Assert.Single(segmentWarm.Subsegments); Assert.True(subSegmentWarm.IsAnnotationsAdded); Assert.Equal(2, subSegmentWarm.Annotations.Count()); - Assert.Equal(false, subSegmentWarm.Annotations.Single(x => x.Key == "ColdStart").Value); + Assert.False((bool)subSegmentWarm.Annotations.Single(x => x.Key == "ColdStart").Value); Assert.Equal("POWERTOOLS", subSegmentWarm.Annotations.Single(x => x.Key == "Service").Value); } @@ -98,14 +98,14 @@ public void OnEntry_WhenFirstCall_And_Service_Not_Set_CapturesColdStart() Assert.Single(segmentCold.Subsegments); Assert.True(subSegmentCold.IsAnnotationsAdded); Assert.Single(subSegmentCold.Annotations); - Assert.Equal(true, subSegmentCold.Annotations.Single(x => x.Key == "ColdStart").Value); + Assert.True((bool)subSegmentCold.Annotations.Single(x => x.Key == "ColdStart").Value); // Warm Assert.True(segmentWarm.IsSubsegmentsAdded); Assert.Single(segmentWarm.Subsegments); Assert.True(subSegmentWarm.IsAnnotationsAdded); Assert.Single(subSegmentWarm.Annotations); - Assert.Equal(false, subSegmentWarm.Annotations.Single(x => x.Key == "ColdStart").Value); + Assert.False((bool)subSegmentWarm.Annotations.Single(x => x.Key == "ColdStart").Value); } public void Dispose()