diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs index 99d38d07..72254726 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs @@ -80,4 +80,14 @@ internal static class Constants /// Constant for LAMBDA_TASK_ROOT environment variable /// internal const string LambdaTaskRoot = "LAMBDA_TASK_ROOT"; + + /// + /// Constant for AWS_EXECUTION_ENV environment variable + /// + internal const string AwsExecutionEnvironmentVariableName = "AWS_EXECUTION_ENV"; + + /// + /// Constant for Powertools feature identifier fo AWS_EXECUTION_ENV environment variable + /// + internal const string FeatureContextIdentifier = "PT"; } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs index d9dc90cb..a2e10cc7 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs @@ -120,4 +120,10 @@ public interface IPowertoolsConfigurations /// if set to true [default value]. /// true if XXXX, false otherwise. bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue); + + /// + /// Sets the execution Environment Variable (AWS_EXECUTION_ENV) + /// + /// + void SetExecutionEnvironment(T type); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs new file mode 100644 index 00000000..059cfb7e --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs @@ -0,0 +1,37 @@ +namespace AWS.Lambda.Powertools.Common; + +/// +/// Interface for PowertoolsEnvironment +/// +public interface IPowertoolsEnvironment +{ + /// + /// Get environment variable by variable name + /// + /// + /// Environment variable + string GetEnvironmentVariable(string variableName); + + /// + /// Set environment variable + /// + /// + /// Setting this to null will remove environment variable with that name + void SetEnvironmentVariable(string variableName, string value); + + /// + /// Get the calling Type Assembly Name + /// + /// + /// + /// Assembly Name + string GetAssemblyName(T type); + + /// + /// Get the calling Type Assembly Version + /// + /// + /// + /// Assembly Version in the Major.Minor.Build format + string GetAssemblyVersion(T type); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs index 92ec9e3e..98abe1b5 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs @@ -44,4 +44,17 @@ public interface ISystemWrapper /// /// System.Double. double GetRandom(); + + /// + /// Sets the environment variable. + /// + /// The variable. + /// + void SetEnvironmentVariable(string variable, string value); + + /// + /// Sets the execution Environment Variable (AWS_EXECUTION_ENV) + /// + /// + void SetExecutionEnvironment(T type); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs index f73631d6..1d099e4d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs @@ -184,4 +184,10 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) /// true if [tracing is disabled]; otherwise, false. public bool TracingDisabled => GetEnvironmentVariableOrDefault(Constants.TracingDisabledEnv, false); + + /// + public void SetExecutionEnvironment(T type) + { + _systemWrapper.SetExecutionEnvironment(type); + } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs new file mode 100644 index 00000000..3ad5317c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs @@ -0,0 +1,43 @@ +using System; + +namespace AWS.Lambda.Powertools.Common; + +/// +public class PowertoolsEnvironment : IPowertoolsEnvironment +{ + /// + /// The instance + /// + private static IPowertoolsEnvironment _instance; + + /// + /// Gets the instance. + /// + /// The instance. + public static IPowertoolsEnvironment Instance => _instance ??= new PowertoolsEnvironment(); + + /// + public string GetEnvironmentVariable(string variableName) + { + return Environment.GetEnvironmentVariable(variableName); + } + + /// + public void SetEnvironmentVariable(string variableName, string value) + { + Environment.SetEnvironmentVariable(variableName, value); + } + + /// + public string GetAssemblyName(T type) + { + return type.GetType().Assembly.GetName().Name; + } + + /// + public string GetAssemblyVersion(T type) + { + var version = type.GetType().Assembly.GetName().Version; + return version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : string.Empty; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs index f6906a60..703a0865 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs @@ -14,6 +14,7 @@ */ using System; +using System.Text; namespace AWS.Lambda.Powertools.Common; @@ -24,6 +25,8 @@ namespace AWS.Lambda.Powertools.Common; /// public class SystemWrapper : ISystemWrapper { + private static IPowertoolsEnvironment _powertoolsEnvironment; + /// /// The instance /// @@ -32,15 +35,17 @@ public class SystemWrapper : ISystemWrapper /// /// Prevents a default instance of the class from being created. /// - private SystemWrapper() + public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment) { + _powertoolsEnvironment = powertoolsEnvironment; + _instance ??= this; } /// /// Gets the instance. /// /// The instance. - public static ISystemWrapper Instance => _instance ??= new SystemWrapper(); + public static ISystemWrapper Instance => _instance ??= new SystemWrapper(PowertoolsEnvironment.Instance); /// /// Gets the environment variable. @@ -49,7 +54,7 @@ private SystemWrapper() /// System.String. public string GetEnvironmentVariable(string variable) { - return Environment.GetEnvironmentVariable(variable); + return _powertoolsEnvironment.GetEnvironmentVariable(variable); } /// @@ -78,4 +83,57 @@ public double GetRandom() { return new Random().NextDouble(); } + + /// + public void SetEnvironmentVariable(string variable, string value) + { + _powertoolsEnvironment.SetEnvironmentVariable(variable, value); + } + + /// + public void SetExecutionEnvironment(T type) + { + const string envName = Constants.AwsExecutionEnvironmentVariableName; + var envValue = new StringBuilder(); + var currentEnvValue = GetEnvironmentVariable(envName); + var assemblyName = ParseAssemblyName(_powertoolsEnvironment.GetAssemblyName(type)); + + // If there is an existing execution environment variable add the annotations package as a suffix. + if(!string.IsNullOrEmpty(currentEnvValue)) + { + // Avoid duplication - should not happen since the calling Instances are Singletons - defensive purposes + if (currentEnvValue.Contains(assemblyName)) + { + return; + } + + envValue.Append($"{currentEnvValue} "); + } + + var assemblyVersion = _powertoolsEnvironment.GetAssemblyVersion(type); + + envValue.Append($"{assemblyName}/{assemblyVersion}"); + + SetEnvironmentVariable(envName, envValue.ToString()); + } + + /// + /// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version) + /// Fallback to Assembly Name on exception + /// + /// + /// + private string ParseAssemblyName(string assemblyName) + { + try + { + var parsedName = assemblyName.Substring(assemblyName.LastIndexOf(".", StringComparison.Ordinal)+1); + return $"{Constants.FeatureContextIdentifier}/{parsedName}"; + } + catch + { + //NOOP + } + return $"{Constants.FeatureContextIdentifier}/{assemblyName}"; + } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs index c45a7515..281fbcc6 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs @@ -76,6 +76,8 @@ public PowertoolsLogger( { (_name, _powertoolsConfigurations, _systemWrapper, _getCurrentConfig) = (name, powertoolsConfigurations, systemWrapper, getCurrentConfig); + + _powertoolsConfigurations.SetExecutionEnvironment(this); } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index 2bbc0fa2..24264e0e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -71,6 +71,8 @@ internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string name _raiseOnEmptyMetrics = raiseOnEmptyMetrics; _captureColdStartEnabled = captureColdStartEnabled; _context = InitializeContext(nameSpace, service, null); + + _powertoolsConfigurations.SetExecutionEnvironment(this); } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs index 89efe413..facfd39c 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs @@ -18,6 +18,7 @@ using Amazon.XRay.Recorder.Core.Internal.Emitters; using Amazon.XRay.Recorder.Core.Internal.Entities; using Amazon.XRay.Recorder.Core.Strategies; +using AWS.Lambda.Powertools.Common; namespace AWS.Lambda.Powertools.Tracing.Internal; @@ -28,6 +29,9 @@ namespace AWS.Lambda.Powertools.Tracing.Internal; /// internal class XRayRecorder : IXRayRecorder { + private static IAWSXRayRecorder _awsxRayRecorder; + private static IPowertoolsConfigurations _powertoolsConfigurations; + /// /// The instance /// @@ -37,25 +41,34 @@ internal class XRayRecorder : IXRayRecorder /// Gets the instance. /// /// The instance. - public static IXRayRecorder Instance => _instance ??= new XRayRecorder(); + public static IXRayRecorder Instance => _instance ??= new XRayRecorder(AWSXRayRecorder.Instance, PowertoolsConfigurations.Instance); + + public XRayRecorder(IAWSXRayRecorder awsxRayRecorder, IPowertoolsConfigurations powertoolsConfigurations) + { + _instance = this; + _powertoolsConfigurations = powertoolsConfigurations; + _powertoolsConfigurations.SetExecutionEnvironment(this); + _isLambda = _powertoolsConfigurations.IsLambdaEnvironment; + _awsxRayRecorder = awsxRayRecorder; + } /// /// Checks whether current execution is in AWS Lambda. /// /// Returns true if current execution is in AWS Lambda. - private static readonly bool _isLambda = AWSXRayRecorder.IsLambda(); + private static bool _isLambda; /// /// Gets the emitter. /// /// The emitter. - public ISegmentEmitter Emitter => _isLambda ? AWSXRayRecorder.Instance.Emitter : null; + public ISegmentEmitter Emitter => _isLambda ? _awsxRayRecorder.Emitter : null; /// /// Gets the streaming strategy. /// /// The streaming strategy. - public IStreamingStrategy StreamingStrategy => _isLambda ? AWSXRayRecorder.Instance.StreamingStrategy : null; + public IStreamingStrategy StreamingStrategy => _isLambda ? _awsxRayRecorder.StreamingStrategy : null; /// /// Begins the subsegment. @@ -64,7 +77,7 @@ internal class XRayRecorder : IXRayRecorder public void BeginSubsegment(string name) { if (_isLambda) - AWSXRayRecorder.Instance.BeginSubsegment(name); + _awsxRayRecorder.BeginSubsegment(name); } /// @@ -74,7 +87,7 @@ public void BeginSubsegment(string name) public void SetNamespace(string value) { if (_isLambda) - AWSXRayRecorder.Instance.SetNamespace(value); + _awsxRayRecorder.SetNamespace(value); } /// @@ -85,7 +98,7 @@ public void SetNamespace(string value) public void AddAnnotation(string key, object value) { if (_isLambda) - AWSXRayRecorder.Instance.AddAnnotation(key, value); + _awsxRayRecorder.AddAnnotation(key, value); } /// @@ -97,7 +110,7 @@ public void AddAnnotation(string key, object value) public void AddMetadata(string nameSpace, string key, object value) { if (_isLambda) - AWSXRayRecorder.Instance.AddMetadata(nameSpace, key, value); + _awsxRayRecorder.AddMetadata(nameSpace, key, value); } /// @@ -106,7 +119,7 @@ public void AddMetadata(string nameSpace, string key, object value) public void EndSubsegment() { if (_isLambda) - AWSXRayRecorder.Instance.EndSubsegment(); + _awsxRayRecorder.EndSubsegment(); } /// @@ -116,7 +129,7 @@ public void EndSubsegment() public Entity GetEntity() { return _isLambda - ? AWSXRayRecorder.Instance.GetEntity() + ? _awsxRayRecorder.TraceContext.GetEntity() : new Subsegment("Root"); } @@ -127,7 +140,7 @@ public Entity GetEntity() public void SetEntity(Entity entity) { if (_isLambda) - AWSXRayRecorder.Instance.SetEntity(entity); + _awsxRayRecorder.TraceContext.SetEntity(entity); } /// @@ -137,7 +150,7 @@ public void SetEntity(Entity entity) public void AddException(Exception exception) { if (_isLambda) - AWSXRayRecorder.Instance.AddException(exception); + _awsxRayRecorder.AddException(exception); } /// @@ -148,6 +161,6 @@ public void AddException(Exception exception) public void AddHttpInformation(string key, object value) { if (_isLambda) - AWSXRayRecorder.Instance.AddHttpInformation(key, value); + _awsxRayRecorder.AddHttpInformation(key, value); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs index 54b6b6c5..7ed9453b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs @@ -639,6 +639,30 @@ public void IsLambdaEnvironment_WhenEnvironmentHasValue_ReturnsTrue() Assert.True(result); } + [Fact] + public void Set_Lambda_Execution_Context() + { + // Arrange + var systemWrapper = new Mock(); + + // systemWrapper.Setup(c => + // c.SetExecutionEnvironment(GetType()) + // ); + + var configurations = new PowertoolsConfigurations(systemWrapper.Object); + + // Act + configurations.SetExecutionEnvironment(typeof(PowertoolsConfigurations)); + + // Assert + // method with correct type was called + systemWrapper.Verify(v => + v.SetExecutionEnvironment( + It.Is(i => i == typeof(PowertoolsConfigurations)) + ), Times.Once); + + } + #endregion } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs new file mode 100644 index 00000000..8bad4d41 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Moq; +using Xunit; + +namespace AWS.Lambda.Powertools.Common.Tests; + +public class PowertoolsEnvironmentTest : IDisposable +{ + [Fact] + public void Set_Execution_Environment() + { + // Arrange + var systemWrapper = new SystemWrapper(new MockEnvironment()); + + // Act + systemWrapper.SetExecutionEnvironment(this); + + // Assert + Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] + public void Set_Execution_Environment_WhenEnvironmentHasValue() + { + // Arrange + var systemWrapper = new SystemWrapper(new MockEnvironment()); + + systemWrapper.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent"); + + // Act + systemWrapper.SetExecutionEnvironment(this); + + // Assert + Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] + public void Set_Multiple_Execution_Environment() + { + // Arrange + var systemWrapper = new SystemWrapper(new MockEnvironment()); + + // Act + systemWrapper.SetExecutionEnvironment(this); + + // Assert + Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] + public void Set_Execution_Real_Environment() + { + // Arrange + var systemWrapper = new SystemWrapper(new PowertoolsEnvironment()); + + // Act + systemWrapper.SetExecutionEnvironment(this); + + // Assert + Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] + public void Set_Execution_Real_Environment_Multiple() + { + // Arrange + var systemWrapper = new SystemWrapper(new PowertoolsEnvironment()); + + // Act + systemWrapper.SetExecutionEnvironment(this); + systemWrapper.SetExecutionEnvironment(systemWrapper); + + // Assert + Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 {Constants.FeatureContextIdentifier}/Common/0.0.1", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] + public void Set_Execution_Real_Environment_Multiple_Avoid_Duplicate() + { + // Arrange + var systemWrapper = new SystemWrapper(new PowertoolsEnvironment()); + + // Act + systemWrapper.SetExecutionEnvironment(this); + systemWrapper.SetExecutionEnvironment(this); + + // Assert + Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + public void Dispose() + { + //Do cleanup actions here + + Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); + } +} + +/// +/// Fake Environment for testing +/// +class MockEnvironment : IPowertoolsEnvironment +{ + private readonly Dictionary _mockEnvironment = new(); + + public string GetEnvironmentVariable(string variableName) + { + return _mockEnvironment.TryGetValue(variableName, out var value) ? value : null; + } + + public void SetEnvironmentVariable(string variableName, string value) + { + // Check for entry not existing and add to dictionary + _mockEnvironment[variableName] = value; + } + + public string GetAssemblyName(T type) + { + return "AWS.Lambda.Powertools.Fake"; + } + + public string GetAssemblyVersion(T type) + { + return "1.0.0"; + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs index d962ba2f..f0ec6d01 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs @@ -1220,5 +1220,42 @@ public void Log_WhenMemoryStream_LogsBase64String_UnsafeRelaxedJsonEscaping() ) ), Times.Once); } + + [Fact] + public void Log_Set_Execution_Environment_Context() + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var assemblyName = "AWS.Lambda.Powertools.Logger"; + var assemblyVersion = "1.0.0"; + + var env = new Mock(); + env.Setup(x => x.GetAssemblyName(It.IsAny())).Returns(assemblyName); + env.Setup(x => x.GetAssemblyVersion(It.IsAny())).Returns(assemblyVersion); + + // Act + + var wrapper = new SystemWrapper(env.Object); + var conf = new PowertoolsConfigurations(wrapper); + + var logger = new PowertoolsLogger(loggerName,conf, wrapper, () => + new LoggerConfiguration + { + Service = null, + MinimumLevel = null + }); + logger.LogInformation("Test"); + + // Assert + env.Verify(v => + v.SetEnvironmentVariable( + "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Logger/{assemblyVersion}" + ), Times.Once); + + env.Verify(v => + v.GetEnvironmentVariable( + "AWS_EXECUTION_ENV" + ), Times.Once); + } } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs new file mode 100644 index 00000000..531b1716 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs @@ -0,0 +1,36 @@ +using AWS.Lambda.Powertools.Common; +using Moq; +using Xunit; + +namespace AWS.Lambda.Powertools.Metrics.Tests; + +[Collection("Sequential")] +public class MetricsTests +{ + [Fact] + public void Metrics_Set_Execution_Environment_Context() + { + // Arrange + var assemblyName = "AWS.Lambda.Powertools.Metrics"; + var assemblyVersion = "1.0.0"; + + var env = new Mock(); + env.Setup(x => x.GetAssemblyName(It.IsAny())).Returns(assemblyName); + env.Setup(x => x.GetAssemblyVersion(It.IsAny())).Returns(assemblyVersion); + + var conf = new PowertoolsConfigurations(new SystemWrapper(env.Object)); + + var metrics = new Metrics(conf); + + // Assert + env.Verify(v => + v.SetEnvironmentVariable( + "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Metrics/{assemblyVersion}" + ), Times.Once); + + env.Verify(v => + v.GetEnvironmentVariable( + "AWS_EXECUTION_ENV" + ), Times.Once); + } +} \ 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 new file mode 100644 index 00000000..4d45c2c1 --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs @@ -0,0 +1,306 @@ +using System; +using Amazon.XRay.Recorder.Core; +using Amazon.XRay.Recorder.Core.Internal.Entities; +using AWS.Lambda.Powertools.Common; +using AWS.Lambda.Powertools.Tracing.Internal; +using Moq; +using Xunit; + +namespace AWS.Lambda.Powertools.Tracing.Tests; + +public class XRayRecorderTests +{ + [Fact] + public void Tracing_Set_Execution_Environment_Context() + { + // Arrange + var assemblyName = "AWS.Lambda.Powertools.Tracing"; + var assemblyVersion = "1.0.0"; + + var env = new Mock(); + env.Setup(x => x.GetAssemblyName(It.IsAny())).Returns(assemblyName); + env.Setup(x => x.GetAssemblyVersion(It.IsAny())).Returns(assemblyVersion); + + var conf = new PowertoolsConfigurations(new SystemWrapper(env.Object)); + var awsXray = new Mock(); + + // Act + var xRayRecorder = new XRayRecorder(awsXray.Object, conf); + + // Assert + env.Verify(v => + v.SetEnvironmentVariable( + "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Tracing/{assemblyVersion}" + ), Times.Once); + + env.Verify(v => + v.GetEnvironmentVariable( + "AWS_EXECUTION_ENV" + ), Times.Once); + + Assert.NotNull(xRayRecorder); + } + + [Fact] + public void Tracing_Instance() + { + // Arrange + var conf = new Mock(); + var awsXray = new Mock(); + + // Act + + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + // Assert + Assert.Equal(tracing, XRayRecorder.Instance); + } + + [Fact] + public void Tracing_Being_Subsegment() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var awsXray = new Mock(); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.BeginSubsegment("test"); + + // Assert + awsXray.Verify(v => + v.BeginSubsegment("test", null + ), Times.Once); + } + + [Fact] + public void Tracing_Set_Namespace() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var awsXray = new Mock(); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.SetNamespace("test"); + + // Assert + awsXray.Verify(v => + v.SetNamespace("test" + ), Times.Once); + } + + [Fact] + public void Tracing_Add_Annotation() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var awsXray = new Mock(); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.AddAnnotation("key", "value"); + + // Assert + awsXray.Verify(v => + v.AddAnnotation("key", "value" + ), Times.Once); + } + + [Fact] + public void Tracing_Add_Metadata() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var awsXray = new Mock(); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.AddMetadata("nameSpace","key", "value"); + + // Assert + awsXray.Verify(v => + v.AddMetadata("nameSpace","key", "value" + ), Times.Once); + } + + [Fact] + public void Tracing_End_Subsegment() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var awsXray = new Mock(); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.EndSubsegment(); + + // Assert + awsXray.Verify(v => + v.EndSubsegment(null), Times.Once); + } + + [Fact] + public void Tracing_Get_Entity_In_Lambda_Environment() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var awsXray = new Mock(); + awsXray.Setup(x => x.TraceContext.GetEntity()).Returns(new Subsegment("root")); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.GetEntity(); + + // Assert + awsXray.Verify(v => + v.TraceContext.GetEntity(), Times.Once); + } + + [Fact] + public void Tracing_Get_Entity_Outside_Lambda_Environment() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(false); + + var awsXray = new Mock(); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + var entity = tracing.GetEntity(); + + // Assert + Assert.Equivalent("Root",entity.Name); + } + + [Fact] + public void Tracing_Set_Entity() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var segment = new Segment("test"); + + var awsXray = new Mock(); + awsXray.Setup(x => x.TraceContext.SetEntity(segment)); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.SetEntity(segment); + + // Assert + awsXray.Verify(v => + v.TraceContext.SetEntity(segment), Times.Once); + } + + [Fact] + public void Tracing_Add_Exception() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var ex = new ArgumentException("test"); + + var awsXray = new Mock(); + awsXray.Setup(x => x.AddException(ex)); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.AddException(ex); + + // Assert + awsXray.Verify(v => + v.AddException(ex), Times.Once); + } + + [Fact] + public void Tracing_Add_Http_Information() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(true); + + var key = "key"; + var value = "value"; + + var awsXray = new Mock(); + awsXray.Setup(x => x.AddHttpInformation(key,value)); + + // Act + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + tracing.AddHttpInformation(key,value); + + // Assert + awsXray.Verify(v => + v.AddHttpInformation(key,value), Times.Once); + } + + [Fact] + public void Tracing_All_When_Outside_Lambda() + { + // Arrange + var conf = new Mock(); + conf.Setup(c => c.IsLambdaEnvironment).Returns(false); + + var awsXray = new Mock(); + var tracing = new XRayRecorder(awsXray.Object, conf.Object); + + // Act + + tracing.AddHttpInformation(It.IsAny(),It.IsAny()); + tracing.AddException(It.IsAny()); + tracing.SetEntity(It.IsAny()); + tracing.EndSubsegment(); + tracing.AddMetadata(It.IsAny(),It.IsAny(), It.IsAny()); + tracing.AddAnnotation(It.IsAny(),It.IsAny()); + tracing.SetNamespace(It.IsAny()); + tracing.BeginSubsegment(It.IsAny()); + + // Assert + awsXray.Verify(v => + v.AddHttpInformation(It.IsAny(),It.IsAny()), Times.Never); + awsXray.Verify(v => + v.AddException(It.IsAny()), Times.Never); + awsXray.Verify(v => + v.TraceContext.SetEntity(It.IsAny()), Times.Never); + awsXray.Verify(v => + v.EndSubsegment(null), Times.Never); + awsXray.Verify(v => + v.AddMetadata(It.IsAny(),It.IsAny(), It.IsAny() + ), Times.Never); + awsXray.Verify(v => + v.AddAnnotation(It.IsAny(),It.IsAny() + ), Times.Never); + awsXray.Verify(v => + v.SetNamespace(It.IsAny() + ), Times.Never); + awsXray.Verify(v => + v.BeginSubsegment(It.IsAny(), null + ), Times.Never); + } +} \ No newline at end of file