diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
deleted file mode 100644
index a873dcfb..00000000
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.IO;
-
-namespace AWS.Lambda.Powertools.Common;
-
-///
-/// Interface ISystemWrapper
-///
-public interface ISystemWrapper
-{
- ///
- /// Gets the environment variable.
- ///
- /// The variable.
- /// System.String.
- string GetEnvironmentVariable(string variable);
-
- ///
- /// Logs the specified value.
- ///
- /// The value.
- void Log(string value);
-
- ///
- /// Logs the line.
- ///
- /// The value.
- void LogLine(string value);
-
- ///
- /// Gets random number
- ///
- /// 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/PowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs
index 649418a4..afc796b6 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Concurrent;
using System.Text;
namespace AWS.Lambda.Powertools.Common;
@@ -11,6 +12,16 @@ public class PowertoolsEnvironment : IPowertoolsEnvironment
///
private static IPowertoolsEnvironment _instance;
+ ///
+ /// Cached runtime environment string
+ ///
+ private static readonly string CachedRuntimeEnvironment = $"PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}";
+
+ ///
+ /// Cache for parsed assembly names to avoid repeated string operations
+ ///
+ private static readonly ConcurrentDictionary ParsedAssemblyNameCache = new();
+
///
/// Gets the instance.
///
@@ -32,13 +43,28 @@ public void SetEnvironmentVariable(string variableName, string value)
///
public string GetAssemblyName(T type)
{
+ if (type is Type typeObject)
+ {
+ return typeObject.Assembly.GetName().Name;
+ }
+
return type.GetType().Assembly.GetName().Name;
}
///
public string GetAssemblyVersion(T type)
{
- var version = type.GetType().Assembly.GetName().Version;
+ Version version;
+
+ if (type is Type typeObject)
+ {
+ version = typeObject.Assembly.GetName().Version;
+ }
+ else
+ {
+ version = type.GetType().Assembly.GetName().Version;
+ }
+
return version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : string.Empty;
}
@@ -46,27 +72,43 @@ public string GetAssemblyVersion(T type)
public void SetExecutionEnvironment(T type)
{
const string envName = Constants.AwsExecutionEnvironmentVariableName;
- var envValue = new StringBuilder();
var currentEnvValue = GetEnvironmentVariable(envName);
var assemblyName = ParseAssemblyName(GetAssemblyName(type));
- // If there is an existing execution environment variable add the annotations package as a suffix.
- if (!string.IsNullOrEmpty(currentEnvValue))
+ // Check for duplication early
+ if (!string.IsNullOrEmpty(currentEnvValue) && currentEnvValue.Contains(assemblyName))
{
- // Avoid duplication - should not happen since the calling Instances are Singletons - defensive purposes
- if (currentEnvValue.Contains(assemblyName))
- {
- return;
- }
-
- envValue.Append($"{currentEnvValue} ");
+ return;
}
var assemblyVersion = GetAssemblyVersion(type);
+ var newEntry = $"{assemblyName}/{assemblyVersion}";
+
+ string finalValue;
+
+ if (string.IsNullOrEmpty(currentEnvValue))
+ {
+ // First entry: "PT/Assembly/1.0.0 PTENV/AWS_LAMBDA_DOTNET8"
+ finalValue = $"{newEntry} {CachedRuntimeEnvironment}";
+ }
+ else
+ {
+ // Check if PTENV already exists in one pass
+ var containsPtenv = currentEnvValue.Contains("PTENV/");
+
+ if (containsPtenv)
+ {
+ // Just append the new entry: "existing PT/Assembly/1.0.0"
+ finalValue = $"{currentEnvValue} {newEntry}";
+ }
+ else
+ {
+ // Append new entry + PTENV: "existing PT/Assembly/1.0.0 PTENV/AWS_LAMBDA_DOTNET8"
+ finalValue = $"{currentEnvValue} {newEntry} {CachedRuntimeEnvironment}";
+ }
+ }
- envValue.Append($"{assemblyName}/{assemblyVersion}");
-
- SetEnvironmentVariable(envName, envValue.ToString());
+ SetEnvironmentVariable(envName, finalValue);
}
///
@@ -75,18 +117,26 @@ public void SetExecutionEnvironment(T type)
///
///
///
- private string ParseAssemblyName(string assemblyName)
+ internal static string ParseAssemblyName(string assemblyName)
{
+ // Use cache to avoid repeated string operations
try
{
- var parsedName = assemblyName.Substring(assemblyName.LastIndexOf(".", StringComparison.Ordinal) + 1);
- return $"{Constants.FeatureContextIdentifier}/{parsedName}";
+ return ParsedAssemblyNameCache.GetOrAdd(assemblyName, name =>
+ {
+ var lastDotIndex = name.LastIndexOf('.');
+ if (lastDotIndex >= 0 && lastDotIndex < name.Length - 1)
+ {
+ var parsedName = name.Substring(lastDotIndex + 1);
+ return $"{Constants.FeatureContextIdentifier}/{parsedName}";
+ }
+
+ return $"{Constants.FeatureContextIdentifier}/{name}";
+ });
}
catch
{
- //NOOP
+ return string.Empty;
}
-
- return $"{Constants.FeatureContextIdentifier}/{assemblyName}";
}
-}
\ 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
deleted file mode 100644
index faf71eeb..00000000
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using System;
-using System.IO;
-using System.Text;
-
-namespace AWS.Lambda.Powertools.Common;
-
-///
-/// Class SystemWrapper.
-/// Implements the
-///
-///
-public class SystemWrapper : ISystemWrapper
-{
- private static IPowertoolsEnvironment _powertoolsEnvironment;
- private static bool _inTestMode = false;
- private static TextWriter _testOutputStream;
- private static bool _outputResetPerformed = false;
-
- ///
- /// The instance
- ///
- private static ISystemWrapper _instance;
-
- ///
- /// Prevents a default instance of the class from being created.
- ///
- public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
- {
- _powertoolsEnvironment = powertoolsEnvironment;
- _instance ??= this;
-
- if (!_inTestMode)
- {
- // Clear AWS SDK Console injected parameters in production only
- ResetConsoleOutput();
- }
- }
-
- ///
- /// Gets the instance.
- ///
- /// The instance.
- public static ISystemWrapper Instance => _instance ??= new SystemWrapper(PowertoolsEnvironment.Instance);
-
- ///
- /// Gets the environment variable.
- ///
- /// The variable.
- /// System.String.
- public string GetEnvironmentVariable(string variable)
- {
- return _powertoolsEnvironment.GetEnvironmentVariable(variable);
- }
-
- ///
- /// Logs the specified value.
- ///
- /// The value.
- public void Log(string value)
- {
- if (_inTestMode && _testOutputStream != null)
- {
- _testOutputStream.Write(value);
- }
- else
- {
- EnsureConsoleOutputOnce();
- Console.Write(value);
- }
- }
-
- ///
- /// Logs the line.
- ///
- /// The value.
- public void LogLine(string value)
- {
- if (_inTestMode && _testOutputStream != null)
- {
- _testOutputStream.WriteLine(value);
- }
- else
- {
- EnsureConsoleOutputOnce();
- Console.WriteLine(value);
- }
- }
-
- ///
- /// Gets random number
- ///
- /// System.Double.
- 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());
- }
-
- ///
- /// Sets console output
- /// Useful for testing and checking the console output
- ///
- /// var consoleOut = new StringWriter();
- /// SystemWrapper.Instance.SetOut(consoleOut);
- ///
- ///
- /// The TextWriter instance where to write to
-
- public static void SetOut(TextWriter writeTo)
- {
- _testOutputStream = writeTo;
- _inTestMode = true;
- Console.SetOut(writeTo);
- }
-
- ///
- /// 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}";
- }
-
- private static void EnsureConsoleOutputOnce()
- {
- if (_outputResetPerformed) return;
- ResetConsoleOutput();
- _outputResetPerformed = true;
- }
-
- private static void ResetConsoleOutput()
- {
- var standardOutput = new StreamWriter(Console.OpenStandardOutput());
- standardOutput.AutoFlush = true;
- Console.SetOut(standardOutput);
- var errorOutput = new StreamWriter(Console.OpenStandardError());
- errorOutput.AutoFlush = true;
- Console.SetError(errorOutput);
- }
-
- public static void ClearOutputResetFlag()
- {
- _outputResetPerformed = false;
- }
-
- // For test cleanup
- internal static void ResetTestMode()
- {
- _inTestMode = false;
- _testOutputStream = null;
- }
-}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs
index 8952948e..4107a1b9 100644
--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs
+++ b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs
@@ -40,7 +40,7 @@ private readonly
public BedrockAgentFunctionResolver(IJsonTypeInfoResolver? typeResolver = null)
{
_parameterMapper = new ParameterMapper(typeResolver);
- SystemWrapper.Instance.SetExecutionEnvironment(this);
+ PowertoolsEnvironment.Instance.SetExecutionEnvironment(this);
}
///
diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs
index afbf077d..09356b64 100644
--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs
+++ b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs
@@ -21,7 +21,7 @@ public AppSyncEventsResolver()
{
_publishRoutes = new RouteHandlerRegistry();
_subscribeRoutes = new RouteHandlerRegistry();
- SystemWrapper.Instance.SetExecutionEnvironment(this);
+ PowertoolsEnvironment.Instance.SetExecutionEnvironment(this);
}
#region OnPublish Methods
diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs
index 4c5b02ee..72b0fef3 100644
--- a/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs
@@ -78,7 +78,7 @@ protected PowertoolsKafkaSerializerBase(JsonSerializerOptions jsonOptions, JsonS
JsonOptions = jsonOptions ?? new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
SerializerContext = serializerContext;
- SystemWrapper.Instance.SetExecutionEnvironment(this);
+ PowertoolsEnvironment.Instance.SetExecutionEnvironment(this);
}
///
diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs
index 936432ac..9f9e153c 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs
@@ -1,9 +1,9 @@
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;
+using NSubstitute;
using Xunit;
namespace AWS.Lambda.Powertools.Common.Tests;
@@ -14,54 +14,57 @@ public class PowertoolsEnvironmentTest : IDisposable
public void Set_Execution_Environment()
{
// Arrange
- var systemWrapper = new SystemWrapper(new MockEnvironment());
+ var powertoolsEnv = new PowertoolsEnvironment();
// Act
- systemWrapper.SetExecutionEnvironment(this);
+ powertoolsEnv.SetExecutionEnvironment(this);
// Assert
- Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
+ Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
}
[Fact]
public void Set_Execution_Environment_WhenEnvironmentHasValue()
{
// Arrange
- var systemWrapper = new SystemWrapper(new MockEnvironment());
+ var powertoolsEnv = new PowertoolsEnvironment();
- systemWrapper.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent");
+ powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent");
// Act
- systemWrapper.SetExecutionEnvironment(this);
+ powertoolsEnv.SetExecutionEnvironment(this);
// Assert
- Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
+ Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
}
[Fact]
- public void Set_Multiple_Execution_Environment()
+ public void Set_Same_Execution_Environment_Multiple_Times_Should_Only_Set_Once()
{
// Arrange
- var systemWrapper = new SystemWrapper(new MockEnvironment());
+ var powertoolsEnv = new PowertoolsEnvironment();
// Act
- systemWrapper.SetExecutionEnvironment(this);
+ powertoolsEnv.SetExecutionEnvironment(this);
+ powertoolsEnv.SetExecutionEnvironment(this);
// Assert
- Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
+ Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
}
[Fact]
- public void Set_Execution_Real_Environment()
+ public void Set_Multiple_Execution_Environment()
{
// Arrange
- var systemWrapper = new SystemWrapper(new PowertoolsEnvironment());
+ var powertoolsEnv = new PowertoolsEnvironment();
// Act
- systemWrapper.SetExecutionEnvironment(this);
+ powertoolsEnv.SetExecutionEnvironment(this);
+ powertoolsEnv.SetExecutionEnvironment(powertoolsEnv.GetType());
// Assert
- Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
+ Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major} {Constants.FeatureContextIdentifier}/Common/1.0.0",
+ powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"));
}
[Fact]
@@ -83,44 +86,251 @@ public void Should_Use_Aspect_Injector_281()
Assert.Equal("2.8.1", packageReference.Version.ToString());
}
- public void Dispose()
+ [Fact]
+ public void SetExecutionEnvironment_Should_Format_Strings_Correctly_With_Mocked_Environment()
{
- //Do cleanup actions here
+ // Arrange
+ var mockEnvironment = Substitute.For();
+
+ // Mock the dependencies to return controlled values
+ mockEnvironment.GetAssemblyName(Arg.Any