diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
index 8a035984..a873dcfb 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
@@ -59,15 +59,4 @@ public interface ISystemWrapper
///
///
void SetExecutionEnvironment(T type);
-
- ///
- /// 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
- void SetOut(TextWriter writeTo);
}
\ 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 8f42bda4..cec85233 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
@@ -27,6 +27,9 @@ namespace AWS.Lambda.Powertools.Common;
public class SystemWrapper : ISystemWrapper
{
private static IPowertoolsEnvironment _powertoolsEnvironment;
+ private static bool _inTestMode = false;
+ private static TextWriter _testOutputStream;
+ private static bool _outputResetPerformed = false;
///
/// The instance
@@ -41,13 +44,11 @@ public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
_powertoolsEnvironment = powertoolsEnvironment;
_instance ??= this;
- // Clear AWS SDK Console injected parameters StdOut and StdErr
- var standardOutput = new StreamWriter(Console.OpenStandardOutput());
- standardOutput.AutoFlush = true;
- Console.SetOut(standardOutput);
- var errordOutput = new StreamWriter(Console.OpenStandardError());
- errordOutput.AutoFlush = true;
- Console.SetError(errordOutput);
+ if (!_inTestMode)
+ {
+ // Clear AWS SDK Console injected parameters in production only
+ ResetConsoleOutput();
+ }
}
///
@@ -72,7 +73,15 @@ public string GetEnvironmentVariable(string variable)
/// The value.
public void Log(string value)
{
- Console.Write(value);
+ if (_inTestMode && _testOutputStream != null)
+ {
+ _testOutputStream.Write(value);
+ }
+ else
+ {
+ EnsureConsoleOutputOnce();
+ Console.Write(value);
+ }
}
///
@@ -81,7 +90,15 @@ public void Log(string value)
/// The value.
public void LogLine(string value)
{
- Console.WriteLine(value);
+ if (_inTestMode && _testOutputStream != null)
+ {
+ _testOutputStream.WriteLine(value);
+ }
+ else
+ {
+ EnsureConsoleOutputOnce();
+ Console.WriteLine(value);
+ }
}
///
@@ -126,9 +143,20 @@ public void SetExecutionEnvironment(T type)
SetEnvironmentVariable(envName, envValue.ToString());
}
- ///
- public void SetOut(TextWriter writeTo)
+ ///
+ /// 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);
}
@@ -152,4 +180,33 @@ private string ParseAssemblyName(string assemblyName)
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/tests/AWS.Lambda.Powertools.Common.Tests/Core/SystemWrapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/SystemWrapperTests.cs
new file mode 100644
index 00000000..ff5a7fb0
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/SystemWrapperTests.cs
@@ -0,0 +1,204 @@
+using System;
+using System.IO;
+using System.Reflection;
+using NSubstitute;
+using Xunit;
+
+namespace AWS.Lambda.Powertools.Common.Tests;
+
+[Collection("Sequential")]
+public class SystemWrapperTests : IDisposable
+{
+ private readonly IPowertoolsEnvironment _mockEnvironment;
+ private readonly StringWriter _testWriter;
+ private readonly FieldInfo _outputResetPerformedField;
+
+
+ public SystemWrapperTests()
+ {
+ _mockEnvironment = Substitute.For();
+ _testWriter = new StringWriter();
+
+ // Get access to private field for testing
+ _outputResetPerformedField = typeof(SystemWrapper).GetField("_outputResetPerformed",
+ BindingFlags.NonPublic | BindingFlags.Static);
+
+ // Reset static state between tests
+ SystemWrapper.ResetTestMode();
+ _outputResetPerformedField.SetValue(null, false);
+ }
+
+ [Fact]
+ public void Log_InProductionMode_ResetsOutputOnce()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ var message1 = "First message";
+ var message2 = "Second message";
+ _outputResetPerformedField.SetValue(null, false);
+
+ // Act
+ wrapper.Log(message1);
+ bool afterFirstLog = (bool)_outputResetPerformedField.GetValue(null);
+ wrapper.Log(message2);
+ bool afterSecondLog = (bool)_outputResetPerformedField.GetValue(null);
+
+ // Assert
+ Assert.True(afterFirstLog, "Flag should be set after first log");
+ Assert.True(afterSecondLog, "Flag should remain set after second log");
+ }
+
+ [Fact]
+ public void LogLine_InProductionMode_ResetsOutputOnce()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ var message1 = "First line";
+ var message2 = "Second line";
+ _outputResetPerformedField.SetValue(null, false);
+
+ // Act
+ wrapper.LogLine(message1);
+ bool afterFirstLog = (bool)_outputResetPerformedField.GetValue(null);
+ wrapper.LogLine(message2);
+ bool afterSecondLog = (bool)_outputResetPerformedField.GetValue(null);
+
+ // Assert
+ Assert.True(afterFirstLog, "Flag should be set after first LogLine");
+ Assert.True(afterSecondLog, "Flag should remain set after second LogLine");
+ }
+
+ [Fact]
+ public void ClearOutputResetFlag_ResetsFlag_AllowsSubsequentReset()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ _outputResetPerformedField.SetValue(null, false);
+
+ // Act
+ wrapper.Log("First message"); // This should cause a reset
+ bool afterFirstLog = (bool)_outputResetPerformedField.GetValue(null);
+
+ SystemWrapper.ClearOutputResetFlag();
+ bool afterClear = (bool)_outputResetPerformedField.GetValue(null);
+
+ wrapper.Log("After clear"); // This should cause another reset
+ bool afterSecondLog = (bool)_outputResetPerformedField.GetValue(null);
+
+ // Assert
+ Assert.True(afterFirstLog, "Flag should be set after first log");
+ Assert.False(afterClear, "Flag should be cleared after ClearOutputResetFlag");
+ Assert.True(afterSecondLog, "Flag should be set again after second log");
+ }
+
+ [Fact]
+ public void Log_InTestMode_WritesToTestOutput()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ SystemWrapper.SetOut(_testWriter);
+ var message = "Test message";
+
+ // Act
+ wrapper.Log(message);
+
+ // Assert
+ Assert.Equal(message, _testWriter.ToString());
+ }
+
+ [Fact]
+ public void LogLine_InTestMode_WritesToTestOutput()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ SystemWrapper.SetOut(_testWriter);
+ var message = "Test line";
+
+ // Act
+ wrapper.LogLine(message);
+
+ // Assert
+ Assert.Equal(message + Environment.NewLine, _testWriter.ToString());
+ }
+
+ [Fact]
+ public void ResetTestMode_ResetsTestState()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ SystemWrapper.SetOut(_testWriter);
+ var message = "This should go to console";
+
+ // Act
+ SystemWrapper.ResetTestMode();
+
+ // Can't directly test that this goes to console, but we can verify
+ // it doesn't go to the test writer
+ wrapper.Log(message);
+
+ // Assert
+ Assert.Equal("", _testWriter.ToString());
+ }
+
+ [Fact]
+ public void SetOut_EnablesTestMode()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ var message = "Test output";
+
+ // Act
+ SystemWrapper.SetOut(_testWriter);
+ wrapper.Log(message);
+
+ // Assert
+ Assert.Equal(message, _testWriter.ToString());
+ }
+
+ [Fact]
+ public void Log_InTestMode_DoesNotCallResetConsoleOutput()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ SystemWrapper.SetOut(_testWriter);
+ var message1 = "First test message";
+ var message2 = "Second test message";
+
+ // Act
+ wrapper.Log(message1);
+ wrapper.Log(message2);
+
+ // Assert
+ Assert.Equal(message1 + message2, _testWriter.ToString());
+ }
+
+ [Fact]
+ public void Log_AfterClearingFlag_ResetsOutputAgain()
+ {
+ // Arrange
+ var wrapper = new SystemWrapper(_mockEnvironment);
+ _outputResetPerformedField.SetValue(null, false);
+
+ // Act
+ wrapper.Log("First message"); // Should reset output
+ bool afterFirstLog = (bool)_outputResetPerformedField.GetValue(null);
+
+ SystemWrapper.ClearOutputResetFlag();
+ bool afterClear = (bool)_outputResetPerformedField.GetValue(null);
+
+ wrapper.Log("Second message"); // Should reset again
+ bool afterSecondLog = (bool)_outputResetPerformedField.GetValue(null);
+
+ // Assert
+ Assert.True(afterFirstLog, "Flag should be set after first log");
+ Assert.False(afterClear, "Flag should be reset after clearing");
+ Assert.True(afterSecondLog, "Flag should be set after second log");
+ }
+
+ public void Dispose()
+ {
+ _testWriter?.Dispose();
+ SystemWrapper.ResetTestMode();
+ _outputResetPerformedField.SetValue(null, false);
+ }
+}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs
index 892a2baf..dd3cd558 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs
@@ -46,7 +46,7 @@ public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.TestMethod();
@@ -72,7 +72,7 @@ public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContextAndLogDebu
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.TestMethodDebug();
@@ -101,7 +101,7 @@ public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArg()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.LogEventNoArgs();
@@ -116,7 +116,7 @@ public void OnEntry_WhenEventArgExist_LogEvent()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
var correlationId = Guid.NewGuid().ToString();
#if NET8_0_OR_GREATER
@@ -150,7 +150,7 @@ public void OnEntry_WhenEventArgExist_LogEvent_False_Should_Not_Log()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
#if NET8_0_OR_GREATER
@@ -175,7 +175,7 @@ public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArgAndLogDebug()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.LogEventDebug();
@@ -190,7 +190,7 @@ public void OnExit_WhenHandler_ClearState_Enabled_ClearKeys()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.ClearState();
@@ -378,7 +378,7 @@ public void When_Setting_SamplingRate_Should_Add_Key()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.HandlerSamplingRate();
@@ -395,7 +395,7 @@ public void When_Setting_Service_Should_Update_Key()
{
// Arrange
var consoleOut = new StringWriter();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.HandlerService();
@@ -411,7 +411,7 @@ public void When_Setting_LogLevel_Should_Update_LogLevel()
{
// Arrange
var consoleOut = new StringWriter();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.TestLogLevelCritical();
@@ -427,7 +427,7 @@ public void When_Setting_LogLevel_HigherThanInformation_Should_Not_LogEvent()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
var context = new TestLambdaContext()
{
FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1"
@@ -445,7 +445,7 @@ public void When_LogLevel_Debug_Should_Log_Message_When_No_Context_And_LogEvent_
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandlers.TestLogEventWithoutContext();
@@ -459,7 +459,7 @@ public void Should_Log_When_Not_Using_Decorator()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
var test = new TestHandlers();
@@ -496,7 +496,7 @@ public void When_Setting_Service_Should_Override_Env()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandler.LogWithEnv();
@@ -517,7 +517,7 @@ public void When_Setting_Service_Should_Override_Env_And_Empty()
{
// Arrange
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
_testHandler.LogWithAndWithoutEnv();
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs
index b9bc8708..0bccdf1a 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs
@@ -48,7 +48,7 @@ public LogFormatterTest()
public void Serialize_ShouldHandleEnumValues()
{
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
@@ -228,7 +228,7 @@ public void Log_WhenCustomFormatter_LogsCustomFormat()
public void Should_Log_CustomFormatter_When_Decorated()
{
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
@@ -263,7 +263,7 @@ public void Should_Log_CustomFormatter_When_Decorated()
public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log()
{
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
@@ -299,7 +299,7 @@ public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log()
public void Should_Log_CustomFormatter_When_Decorated_No_Context()
{
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
Logger.UseFormatter(new CustomLogFormatter());
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs
index 399792a5..46e76a2c 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs
@@ -74,7 +74,7 @@ public void ObjectToDictionary_NullObject_Return_New_Dictionary()
public void Should_Log_With_Anonymous()
{
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act & Assert
Logger.AppendKey("newKey", new
@@ -94,7 +94,7 @@ public void Should_Log_With_Anonymous()
public void Should_Log_With_Complex_Anonymous()
{
var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act & Assert
Logger.AppendKey("newKey", new
diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs
index 0a46d6fd..8a2b3c7f 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs
@@ -13,7 +13,7 @@ public void WhenClearAllDimensions_NoDimensionsInOutput()
{
// Arrange
var consoleOut = new StringWriter();
- SystemWrapper.Instance.SetOut(consoleOut);
+ SystemWrapper.SetOut(consoleOut);
// Act
var handler = new FunctionHandler();
diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs
index 0934b316..cba56806 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs
@@ -35,7 +35,7 @@ public EmfValidationTests()
{
_handler = new FunctionHandler();
_consoleOut = new CustomConsoleWriter();
- SystemWrapper.Instance.SetOut(_consoleOut);
+ SystemWrapper.SetOut(_consoleOut);
}
[Trait("Category", value: "SchemaValidation")]
diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs
index 254de9e9..d9369bc4 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs
@@ -34,7 +34,7 @@ public FunctionHandlerTests()
{
_handler = new FunctionHandler();
_consoleOut = new CustomConsoleWriter();
- SystemWrapper.Instance.SetOut(_consoleOut);
+ SystemWrapper.SetOut(_consoleOut);
}
[Fact]