diff --git a/docs/core/logging.md b/docs/core/logging.md
index d3d59703..6cec97ce 100644
--- a/docs/core/logging.md
+++ b/docs/core/logging.md
@@ -69,6 +69,51 @@ Here is an example using the AWS SAM [Globals section](https://docs.aws.amazon.c
| **POWERTOOLS_LOGGER_LOG_EVENT** | Logs incoming event | `false` |
| **POWERTOOLS_LOGGER_SAMPLE_RATE** | Debug log sampling | `0` |
+
+### Using AWS Lambda Advanced Logging Controls (ALC)
+
+With [AWS Lambda Advanced Logging Controls (ALC)](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-advanced), you can control the output format of your logs as either TEXT or JSON and specify the minimum accepted log level for your application. Regardless of the output format setting in Lambda, Powertools for AWS Lambda will always output JSON formatted logging messages.
+
+When you have this feature enabled, log messages that don’t meet the configured log level are discarded by Lambda. For example, if you set the minimum log level to WARN, you will only receive WARN and ERROR messages in your AWS CloudWatch Logs, all other log levels will be discarded by Lambda.
+
+!!! warning "When using AWS Lambda Advanced Logging Controls (ALC)"
+ - When Powertools Logger output is set to `PascalCase` **`Level`** property name will be replaced by **`LogLevel`** as a property name.
+ - ALC takes precedence over **`POWERTOOLS_LOG_LEVEL`** and when setting it in code using **`[Logging(LogLevel = )]`**
+
+```mermaid
+sequenceDiagram
+ title Lambda ALC allows WARN logs only
+ participant Lambda service
+ participant Lambda function
+ participant Application Logger
+
+ Note over Lambda service: AWS_LAMBDA_LOG_LEVEL="WARN"
+ Lambda service->>Lambda function: Invoke (event)
+ Lambda function->>Lambda function: Calls handler
+ Lambda function->>Application Logger: Logger.Warning("Something happened")
+ Lambda function-->>Application Logger: Logger.Debug("Something happened")
+ Lambda function-->>Application Logger: Logger.Information("Something happened")
+
+ Lambda service->>Lambda service: DROP INFO and DEBUG logs
+
+ Lambda service->>CloudWatch Logs: Ingest error logs
+```
+
+Logger will automatically listen for the AWS_LAMBDA_LOG_FORMAT and AWS_LAMBDA_LOG_LEVEL environment variables, and change behaviour if they’re found to ensure as much compatibility as possible.
+
+**Priority of log level settings in Powertools for AWS Lambda**
+
+When the Advanced Logging Controls feature is enabled, we are unable to increase the minimum log level below the AWS_LAMBDA_LOG_LEVEL environment variable value, see [AWS Lambda service documentation](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-log-level) for more details.
+
+We prioritise log level settings in this order:
+
+1. AWS_LAMBDA_LOG_LEVEL environment variable
+2. Setting the log level in code using `[Logging(LogLevel = )]`
+3. POWERTOOLS_LOG_LEVEL environment variable
+
+In the event you have set POWERTOOLS_LOG_LEVEL to a level lower than the ACL setting, Powertools for AWS Lambda will output a warning log message informing you that your messages will be discarded by Lambda.
+
+
## Standard structured keys
Your logs will always include the following keys to your structured logging:
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs
index d4f0350f..1a15d30b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs
@@ -54,6 +54,11 @@ internal static class Constants
/// Constant for POWERTOOLS_LOG_LEVEL environment variable
///
internal const string LogLevelNameEnv = "POWERTOOLS_LOG_LEVEL";
+
+ ///
+ /// Constant for POWERTOOLS_LOG_LEVEL environment variable
+ ///
+ internal const string AWSLambdaLogLevelNameEnv = "AWS_LAMBDA_LOG_LEVEL";
///
/// Constant for POWERTOOLS_LOGGER_SAMPLE_RATE environment variable
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs
index f11fd344..ee95b318 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs
@@ -57,10 +57,16 @@ public interface IPowertoolsConfigurations
string MetricsNamespace { get; }
///
- /// Gets the log level.
+ /// Gets the Powertools log level.
///
/// The log level.
string LogLevel { get; }
+
+ ///
+ /// Gets the AWS Lambda log level.
+ ///
+ /// The log level.
+ string AWSLambdaLogLevel { get; }
///
/// Gets the logger sample rate.
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs
index 3ff5fd14..f098d426 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs
@@ -147,12 +147,11 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue)
public string MetricsNamespace =>
GetEnvironmentVariable(Constants.MetricsNamespaceEnv);
- ///
- /// Gets the log level.
- ///
- /// The log level.
- public string LogLevel =>
- GetEnvironmentVariable(Constants.LogLevelNameEnv);
+ ///
+ public string LogLevel => GetEnvironmentVariable(Constants.LogLevelNameEnv);
+
+ ///
+ public string AWSLambdaLogLevel => GetEnvironmentVariable(Constants.AWSLambdaLogLevelNameEnv);
///
/// Gets the logger sample rate.
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
index 703a0865..6389b3a0 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.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,6 +14,7 @@
*/
using System;
+using System.IO;
using System.Text;
namespace AWS.Lambda.Powertools.Common;
@@ -39,6 +40,14 @@ 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);
}
///
@@ -97,21 +106,21 @@ public void SetExecutionEnvironment(T type)
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))
+ 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());
@@ -127,13 +136,14 @@ private string ParseAssemblyName(string assemblyName)
{
try
{
- var parsedName = assemblyName.Substring(assemblyName.LastIndexOf(".", StringComparison.Ordinal)+1);
+ 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/PowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurations.cs
index a772003a..4d39cb16 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurations.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurations.cs
@@ -14,6 +14,7 @@
*/
using System;
+using System.Collections.Generic;
using AWS.Lambda.Powertools.Common;
using Microsoft.Extensions.Logging;
@@ -42,6 +43,18 @@ internal static LogLevel GetLogLevel(this IPowertoolsConfigurations powertoolsCo
return LoggingConstants.DefaultLogLevel;
}
+ internal static LogLevel GetLambdaLogLevel(this IPowertoolsConfigurations powertoolsConfigurations)
+ {
+ AwsLogLevelMapper.TryGetValue((powertoolsConfigurations.AWSLambdaLogLevel ?? "").Trim().ToUpper(), out var awsLogLevel);
+
+ if (Enum.TryParse(awsLogLevel, true, out LogLevel result))
+ {
+ return result;
+ }
+
+ return LogLevel.None;
+ }
+
internal static LoggerOutputCase GetLoggerOutputCase(this IPowertoolsConfigurations powertoolsConfigurations,
LoggerOutputCase? loggerOutputCase = null)
{
@@ -53,4 +66,14 @@ internal static LoggerOutputCase GetLoggerOutputCase(this IPowertoolsConfigurati
return LoggingConstants.DefaultLoggerOutputCase;
}
+
+ private static Dictionary AwsLogLevelMapper = new()
+ {
+ { "TRACE", "TRACE" },
+ { "DEBUG", "DEBUG" },
+ { "INFO", "INFORMATION" },
+ { "WARN", "WARNING" },
+ { "ERROR", "ERROR" },
+ { "FATAL", "CRITICAL" }
+ };
}
\ 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 707d4a8e..27d7d613 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -50,17 +50,16 @@ internal sealed class PowertoolsLogger : ILogger
/// The system wrapper
///
private readonly ISystemWrapper _systemWrapper;
-
- ///
- /// The current configuration
- ///
- private LoggerConfiguration _currentConfig;
///
/// The JsonSerializer options
///
private JsonSerializerOptions _jsonSerializerOptions;
+ private LogLevel _lambdaLogLevel;
+ private LogLevel _logLevel;
+ private bool _lambdaLogLevelEnabled;
+
///
/// Initializes a new instance of the class.
///
@@ -78,14 +77,17 @@ public PowertoolsLogger(
powertoolsConfigurations, systemWrapper, getCurrentConfig);
_powertoolsConfigurations.SetExecutionEnvironment(this);
+ CurrentConfig = GetCurrentConfig();
+
+ if (_lambdaLogLevelEnabled && _logLevel < _lambdaLogLevel)
+ {
+ var message =
+ $"Current log level ({_logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({_lambdaLogLevel}). This can lead to data loss, consider adjusting them.";
+ this.LogWarning(message);
+ }
}
- ///
- /// Sets the current configuration.
- ///
- /// The current configuration.
- private LoggerConfiguration CurrentConfig =>
- _currentConfig ??= GetCurrentConfig();
+ private LoggerConfiguration CurrentConfig { get; set; }
///
/// Sets the minimum level.
@@ -255,8 +257,15 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
}
}
+ var keyLogLevel = LoggingConstants.KeyLogLevel;
+ // If ALC is enabled and PascalCase we need to convert Level to LogLevel for it to be parsed and sent to CW
+ if (_lambdaLogLevelEnabled && CurrentConfig.LoggerOutputCase == LoggerOutputCase.PascalCase)
+ {
+ keyLogLevel = "LogLevel";
+ }
+
logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString("o"));
- logEntry.TryAdd(LoggingConstants.KeyLogLevel, logLevel.ToString());
+ logEntry.TryAdd(keyLogLevel, logLevel.ToString());
logEntry.TryAdd(LoggingConstants.KeyService, Service);
logEntry.TryAdd(LoggingConstants.KeyLoggerName, _name);
logEntry.TryAdd(LoggingConstants.KeyMessage, message);
@@ -361,7 +370,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
///
internal void ClearConfig()
{
- _currentConfig = null;
+ CurrentConfig = null;
}
///
@@ -371,14 +380,22 @@ internal void ClearConfig()
private LoggerConfiguration GetCurrentConfig()
{
var currConfig = _getCurrentConfig();
- var minimumLevel = _powertoolsConfigurations.GetLogLevel(currConfig?.MinimumLevel);
+ _logLevel = _powertoolsConfigurations.GetLogLevel(currConfig?.MinimumLevel);
var samplingRate = currConfig?.SamplingRate ?? _powertoolsConfigurations.LoggerSampleRate;
var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(currConfig?.LoggerOutputCase);
-
+ _lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
+ _lambdaLogLevelEnabled = _lambdaLogLevel != LogLevel.None;
+
+ var minLogLevel = _logLevel;
+ if (_lambdaLogLevelEnabled)
+ {
+ minLogLevel = _lambdaLogLevel;
+ }
+
var config = new LoggerConfiguration
{
Service = currConfig?.Service,
- MinimumLevel = minimumLevel,
+ MinimumLevel = minLogLevel,
SamplingRate = samplingRate,
LoggerOutputCase = loggerOutputCase
};
@@ -388,7 +405,7 @@ private LoggerConfiguration GetCurrentConfig()
if (samplingRate.Value < 0 || samplingRate.Value > 1)
{
- if (minimumLevel is LogLevel.Debug or LogLevel.Trace)
+ if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
_systemWrapper.LogLine(
$"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate.Value}");
config.SamplingRate = null;
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs
index c0fd3c09..c49852af 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.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
@@ -21,8 +21,11 @@
using System.Text;
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
+using AWS.Lambda.Powertools.Logging.Tests.Utilities;
using Microsoft.Extensions.Logging;
using NSubstitute;
+using NSubstitute.Extensions;
+using NSubstitute.ReceivedExtensions;
using Xunit;
namespace AWS.Lambda.Powertools.Logging.Tests
@@ -34,7 +37,7 @@ public PowertoolsLoggerTest()
{
Logger.UseDefaultFormatter();
}
-
+
private static void Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel minimumLevel)
{
// Arrange
@@ -1219,7 +1222,7 @@ public void Log_Set_Execution_Environment_Context()
$"{Constants.FeatureContextIdentifier}/Logger/{assemblyVersion}");
env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV");
}
-
+
[Fact]
public void Log_Should_Serialize_DateOnly()
{
@@ -1260,7 +1263,7 @@ public void Log_Should_Serialize_DateOnly()
)
);
}
-
+
[Fact]
public void Log_Should_Serialize_TimeOnly()
{
@@ -1301,5 +1304,240 @@ public void Log_Should_Serialize_TimeOnly()
)
);
}
+
+
+ [Theory]
+ [InlineData(true, "WARN", LogLevel.Warning)]
+ [InlineData(false, "Fatal", LogLevel.Critical)]
+ [InlineData(false, "NotValid", LogLevel.Critical)]
+ [InlineData(true, "NotValid", LogLevel.Warning)]
+ public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(bool willLog, string awsLogLevel, LogLevel logLevel)
+ {
+ // Arrange
+ var loggerName = Guid.NewGuid().ToString();
+
+ var environment = Substitute.For();
+ // Powertools Log Level takes precedence
+ environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(logLevel.ToString());
+ environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel);
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+
+ var logger = new PowertoolsLogger(loggerName, configuration, systemWrapper, () =>
+ new LoggerConfiguration
+ {
+ LoggerOutputCase = LoggerOutputCase.CamelCase
+ });
+
+ var message = new
+ {
+ PropOne = "Value 1",
+ PropTwo = "Value 2",
+ Time = new TimeOnly(12, 0, 0)
+ };
+
+ // Act
+ logger.LogWarning(message);
+
+ // Assert
+ Assert.True(logger.IsEnabled(logLevel));
+ Assert.Equal(logLevel, configuration.GetLogLevel());
+ Assert.Equal(willLog, systemWrapper.LogMethodCalled);
+ }
+
+ [Theory]
+ [InlineData(true, "WARN", LogLevel.Warning)]
+ [InlineData(false, "Fatal", LogLevel.Critical)]
+ public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(bool willLog, string awsLogLevel, LogLevel logLevel)
+ {
+ // Arrange
+ var loggerName = Guid.NewGuid().ToString();
+
+ var environment = Substitute.For();
+ // Powertools Log Level not set, AWS Lambda Log Level is used
+ environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(string.Empty);
+ environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel);
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+
+ var logger = new PowertoolsLogger(loggerName, configuration, systemWrapper, () =>
+ new LoggerConfiguration
+ {
+ LoggerOutputCase = LoggerOutputCase.CamelCase
+ });
+
+ var message = new
+ {
+ PropOne = "Value 1",
+ PropTwo = "Value 2",
+ Time = new TimeOnly(12, 0, 0)
+ };
+
+ // Act
+ logger.LogWarning(message);
+
+ // Assert
+ Assert.True(logger.IsEnabled(logLevel));
+ Assert.Equal(LogLevel.Information, configuration.GetLogLevel()); //default
+ Assert.Equal(logLevel, configuration.GetLambdaLogLevel());
+ Assert.Equal(willLog, systemWrapper.LogMethodCalled);
+ }
+
+ [Fact]
+ public void Log_Should_Show_Warning_When_AWS_Lambda_Log_Level_Enabled()
+ {
+ // Arrange
+ var loggerName = Guid.NewGuid().ToString();
+
+ var environment = Substitute.For();
+ environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns("Debug");
+ environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Warn");
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+
+ var logger = new PowertoolsLogger(loggerName, configuration, systemWrapper, () =>
+ new LoggerConfiguration
+ {
+ LoggerOutputCase = LoggerOutputCase.CamelCase
+ });
+
+ var logLevel = configuration.GetLogLevel();
+ var lambdaLogLevel = configuration.GetLambdaLogLevel();
+
+ // Assert
+ Assert.True(logger.IsEnabled(LogLevel.Warning));
+ Assert.Equal(LogLevel.Debug, logLevel);
+ Assert.Equal(LogLevel.Warning, lambdaLogLevel);
+ Assert.True(systemWrapper.LogMethodCalled);
+ Assert.Contains($"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.",
+ systemWrapper.LogMethodCalledWithArgument);
+ }
+
+ [Theory]
+ [InlineData(true,"LogLevel")]
+ [InlineData(false,"Level")]
+ public void Log_PascalCase_Outputs_Correct_Level_Property_When_AWS_Lambda_Log_Level_Enabled_Or_Disabled(bool alcEnabled, string levelProp)
+ {
+ // Arrange
+ var loggerName = Guid.NewGuid().ToString();
+
+ var environment = Substitute.For();
+ environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns("Information");
+ if(alcEnabled)
+ environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Info");
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+ var logger = new PowertoolsLogger(loggerName, configuration, systemWrapper, () =>
+ new LoggerConfiguration
+ {
+ LoggerOutputCase = LoggerOutputCase.PascalCase
+ });
+
+ var message = new
+ {
+ PropOne = "Value 1",
+ };
+
+ logger.LogInformation(message);
+
+ // Assert
+ Assert.True(systemWrapper.LogMethodCalled);
+ Assert.Contains($"\"{levelProp}\":\"Information\"",systemWrapper.LogMethodCalledWithArgument);
+ }
+
+ [Theory]
+ [InlineData(LoggerOutputCase.CamelCase)]
+ [InlineData(LoggerOutputCase.SnakeCase)]
+ public void Log_CamelCase_Outputs_Level_When_AWS_Lambda_Log_Level_Enabled(LoggerOutputCase casing)
+ {
+ // Arrange
+ var loggerName = Guid.NewGuid().ToString();
+
+ var environment = Substitute.For();
+ environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(string.Empty);
+ environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Info");
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+ var logger = new PowertoolsLogger(loggerName, configuration, systemWrapper, () =>
+ new LoggerConfiguration
+ {
+ LoggerOutputCase = casing
+ });
+
+ var message = new
+ {
+ PropOne = "Value 1",
+ };
+
+ logger.LogInformation(message);
+
+ // Assert
+ Assert.True(systemWrapper.LogMethodCalled);
+ Assert.Contains("\"level\":\"Information\"",systemWrapper.LogMethodCalledWithArgument);
+ }
+
+ [Theory]
+ [InlineData("TRACE", LogLevel.Trace)]
+ [InlineData("debug", LogLevel.Debug)]
+ [InlineData("Info", LogLevel.Information)]
+ [InlineData("WARN", LogLevel.Warning)]
+ [InlineData("ERROR", LogLevel.Error)]
+ [InlineData("Fatal", LogLevel.Critical)]
+ [InlineData("DoesNotExist", LogLevel.None)]
+ public void Should_Map_AWS_Log_Level_And_Default_To_Information(string awsLogLevel, LogLevel logLevel)
+ {
+ // Arrange
+ var environment = Substitute.For();
+ environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel);
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+
+ // Act
+ var logLvl = configuration.GetLambdaLogLevel();
+
+ // Assert
+ Assert.Equal(logLevel, logLvl);
+ }
+
+ [Theory]
+ [InlineData(true, LogLevel.Warning)]
+ [InlineData(false, LogLevel.Critical)]
+ public void Log_Should_Use_Powertools_Log_Level_When_Set(bool willLog, LogLevel logLevel)
+ {
+ // Arrange
+ var loggerName = Guid.NewGuid().ToString();
+ var environment = Substitute.For();
+ environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(logLevel.ToString());
+
+ var systemWrapper = new SystemWrapperMock(environment);
+ var configuration = new PowertoolsConfigurations(systemWrapper);
+
+ var logger = new PowertoolsLogger(loggerName, configuration, systemWrapper, () =>
+ new LoggerConfiguration
+ {
+ LoggerOutputCase = LoggerOutputCase.CamelCase
+ });
+
+ var message = new
+ {
+ PropOne = "Value 1",
+ PropTwo = "Value 2",
+ Time = new TimeOnly(12, 0, 0)
+ };
+
+ // Act
+ logger.LogWarning(message);
+
+ // Assert
+ Assert.True(logger.IsEnabled(logLevel));
+ Assert.Equal(logLevel.ToString(), configuration.LogLevel);
+ Assert.Equal(willLog, systemWrapper.LogMethodCalled);
+ }
}
}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs
new file mode 100644
index 00000000..4af6e593
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs
@@ -0,0 +1,62 @@
+/*
+ * 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 AWS.Lambda.Powertools.Common;
+
+namespace AWS.Lambda.Powertools.Logging.Tests.Utilities;
+
+public class SystemWrapperMock : ISystemWrapper
+{
+ private readonly IPowertoolsEnvironment _powertoolsEnvironment;
+ public bool LogMethodCalled { get; private set; }
+ public string LogMethodCalledWithArgument { get; private set; }
+
+ public SystemWrapperMock(IPowertoolsEnvironment powertoolsEnvironment)
+ {
+ _powertoolsEnvironment = powertoolsEnvironment;
+ }
+
+ public string GetEnvironmentVariable(string variable)
+ {
+ return _powertoolsEnvironment.GetEnvironmentVariable(variable);
+ }
+
+ public void Log(string value)
+ {
+ LogMethodCalledWithArgument = value;
+ LogMethodCalled = true;
+ }
+
+ public void LogLine(string value)
+ {
+ LogMethodCalledWithArgument = value;
+ LogMethodCalled = true;
+ }
+
+
+ public double GetRandom()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void SetEnvironmentVariable(string variable, string value)
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public void SetExecutionEnvironment(T type)
+ {
+ }
+}
\ No newline at end of file
diff --git a/version.json b/version.json
index 2c0034cb..d83c948c 100644
--- a/version.json
+++ b/version.json
@@ -1,7 +1,7 @@
{
"Core": {
- "Logging": "1.3.3",
- "Metrics": "1.4.3",
+ "Logging": "1.4.3",
+ "Metrics": "1.5.3",
"Tracing": "1.3.2"
},
"Utilities": {