From 01638c92e0545780b603e1dfe4fe61df16e04b0d Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Thu, 6 Mar 2025 18:58:28 +0000
Subject: [PATCH 01/49] refactor to support ILogger
---
.../Logger.ExtraKeysLogs.cs | 439 +++++++
.../Logger.Formatter.cs | 49 +
.../Logger.JsonLogs.cs | 168 +++
.../Logger.Scope.cs | 97 ++
.../Logger.StandardLogs.cs | 463 +++++++
.../AWS.Lambda.Powertools.Logging/Logger.cs | 1110 +----------------
6 files changed, 1231 insertions(+), 1095 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Logger.ExtraKeysLogs.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.ExtraKeysLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.ExtraKeysLogs.cs
new file mode 100644
index 00000000..be00722a
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.ExtraKeysLogs.cs
@@ -0,0 +1,439 @@
+/*
+ * 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;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public partial class Logger
+{
+ #region ExtraKeys Logger Methods
+
+ #region Debug
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogDebug(extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void LogDebug(T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.LogDebug(extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogDebug(extraKeys, 0, "Processing request from {Address}", address)
+ public static void LogDebug(T extraKeys, EventId eventId, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogDebug(extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogDebug(extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void LogDebug(T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogDebug(extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogDebug(extraKeys, "Processing request from {Address}", address)
+ public static void LogDebug(T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogDebug(extraKeys, message, args);
+ }
+
+ #endregion
+
+ #region Trace
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogTrace(extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void LogTrace(T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.LogTrace(extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogTrace(extraKeys, 0, "Processing request from {Address}", address)
+ public static void LogTrace(T extraKeys, EventId eventId, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogTrace(extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogTrace(extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void LogTrace(T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogTrace(extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogTrace(extraKeys, "Processing request from {Address}", address)
+ public static void LogTrace(T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogTrace(extraKeys, message, args);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogInformation(extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void LogInformation(T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.LogInformation(extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogInformation(extraKeys, 0, "Processing request from {Address}", address)
+ public static void LogInformation(T extraKeys, EventId eventId, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogInformation(extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogInformation(extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void LogInformation(T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogInformation(extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogInformation(extraKeys, "Processing request from {Address}", address)
+ public static void LogInformation(T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogInformation(extraKeys, message, args);
+ }
+
+ #endregion
+
+ #region Warning
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogWarning(extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void LogWarning(T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.LogWarning(extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogWarning(extraKeys, 0, "Processing request from {Address}", address)
+ public static void LogWarning(T extraKeys, EventId eventId, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogWarning(extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogWarning(extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void LogWarning(T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogWarning(extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogWarning(extraKeys, "Processing request from {Address}", address)
+ public static void LogWarning(T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogWarning(extraKeys, message, args);
+ }
+
+ #endregion
+
+ #region Error
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogError(extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void LogError(T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.LogError(extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogError(extraKeys, 0, "Processing request from {Address}", address)
+ public static void LogError(T extraKeys, EventId eventId, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogError(extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogError(extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void LogError(T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogError(extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogError(extraKeys, "Processing request from {Address}", address)
+ public static void LogError(T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogError(extraKeys, message, args);
+ }
+
+ #endregion
+
+ #region Critical
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogCritical(extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void LogCritical(T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.LogCritical(extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogCritical(extraKeys, 0, "Processing request from {Address}", address)
+ public static void LogCritical(T extraKeys, EventId eventId, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogCritical(extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogCritical(extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void LogCritical(T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.LogCritical(extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.LogCritical(extraKeys, "Processing request from {Address}", address)
+ public static void LogCritical(T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.LogCritical(extraKeys, message, args);
+ }
+
+ #endregion
+
+ #region Log
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.Log(LogLevel.Information, extraKeys, 0, exception, "Error while processing request from {Address}", address)
+ public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, Exception exception, string message,
+ params object[] args) where T : class
+ {
+ LoggerInstance.Log(logLevel, extraKeys, eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// Additional keys will be appended to the log entry.
+ /// The event id associated with the log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.Log(LogLevel.Information, extraKeys, 0, "Processing request from {Address}", address)
+ public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.Log(logLevel, extraKeys, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// Additional keys will be appended to the log entry.
+ /// The exception to log.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.Log(LogLevel.Information, extraKeys, exception, "Error while processing request from {Address}", address)
+ public static void Log(LogLevel logLevel, T extraKeys, Exception exception, string message, params object[] args)
+ where T : class
+ {
+ LoggerInstance.Log(logLevel, extraKeys, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// Additional keys will be appended to the log entry.
+ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
+ /// An object array that contains zero or more objects to format.
+ /// logger.Log(LogLevel.Information, extraKeys, "Processing request from {Address}", address)
+ public static void Log(LogLevel logLevel, T extraKeys, string message, params object[] args) where T : class
+ {
+ LoggerInstance.Log(logLevel, extraKeys, message, args);
+ }
+
+ #endregion
+
+ #endregion
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
new file mode 100644
index 00000000..62a5bb51
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
@@ -0,0 +1,49 @@
+/*
+ * 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;
+using AWS.Lambda.Powertools.Logging.Internal;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public partial class Logger
+{
+ #region Custom Log Formatter
+
+ ///
+ /// Set the log formatter.
+ ///
+ /// The log formatter.
+ /// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor
+ public static void UseFormatter(ILogFormatter logFormatter)
+ {
+ _logFormatter = logFormatter ?? throw new ArgumentNullException(nameof(logFormatter));
+ }
+
+ ///
+ /// Set the log formatter to default.
+ ///
+ public static void UseDefaultFormatter()
+ {
+ _logFormatter = null;
+ }
+
+ ///
+ /// Returns the log formatter.
+ ///
+ internal static ILogFormatter GetFormatter() => _logFormatter;
+
+ #endregion
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs
new file mode 100644
index 00000000..680f766e
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs
@@ -0,0 +1,168 @@
+/*
+ * 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;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public partial class Logger
+{
+ #region JSON Logger Methods
+
+ ///
+ /// Formats and writes a trace log message as JSON.
+ ///
+ /// The object to be serialized as JSON.
+ /// logger.LogTrace(new {User = user, Address = address})
+ public static void LogTrace(object message)
+ {
+ LoggerInstance.LogTrace(message);
+ }
+
+ ///
+ /// Formats and writes an trace log message.
+ ///
+ /// The exception to log.
+ /// logger.LogTrace(exception)
+ public static void LogTrace(Exception exception)
+ {
+ LoggerInstance.LogTrace(exception);
+ }
+
+ ///
+ /// Formats and writes a debug log message as JSON.
+ ///
+ /// The object to be serialized as JSON.
+ /// logger.LogDebug(new {User = user, Address = address})
+ public static void LogDebug(object message)
+ {
+ LoggerInstance.LogDebug(message);
+ }
+
+ ///
+ /// Formats and writes an debug log message.
+ ///
+ /// The exception to log.
+ /// logger.LogDebug(exception)
+ public static void LogDebug(Exception exception)
+ {
+ LoggerInstance.LogDebug(exception);
+ }
+
+ ///
+ /// Formats and writes an information log message as JSON.
+ ///
+ /// The object to be serialized as JSON.
+ /// logger.LogInformation(new {User = user, Address = address})
+ public static void LogInformation(object message)
+ {
+ LoggerInstance.LogInformation(message);
+ }
+
+ ///
+ /// Formats and writes an information log message.
+ ///
+ /// The exception to log.
+ /// logger.LogInformation(exception)
+ public static void LogInformation(Exception exception)
+ {
+ LoggerInstance.LogInformation(exception);
+ }
+
+ ///
+ /// Formats and writes a warning log message as JSON.
+ ///
+ /// The object to be serialized as JSON.
+ /// logger.LogWarning(new {User = user, Address = address})
+ public static void LogWarning(object message)
+ {
+ LoggerInstance.LogWarning(message);
+ }
+
+ ///
+ /// Formats and writes an warning log message.
+ ///
+ /// The exception to log.
+ /// logger.LogWarning(exception)
+ public static void LogWarning(Exception exception)
+ {
+ LoggerInstance.LogWarning(exception);
+ }
+
+ ///
+ /// Formats and writes a error log message as JSON.
+ ///
+ /// The object to be serialized as JSON.
+ /// logger.LogCritical(new {User = user, Address = address})
+ public static void LogError(object message)
+ {
+ LoggerInstance.LogError(message);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// The exception to log.
+ /// logger.LogError(exception)
+ public static void LogError(Exception exception)
+ {
+ LoggerInstance.LogError(exception);
+ }
+
+ ///
+ /// Formats and writes a critical log message as JSON.
+ ///
+ /// The object to be serialized as JSON.
+ /// logger.LogCritical(new {User = user, Address = address})
+ public static void LogCritical(object message)
+ {
+ LoggerInstance.LogCritical(message);
+ }
+
+ ///
+ /// Formats and writes an critical log message.
+ ///
+ /// The exception to log.
+ /// logger.LogCritical(exception)
+ public static void LogCritical(Exception exception)
+ {
+ LoggerInstance.LogCritical(exception);
+ }
+
+ ///
+ /// Formats and writes a log message as JSON at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// The object to be serialized as JSON.
+ /// logger.Log(LogLevel.Information, new {User = user, Address = address})
+ public static void Log(LogLevel logLevel, object message)
+ {
+ LoggerInstance.Log(logLevel, message);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// The exception to log.
+ /// logger.Log(LogLevel.Information, exception)
+ public static void Log(LogLevel logLevel, Exception exception)
+ {
+ LoggerInstance.Log(logLevel, exception);
+ }
+
+ #endregion
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
new file mode 100644
index 00000000..d5561327
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
@@ -0,0 +1,97 @@
+/*
+ * 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;
+using System.Collections.Generic;
+using System.Linq;
+using AWS.Lambda.Powertools.Logging.Internal.Helpers;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public partial class Logger
+{
+ #region Scope Variables
+
+ ///
+ /// Appending additional key to the log context.
+ ///
+ /// The key.
+ /// The value.
+ /// key
+ /// value
+ public static void AppendKey(string key, object value)
+ {
+ if (string.IsNullOrWhiteSpace(key))
+ throw new ArgumentNullException(nameof(key));
+
+#if NET8_0_OR_GREATER
+ Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ??
+ throw new ArgumentNullException(nameof(value));
+#else
+ Scope[key] = value ?? throw new ArgumentNullException(nameof(value));
+#endif
+ }
+
+ ///
+ /// Appending additional key to the log context.
+ ///
+ /// The list of keys.
+ public static void AppendKeys(IEnumerable> keys)
+ {
+ foreach (var (key, value) in keys)
+ AppendKey(key, value);
+ }
+
+ ///
+ /// Appending additional key to the log context.
+ ///
+ /// The list of keys.
+ public static void AppendKeys(IEnumerable> keys)
+ {
+ foreach (var (key, value) in keys)
+ AppendKey(key, value);
+ }
+
+ ///
+ /// Remove additional keys from the log context.
+ ///
+ /// The list of keys.
+ public static void RemoveKeys(params string[] keys)
+ {
+ if (keys == null) return;
+ foreach (var key in keys)
+ if (Scope.ContainsKey(key))
+ Scope.Remove(key);
+ }
+
+ ///
+ /// Returns all additional keys added to the log context.
+ ///
+ /// IEnumerable<KeyValuePair<System.String, System.Object>>.
+ public static IEnumerable> GetAllKeys()
+ {
+ return Scope.AsEnumerable();
+ }
+
+ ///
+ /// Removes all additional keys from the log context.
+ ///
+ internal static void RemoveAllKeys()
+ {
+ Scope.Clear();
+ }
+
+ #endregion
+}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs
new file mode 100644
index 00000000..c403a8ef
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs
@@ -0,0 +1,463 @@
+/*
+ * 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;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public partial class Logger
+{
+ #region Core Logger Methods
+
+ #region Debug
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// The event id associated with the log.
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogDebug(0, exception, "Error while processing request from {Address}", address)
+ public static void LogDebug(EventId eventId, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogDebug(eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// The event id associated with the log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogDebug(0, "Processing request from {Address}", address)
+ public static void LogDebug(EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.LogDebug(eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogDebug(exception, "Error while processing request from {Address}", address)
+ public static void LogDebug(Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogDebug(exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a debug log message.
+ ///
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogDebug("Processing request from {Address}", address)
+ public static void LogDebug(string message, params object[] args)
+ {
+ LoggerInstance.LogDebug(message, args);
+ }
+
+ #endregion
+
+ #region Trace
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// The event id associated with the log.
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogTrace(0, exception, "Error while processing request from {Address}", address)
+ public static void LogTrace(EventId eventId, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogTrace(eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// The event id associated with the log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogTrace(0, "Processing request from {Address}", address)
+ public static void LogTrace(EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.LogTrace(eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogTrace(exception, "Error while processing request from {Address}", address)
+ public static void LogTrace(Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogTrace(exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a trace log message.
+ ///
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogTrace("Processing request from {Address}", address)
+ public static void LogTrace(string message, params object[] args)
+ {
+ LoggerInstance.LogTrace(message, args);
+ }
+
+ #endregion
+
+ #region Information
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// The event id associated with the log.
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogInformation(0, exception, "Error while processing request from {Address}", address)
+ public static void LogInformation(EventId eventId, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogInformation(eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// The event id associated with the log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogInformation(0, "Processing request from {Address}", address)
+ public static void LogInformation(EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.LogInformation(eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogInformation(exception, "Error while processing request from {Address}", address)
+ public static void LogInformation(Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogInformation(exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an informational log message.
+ ///
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogInformation("Processing request from {Address}", address)
+ public static void LogInformation(string message, params object[] args)
+ {
+ LoggerInstance.LogInformation(message, args);
+ }
+
+ #endregion
+
+ #region Warning
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// The event id associated with the log.
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogWarning(0, exception, "Error while processing request from {Address}", address)
+ public static void LogWarning(EventId eventId, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogWarning(eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// The event id associated with the log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogWarning(0, "Processing request from {Address}", address)
+ public static void LogWarning(EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.LogWarning(eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogWarning(exception, "Error while processing request from {Address}", address)
+ public static void LogWarning(Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogWarning(exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a warning log message.
+ ///
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogWarning("Processing request from {Address}", address)
+ public static void LogWarning(string message, params object[] args)
+ {
+ LoggerInstance.LogWarning(message, args);
+ }
+
+ #endregion
+
+ #region Error
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// The event id associated with the log.
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogError(0, exception, "Error while processing request from {Address}", address)
+ public static void LogError(EventId eventId, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogError(eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// The event id associated with the log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogError(0, "Processing request from {Address}", address)
+ public static void LogError(EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.LogError(eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogError(exception, "Error while processing request from {Address}", address)
+ public static void LogError(Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogError(exception, message, args);
+ }
+
+ ///
+ /// Formats and writes an error log message.
+ ///
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogError("Processing request from {Address}", address)
+ public static void LogError(string message, params object[] args)
+ {
+ LoggerInstance.LogError(message, args);
+ }
+
+ #endregion
+
+ #region Critical
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// The event id associated with the log.
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogCritical(0, exception, "Error while processing request from {Address}", address)
+ public static void LogCritical(EventId eventId, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogCritical(eventId, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// The event id associated with the log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogCritical(0, "Processing request from {Address}", address)
+ public static void LogCritical(EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.LogCritical(eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ /// The exception to log.
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogCritical(exception, "Error while processing request from {Address}", address)
+ public static void LogCritical(Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.LogCritical(exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a critical log message.
+ ///
+ ///
+ /// Format string of the log message in message template format. Example:
+ /// "User {User} logged in from {Address}"
+ ///
+ /// An object array that contains zero or more objects to format.
+ /// Logger.LogCritical("Processing request from {Address}", address)
+ public static void LogCritical(string message, params object[] args)
+ {
+ LoggerInstance.LogCritical(message, args);
+ }
+
+ #endregion
+
+ #region Log
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// Format string of the log message.
+ /// An object array that contains zero or more objects to format.
+ public static void Log(LogLevel logLevel, string message, params object[] args)
+ {
+ LoggerInstance.Log(logLevel, message, args);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// The event id associated with the log.
+ /// Format string of the log message.
+ /// An object array that contains zero or more objects to format.
+ public static void Log(LogLevel logLevel, EventId eventId, string message, params object[] args)
+ {
+ LoggerInstance.Log(logLevel, eventId, message, args);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// The exception to log.
+ /// Format string of the log message.
+ /// An object array that contains zero or more objects to format.
+ public static void Log(LogLevel logLevel, Exception exception, string message, params object[] args)
+ {
+ LoggerInstance.Log(logLevel, exception, message, args);
+ }
+
+ ///
+ /// Formats and writes a log message at the specified log level.
+ ///
+ /// Entry will be written on this level.
+ /// The event id associated with the log.
+ /// The exception to log.
+ /// Format string of the log message.
+ /// An object array that contains zero or more objects to format.
+ public static void Log(LogLevel logLevel, EventId eventId, Exception exception, string message,
+ params object[] args)
+ {
+ LoggerInstance.Log(logLevel, eventId, exception, message, args);
+ }
+
+ #endregion
+
+ #endregion
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index 4271de83..e0aa1266 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -25,7 +25,7 @@ namespace AWS.Lambda.Powertools.Logging;
///
/// Class Logger.
///
-public class Logger
+public partial class Logger : ILogger
{
///
/// The logger instance
@@ -82,524 +82,36 @@ public static ILogger Create()
return Create(typeof(T).FullName);
}
- #region Scope Variables
-
- ///
- /// Appending additional key to the log context.
- ///
- /// The key.
- /// The value.
- /// key
- /// value
- public static void AppendKey(string key, object value)
- {
- if (string.IsNullOrWhiteSpace(key))
- throw new ArgumentNullException(nameof(key));
-
-#if NET8_0_OR_GREATER
- Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ??
- throw new ArgumentNullException(nameof(value));
-#else
- Scope[key] = value ?? throw new ArgumentNullException(nameof(value));
-#endif
- }
-
- ///
- /// Appending additional key to the log context.
- ///
- /// The list of keys.
- public static void AppendKeys(IEnumerable> keys)
- {
- foreach (var (key, value) in keys)
- AppendKey(key, value);
- }
-
- ///
- /// Appending additional key to the log context.
- ///
- /// The list of keys.
- public static void AppendKeys(IEnumerable> keys)
- {
- foreach (var (key, value) in keys)
- AppendKey(key, value);
- }
-
- ///
- /// Remove additional keys from the log context.
- ///
- /// The list of keys.
- public static void RemoveKeys(params string[] keys)
- {
- if (keys == null) return;
- foreach (var key in keys)
- if (Scope.ContainsKey(key))
- Scope.Remove(key);
- }
-
- ///
- /// Returns all additional keys added to the log context.
- ///
- /// IEnumerable<KeyValuePair<System.String, System.Object>>.
- public static IEnumerable> GetAllKeys()
- {
- return Scope.AsEnumerable();
- }
-
- ///
- /// Removes all additional keys from the log context.
- ///
- internal static void RemoveAllKeys()
- {
- Scope.Clear();
- }
-
internal static void ClearLoggerInstance()
{
_loggerInstance = null;
}
- #endregion
-
- #region Core Logger Methods
-
- #region Debug
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// The event id associated with the log.
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogDebug(0, exception, "Error while processing request from {Address}", address)
- public static void LogDebug(EventId eventId, Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogDebug(eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// The event id associated with the log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogDebug(0, "Processing request from {Address}", address)
- public static void LogDebug(EventId eventId, string message, params object[] args)
- {
- LoggerInstance.LogDebug(eventId, message, args);
- }
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogDebug(exception, "Error while processing request from {Address}", address)
- public static void LogDebug(Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogDebug(exception, message, args);
- }
-
- ///
- /// Formats and writes a debug log message.
- ///
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogDebug("Processing request from {Address}", address)
- public static void LogDebug(string message, params object[] args)
- {
- LoggerInstance.LogDebug(message, args);
- }
-
- #endregion
-
- #region Trace
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// The event id associated with the log.
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogTrace(0, exception, "Error while processing request from {Address}", address)
- public static void LogTrace(EventId eventId, Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogTrace(eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// The event id associated with the log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogTrace(0, "Processing request from {Address}", address)
- public static void LogTrace(EventId eventId, string message, params object[] args)
- {
- LoggerInstance.LogTrace(eventId, message, args);
- }
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogTrace(exception, "Error while processing request from {Address}", address)
- public static void LogTrace(Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogTrace(exception, message, args);
- }
-
- ///
- /// Formats and writes a trace log message.
- ///
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogTrace("Processing request from {Address}", address)
- public static void LogTrace(string message, params object[] args)
- {
- LoggerInstance.LogTrace(message, args);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// The event id associated with the log.
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogInformation(0, exception, "Error while processing request from {Address}", address)
- public static void LogInformation(EventId eventId, Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogInformation(eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// The event id associated with the log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogInformation(0, "Processing request from {Address}", address)
- public static void LogInformation(EventId eventId, string message, params object[] args)
- {
- LoggerInstance.LogInformation(eventId, message, args);
- }
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogInformation(exception, "Error while processing request from {Address}", address)
- public static void LogInformation(Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogInformation(exception, message, args);
- }
-
- ///
- /// Formats and writes an informational log message.
- ///
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogInformation("Processing request from {Address}", address)
- public static void LogInformation(string message, params object[] args)
- {
- LoggerInstance.LogInformation(message, args);
- }
-
- #endregion
-
- #region Warning
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// The event id associated with the log.
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogWarning(0, exception, "Error while processing request from {Address}", address)
- public static void LogWarning(EventId eventId, Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogWarning(eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// The event id associated with the log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogWarning(0, "Processing request from {Address}", address)
- public static void LogWarning(EventId eventId, string message, params object[] args)
- {
- LoggerInstance.LogWarning(eventId, message, args);
- }
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogWarning(exception, "Error while processing request from {Address}", address)
- public static void LogWarning(Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogWarning(exception, message, args);
- }
-
- ///
- /// Formats and writes a warning log message.
- ///
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogWarning("Processing request from {Address}", address)
- public static void LogWarning(string message, params object[] args)
- {
- LoggerInstance.LogWarning(message, args);
- }
-
- #endregion
-
- #region Error
-
- ///
- /// Formats and writes an error log message.
- ///
- /// The event id associated with the log.
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogError(0, exception, "Error while processing request from {Address}", address)
- public static void LogError(EventId eventId, Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogError(eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- /// The event id associated with the log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogError(0, "Processing request from {Address}", address)
- public static void LogError(EventId eventId, string message, params object[] args)
- {
- LoggerInstance.LogError(eventId, message, args);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// >
- /// Logger.LogError(exception, "Error while processing request from {Address}", address)
- public static void LogError(Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogError(exception, message, args);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogError("Processing request from {Address}", address)
- public static void LogError(string message, params object[] args)
- {
- LoggerInstance.LogError(message, args);
- }
-
- #endregion
-
- #region Critical
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// The event id associated with the log.
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogCritical(0, exception, "Error while processing request from {Address}", address)
- public static void LogCritical(EventId eventId, Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogCritical(eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// The event id associated with the log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogCritical(0, "Processing request from {Address}", address)
- public static void LogCritical(EventId eventId, string message, params object[] args)
- {
- LoggerInstance.LogCritical(eventId, message, args);
- }
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// The exception to log.
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogCritical(exception, "Error while processing request from {Address}", address)
- public static void LogCritical(Exception exception, string message, params object[] args)
- {
- LoggerInstance.LogCritical(exception, message, args);
- }
-
- ///
- /// Formats and writes a critical log message.
- ///
- ///
- /// Format string of the log message in message template format. Example:
- /// "User {User} logged in from {Address}"
- ///
- /// An object array that contains zero or more objects to format.
- /// Logger.LogCritical("Processing request from {Address}", address)
- public static void LogCritical(string message, params object[] args)
- {
- LoggerInstance.LogCritical(message, args);
- }
-
- #endregion
-
- #region Log
+ #region ILogger Interface Implementation
///
- /// Formats and writes a log message at the specified log level.
+ /// Begins a logical operation scope.
///
- /// Entry will be written on this level.
- /// Format string of the log message.
- /// An object array that contains zero or more objects to format.
- public static void Log(LogLevel logLevel, string message, params object[] args)
+ /// The type of state to begin scope for.
+ /// The identifier for the scope.
+ /// An that ends the logical operation scope on dispose.
+ public IDisposable BeginScope(TState state)
{
- LoggerInstance.Log(logLevel, message, args);
+ return LoggerInstance.BeginScope(state);
}
///
- /// Formats and writes a log message at the specified log level.
+ /// Checks if the given is enabled.
///
- /// Entry will be written on this level.
- /// The event id associated with the log.
- /// Format string of the log message.
- /// An object array that contains zero or more objects to format.
- public static void Log(LogLevel logLevel, EventId eventId, string message, params object[] args)
+ /// Level to be checked.
+ /// true if enabled.
+ public bool IsEnabled(LogLevel logLevel)
{
- LoggerInstance.Log(logLevel, eventId, message, args);
+ return LoggerInstance.IsEnabled(logLevel);
}
///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// The exception to log.
- /// Format string of the log message.
- /// An object array that contains zero or more objects to format.
- public static void Log(LogLevel logLevel, Exception exception, string message, params object[] args)
- {
- LoggerInstance.Log(logLevel, exception, message, args);
- }
-
- ///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message.
- /// An object array that contains zero or more objects to format.
- public static void Log(LogLevel logLevel, EventId eventId, Exception exception, string message,
- params object[] args)
- {
- LoggerInstance.Log(logLevel, eventId, exception, message, args);
- }
-
- ///
- /// Writes a log entry.
+ /// Writes a log entry.
///
/// The type of the object to be written.
/// Entry will be written on this level.
@@ -610,603 +122,11 @@ public static void Log(LogLevel logLevel, EventId eventId, Exception exception,
/// Function to create a message of the
/// and .
///
- public static void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception,
+ public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func formatter)
{
LoggerInstance.Log(logLevel, eventId, state, exception, formatter);
}
#endregion
-
- #endregion
-
- #region JSON Logger Methods
-
- ///
- /// Formats and writes a trace log message as JSON.
- ///
- /// The object to be serialized as JSON.
- /// logger.LogTrace(new {User = user, Address = address})
- public static void LogTrace(object message)
- {
- LoggerInstance.LogTrace(message);
- }
-
- ///
- /// Formats and writes an trace log message.
- ///
- /// The exception to log.
- /// logger.LogTrace(exception)
- public static void LogTrace(Exception exception)
- {
- LoggerInstance.LogTrace(exception);
- }
-
- ///
- /// Formats and writes a debug log message as JSON.
- ///
- /// The object to be serialized as JSON.
- /// logger.LogDebug(new {User = user, Address = address})
- public static void LogDebug(object message)
- {
- LoggerInstance.LogDebug(message);
- }
-
- ///
- /// Formats and writes an debug log message.
- ///
- /// The exception to log.
- /// logger.LogDebug(exception)
- public static void LogDebug(Exception exception)
- {
- LoggerInstance.LogDebug(exception);
- }
-
- ///
- /// Formats and writes an information log message as JSON.
- ///
- /// The object to be serialized as JSON.
- /// logger.LogInformation(new {User = user, Address = address})
- public static void LogInformation(object message)
- {
- LoggerInstance.LogInformation(message);
- }
-
- ///
- /// Formats and writes an information log message.
- ///
- /// The exception to log.
- /// logger.LogInformation(exception)
- public static void LogInformation(Exception exception)
- {
- LoggerInstance.LogInformation(exception);
- }
-
- ///
- /// Formats and writes a warning log message as JSON.
- ///
- /// The object to be serialized as JSON.
- /// logger.LogWarning(new {User = user, Address = address})
- public static void LogWarning(object message)
- {
- LoggerInstance.LogWarning(message);
- }
-
- ///
- /// Formats and writes an warning log message.
- ///
- /// The exception to log.
- /// logger.LogWarning(exception)
- public static void LogWarning(Exception exception)
- {
- LoggerInstance.LogWarning(exception);
- }
-
- ///
- /// Formats and writes a error log message as JSON.
- ///
- /// The object to be serialized as JSON.
- /// logger.LogCritical(new {User = user, Address = address})
- public static void LogError(object message)
- {
- LoggerInstance.LogError(message);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- /// The exception to log.
- /// logger.LogError(exception)
- public static void LogError(Exception exception)
- {
- LoggerInstance.LogError(exception);
- }
-
- ///
- /// Formats and writes a critical log message as JSON.
- ///
- /// The object to be serialized as JSON.
- /// logger.LogCritical(new {User = user, Address = address})
- public static void LogCritical(object message)
- {
- LoggerInstance.LogCritical(message);
- }
-
- ///
- /// Formats and writes an critical log message.
- ///
- /// The exception to log.
- /// logger.LogCritical(exception)
- public static void LogCritical(Exception exception)
- {
- LoggerInstance.LogCritical(exception);
- }
-
- ///
- /// Formats and writes a log message as JSON at the specified log level.
- ///
- /// Entry will be written on this level.
- /// The object to be serialized as JSON.
- /// logger.Log(LogLevel.Information, new {User = user, Address = address})
- public static void Log(LogLevel logLevel, object message)
- {
- LoggerInstance.Log(logLevel, message);
- }
-
- ///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// The exception to log.
- /// logger.Log(LogLevel.Information, exception)
- public static void Log(LogLevel logLevel, Exception exception)
- {
- LoggerInstance.Log(logLevel, exception);
- }
-
- #endregion
-
- #region ExtraKeys Logger Methods
-
- #region Debug
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogDebug(extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void LogDebug(T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.LogDebug(extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogDebug(extraKeys, 0, "Processing request from {Address}", address)
- public static void LogDebug(T extraKeys, EventId eventId, string message, params object[] args) where T : class
- {
- LoggerInstance.LogDebug(extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogDebug(extraKeys, exception, "Error while processing request from {Address}", address)
- public static void LogDebug(T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogDebug(extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes a debug log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogDebug(extraKeys, "Processing request from {Address}", address)
- public static void LogDebug(T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.LogDebug(extraKeys, message, args);
- }
-
- #endregion
-
- #region Trace
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogTrace(extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void LogTrace(T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.LogTrace(extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogTrace(extraKeys, 0, "Processing request from {Address}", address)
- public static void LogTrace(T extraKeys, EventId eventId, string message, params object[] args) where T : class
- {
- LoggerInstance.LogTrace(extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogTrace(extraKeys, exception, "Error while processing request from {Address}", address)
- public static void LogTrace(T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogTrace(extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes a trace log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogTrace(extraKeys, "Processing request from {Address}", address)
- public static void LogTrace(T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.LogTrace(extraKeys, message, args);
- }
-
- #endregion
-
- #region Information
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogInformation(extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void LogInformation(T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.LogInformation(extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogInformation(extraKeys, 0, "Processing request from {Address}", address)
- public static void LogInformation(T extraKeys, EventId eventId, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogInformation(extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogInformation(extraKeys, exception, "Error while processing request from {Address}", address)
- public static void LogInformation(T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogInformation(extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes an informational log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogInformation(extraKeys, "Processing request from {Address}", address)
- public static void LogInformation(T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.LogInformation(extraKeys, message, args);
- }
-
- #endregion
-
- #region Warning
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogWarning(extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void LogWarning(T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.LogWarning(extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogWarning(extraKeys, 0, "Processing request from {Address}", address)
- public static void LogWarning(T extraKeys, EventId eventId, string message, params object[] args) where T : class
- {
- LoggerInstance.LogWarning(extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogWarning(extraKeys, exception, "Error while processing request from {Address}", address)
- public static void LogWarning(T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogWarning(extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes a warning log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogWarning(extraKeys, "Processing request from {Address}", address)
- public static void LogWarning(T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.LogWarning(extraKeys, message, args);
- }
-
- #endregion
-
- #region Error
-
- ///
- /// Formats and writes an error log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogError(extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void LogError(T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.LogError(extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogError(extraKeys, 0, "Processing request from {Address}", address)
- public static void LogError(T extraKeys, EventId eventId, string message, params object[] args) where T : class
- {
- LoggerInstance.LogError(extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogError(extraKeys, exception, "Error while processing request from {Address}", address)
- public static void LogError(T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogError(extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes an error log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogError(extraKeys, "Processing request from {Address}", address)
- public static void LogError(T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.LogError(extraKeys, message, args);
- }
-
- #endregion
-
- #region Critical
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogCritical(extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void LogCritical(T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.LogCritical(extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogCritical(extraKeys, 0, "Processing request from {Address}", address)
- public static void LogCritical(T extraKeys, EventId eventId, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogCritical(extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogCritical(extraKeys, exception, "Error while processing request from {Address}", address)
- public static void LogCritical(T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.LogCritical(extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes a critical log message.
- ///
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.LogCritical(extraKeys, "Processing request from {Address}", address)
- public static void LogCritical(T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.LogCritical(extraKeys, message, args);
- }
-
- #endregion
-
- #region Log
-
- ///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.Log(LogLevel.Information, extraKeys, 0, exception, "Error while processing request from {Address}", address)
- public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, Exception exception, string message,
- params object[] args) where T : class
- {
- LoggerInstance.Log(logLevel, extraKeys, eventId, exception, message, args);
- }
-
- ///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// Additional keys will be appended to the log entry.
- /// The event id associated with the log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.Log(LogLevel.Information, extraKeys, 0, "Processing request from {Address}", address)
- public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, string message, params object[] args)
- where T : class
- {
- LoggerInstance.Log(logLevel, extraKeys, eventId, message, args);
- }
-
- ///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// Additional keys will be appended to the log entry.
- /// The exception to log.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.Log(LogLevel.Information, extraKeys, exception, "Error while processing request from {Address}", address)
- public static void Log(LogLevel logLevel, T extraKeys, Exception exception, string message, params object[] args)
- where T : class
- {
- LoggerInstance.Log(logLevel, extraKeys, exception, message, args);
- }
-
- ///
- /// Formats and writes a log message at the specified log level.
- ///
- /// Entry will be written on this level.
- /// Additional keys will be appended to the log entry.
- /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}"
- /// An object array that contains zero or more objects to format.
- /// logger.Log(LogLevel.Information, extraKeys, "Processing request from {Address}", address)
- public static void Log(LogLevel logLevel, T extraKeys, string message, params object[] args) where T : class
- {
- LoggerInstance.Log(logLevel, extraKeys, message, args);
- }
-
- #endregion
-
- #endregion
-
- #region Custom Log Formatter
-
- ///
- /// Set the log formatter.
- ///
- /// The log formatter.
- /// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor
- public static void UseFormatter(ILogFormatter logFormatter)
- {
- _logFormatter = logFormatter ?? throw new ArgumentNullException(nameof(logFormatter));
- }
-
- ///
- /// Set the log formatter to default.
- ///
- public static void UseDefaultFormatter()
- {
- _logFormatter = null;
- }
-
- ///
- /// Returns the log formatter.
- ///
- internal static ILogFormatter GetFormatter() => _logFormatter;
-
- #endregion
}
\ No newline at end of file
From 477ad84f403853bd9e5bacde1b4bda9c3e21dcf5 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Mon, 17 Mar 2025 19:24:33 +0000
Subject: [PATCH 02/49] refactor: change Logger class to static and enhance
logging capabilities
---
.../AWS.Lambda.Powertools.Logging.csproj | 1 +
.../BuilderExtensions.cs | 60 ++++++
.../Internal/LoggerProvider.cs | 120 +++++++++--
.../Internal/LoggingAspect.cs | 150 +++++++-------
.../Internal/LoggingAspectFactory.cs | 2 +-
.../PowertoolsConfigurationsExtension.cs | 119 +++++------
.../Internal/PowertoolsLogger.cs | 66 ++----
.../Logger.Formatter.cs | 4 +-
.../Logger.JsonLogs.cs | 2 +-
.../Logger.Scope.cs | 12 +-
.../Logger.StandardLogs.cs | 33 +--
.../AWS.Lambda.Powertools.Logging/Logger.cs | 190 +++++++++---------
.../LoggerExtensions.cs | 15 ++
...on.cs => PowertoolsLoggerConfiguration.cs} | 10 +-
.../PowertoolsLoggerFactory.cs | 73 +++++++
.../PowertoolsLoggerFactoryBuilder.cs | 83 ++++++++
libraries/src/Directory.Packages.props | 7 +-
17 files changed, 607 insertions(+), 340 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
rename libraries/src/AWS.Lambda.Powertools.Logging/{LoggerConfiguration.cs => PowertoolsLoggerConfiguration.cs} (88%)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj
index a4a1478f..ccf8c3ea 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj
@@ -15,6 +15,7 @@
+
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
new file mode 100644
index 00000000..0b977275
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -0,0 +1,60 @@
+using System;
+using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public static class BuilderExtensions
+{
+ // Track if we're in the middle of configuration to prevent recursion
+ private static bool _configuring = false;
+
+ // Single base method that all other overloads call
+ public static ILoggingBuilder AddPowertoolsLogger(
+ this ILoggingBuilder builder,
+ Action? configure = null,
+ bool fromLoggerConfigure = false)
+ {
+ // Add configuration
+ builder.AddConfiguration();
+
+ // Register the provider
+ builder.Services.TryAddEnumerable(
+ ServiceDescriptor.Singleton());
+
+ LoggerProviderOptions.RegisterProviderOptions
+ (builder.Services);
+
+ // Apply configuration if provided
+ if (configure != null)
+ {
+ // Create and apply configuration
+ var options = new PowertoolsLoggerConfiguration();
+ configure(options);
+
+ // Configure options for DI
+ builder.Services.Configure(configure);
+
+ // Configure static Logger (if not already in a configuration cycle)
+ if (!fromLoggerConfigure && !_configuring)
+ {
+ try
+ {
+ _configuring = true;
+ Logger.Configure(options);
+ }
+ finally
+ {
+ _configuring = false;
+ }
+ }
+ }
+
+ return builder;
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
index 94bb1c0d..09735162 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
@@ -13,8 +13,10 @@
* permissions and limitations under the License.
*/
+using System;
using System.Collections.Concurrent;
using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Serializers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -25,13 +27,14 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
/// Implements the
///
///
-public sealed class LoggerProvider : ILoggerProvider
+[ProviderAlias("PowertoolsLogger")]
+internal sealed class LoggerProvider : ILoggerProvider
{
///
/// The powertools configurations
///
private readonly IPowertoolsConfigurations _powertoolsConfigurations;
-
+
///
/// The system wrapper
///
@@ -40,8 +43,10 @@ public sealed class LoggerProvider : ILoggerProvider
///
/// The loggers
///
- private readonly ConcurrentDictionary _loggers = new();
+ private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase);
+ private readonly IDisposable? _onChangeToken;
+ private PowertoolsLoggerConfiguration _currentConfig;
///
/// Initializes a new instance of the class.
@@ -49,19 +54,28 @@ public sealed class LoggerProvider : ILoggerProvider
/// The configuration.
///
///
- public LoggerProvider(IOptions config, IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper)
+ public LoggerProvider(IOptionsMonitor config,
+ IPowertoolsConfigurations powertoolsConfigurations,
+ ISystemWrapper systemWrapper)
{
+ _currentConfig = config.CurrentValue;
_powertoolsConfigurations = powertoolsConfigurations;
_systemWrapper = systemWrapper;
- _powertoolsConfigurations.SetCurrentConfig(config?.Value, systemWrapper);
+ _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
+
+ // TODO: FIx this
+ // It was moved bellow
+ // _powertoolsConfigurations.SetCurrentConfig(_currentConfig, systemWrapper);
}
-
+
///
/// Initializes a new instance of the class.
///
/// The configuration.
- public LoggerProvider(IOptions config)
- : this(config, PowertoolsConfigurations.Instance, SystemWrapper.Instance) { }
+ public LoggerProvider(IOptionsMonitor config)
+ : this(config, PowertoolsConfigurations.Instance, SystemWrapper.Instance)
+ {
+ }
///
/// Creates a new instance.
@@ -70,10 +84,91 @@ public LoggerProvider(IOptions config)
/// The instance of that was created.
public ILogger CreateLogger(string categoryName)
{
- return _loggers.GetOrAdd(categoryName,
- name => PowertoolsLogger.CreateLogger(name,
- _powertoolsConfigurations,
- _systemWrapper));
+ return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger(name,
+ GetCurrentConfig,
+ _systemWrapper));
+ }
+
+ private PowertoolsLoggerConfiguration GetCurrentConfig()
+ {
+ var config = _currentConfig;
+
+ ApplyPowertoolsConfig(config);
+
+ return config;
+ }
+
+ private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
+ {
+ var logLevel = _powertoolsConfigurations.GetLogLevel(config.MinimumLevel);
+ var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
+ var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
+
+ if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel)
+ {
+ _systemWrapper.LogLine(
+ $"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.");
+ }
+
+ // // Set service
+ config.Service ??= _powertoolsConfigurations.Service;
+
+
+ // // Set output case
+ if (config.LoggerOutputCase == LoggerOutputCase.Default)
+ {
+ var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(config.LoggerOutputCase);
+ config.LoggerOutputCase = loggerOutputCase;
+ // TODO: Fix this
+ }
+
+ PowertoolsLoggingSerializer.ConfigureNamingPolicy(config.LoggerOutputCase);
+
+ //
+
+ //
+ // // Set log level
+ // var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
+ // config.MinimumLevel = minLogLevel;
+
+ config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
+ config.LoggerOutputCase == LoggerOutputCase.PascalCase
+ ? "LogLevel"
+ : LoggingConstants.KeyLogLevel;
+
+ // Set sampling rate
+ // var samplingRate = config.SamplingRate > 0 ? config.SamplingRate : _powertoolsConfigurations.LoggerSampleRate;
+ // samplingRate = ValidateSamplingRate(samplingRate, minLogLevel, _systemWrapper);
+ //
+ // config.SamplingRate = samplingRate;
+ //
+ // if (samplingRate > 0)
+ // {
+ // double sample = _systemWrapper.GetRandom();
+ //
+ // if (sample <= samplingRate)
+ // {
+ // _systemWrapper.LogLine(
+ // $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
+ // config.MinimumLevel = LogLevel.Debug;
+ // }
+ // }
+ }
+
+ private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
+ {
+ if (samplingRate < 0 || samplingRate > 1)
+ {
+ if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
+ {
+ systemWrapper.LogLine(
+ $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
+ }
+
+ return 0;
+ }
+
+ return samplingRate;
}
///
@@ -82,5 +177,6 @@ public ILogger CreateLogger(string categoryName)
public void Dispose()
{
_loggers.Clear();
+ _onChangeToken?.Dispose();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index c92566e2..bf875281 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -14,6 +14,8 @@
*/
using System;
+using System.Collections.Concurrent;
+using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -49,21 +51,6 @@ public class LoggingAspect
///
private bool _clearState;
- ///
- /// The correlation identifier path
- ///
- private string _correlationIdPath;
-
- ///
- /// The Powertools for AWS Lambda (.NET) configurations
- ///
- private readonly IPowertoolsConfigurations _powertoolsConfigurations;
-
- ///
- /// The system wrapper
- ///
- private readonly ISystemWrapper _systemWrapper;
-
///
/// The is context initialized
///
@@ -73,21 +60,50 @@ public class LoggingAspect
/// Specify to clear Lambda Context on exit
///
private bool _clearLambdaContext;
+
+ private ILogger _logger;
+ private readonly bool _LogEventEnv;
+ private readonly string _xRayTraceId;
+ private bool _isDebug;
- ///
- /// The configuration
- ///
- private LoggerConfiguration _config;
///
/// Initializes a new instance of the class.
///
/// The Powertools configurations.
- /// The system wrapper.
- public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper)
+ public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
{
- _powertoolsConfigurations = powertoolsConfigurations;
- _systemWrapper = systemWrapper;
+ _LogEventEnv = powertoolsConfigurations.LoggerLogEvent;
+ _xRayTraceId = powertoolsConfigurations.XRayTraceId;
+ }
+
+ private void InitializeLogger(LoggingAttribute trigger)
+ {
+ // Always configure when we have explicit trigger settings
+ bool hasExplicitSettings = (trigger.LogLevel != LogLevel.None ||
+ !string.IsNullOrEmpty(trigger.Service) ||
+ trigger.LoggerOutputCase != default ||
+ trigger.SamplingRate > 0);
+
+ // Configure logger if not configured or we have explicit settings
+ if (!Logger.IsConfigured || hasExplicitSettings)
+ {
+ // Create configuration with default values when not explicitly specified
+ Logger.Configure(new PowertoolsLoggerConfiguration
+ {
+ // Use sensible defaults if not specified in the attribute
+ MinimumLevel = trigger.LogLevel != LogLevel.None ? trigger.LogLevel : LogLevel.Information,
+ Service = !string.IsNullOrEmpty(trigger.Service) ? trigger.Service : "service_undefined",
+ LoggerOutputCase = trigger.LoggerOutputCase != default ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
+ SamplingRate = trigger.SamplingRate > 0 ? trigger.SamplingRate : 1.0
+ });
+ }
+
+ // Get logger after configuration
+ _logger = Logger.GetLogger();
+
+ // TODO: Fix this
+ // _isDebug = config.MinimumLevel <= LogLevel.Debug;
}
///
@@ -126,24 +142,18 @@ public void OnEntry(
Triggers = triggers
};
- _config = new LoggerConfiguration
- {
- Service = trigger.Service,
- LoggerOutputCase = trigger.LoggerOutputCase,
- SamplingRate = trigger.SamplingRate,
- MinimumLevel = trigger.LogLevel
- };
var logEvent = trigger.LogEvent;
- _correlationIdPath = trigger.CorrelationIdPath;
_clearState = trigger.ClearState;
- Logger.LoggerProvider = new LoggerProvider(_config, _powertoolsConfigurations, _systemWrapper);
+ InitializeLogger(trigger);
if (!_initializeContext)
return;
+
+ _isDebug = LogLevel.Debug >= trigger.LogLevel;
- Logger.AppendKey(LoggingConstants.KeyColdStart, _isColdStart);
+ _logger.AppendKey(LoggingConstants.KeyColdStart, _isColdStart);
_isColdStart = false;
_initializeContext = false;
@@ -152,8 +162,8 @@ public void OnEntry(
var eventObject = eventArgs.Args.FirstOrDefault();
CaptureXrayTraceId();
CaptureLambdaContext(eventArgs);
- CaptureCorrelationId(eventObject);
- if (logEvent || _powertoolsConfigurations.LoggerLogEvent)
+ CaptureCorrelationId(eventObject, trigger.CorrelationIdPath);
+ if (logEvent || _LogEventEnv)
LogEvent(eventObject);
}
catch (Exception exception)
@@ -175,32 +185,18 @@ public void OnExit()
if (_clearLambdaContext)
LoggingLambdaContext.Clear();
if (_clearState)
- Logger.RemoveAllKeys();
+ _logger.RemoveAllKeys();
_initializeContext = true;
}
- ///
- /// Determines whether this instance is debug.
- ///
- /// true if this instance is debug; otherwise, false.
- private bool IsDebug()
- {
- return LogLevel.Debug >= _powertoolsConfigurations.GetLogLevel(_config.MinimumLevel);
- }
-
///
/// Captures the xray trace identifier.
///
private void CaptureXrayTraceId()
{
- var xRayTraceId = _powertoolsConfigurations.XRayTraceId;
- if (string.IsNullOrWhiteSpace(xRayTraceId))
+ if (string.IsNullOrWhiteSpace(_xRayTraceId))
return;
-
- xRayTraceId = xRayTraceId
- .Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", "");
-
- Logger.AppendKey(LoggingConstants.KeyXRayTraceId, xRayTraceId);
+ _logger.AppendKey(LoggingConstants.KeyXRayTraceId, _xRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""));
}
///
@@ -213,8 +209,8 @@ private void CaptureXrayTraceId()
private void CaptureLambdaContext(AspectEventArgs eventArgs)
{
_clearLambdaContext = LoggingLambdaContext.Extract(eventArgs);
- if (LoggingLambdaContext.Instance is null && IsDebug())
- _systemWrapper.LogLine(
+ if (LoggingLambdaContext.Instance is null && _isDebug)
+ Debug.WriteLine(
"Skipping Lambda Context injection because ILambdaContext context parameter not found.");
}
@@ -222,12 +218,12 @@ private void CaptureLambdaContext(AspectEventArgs eventArgs)
/// Captures the correlation identifier.
///
/// The event argument.
- private void CaptureCorrelationId(object eventArg)
+ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
{
- if (string.IsNullOrWhiteSpace(_correlationIdPath))
+ if (string.IsNullOrWhiteSpace(correlationIdPath))
return;
- var correlationIdPaths = _correlationIdPath
+ var correlationIdPaths = correlationIdPath
.Split(CorrelationIdPaths.Separator, StringSplitOptions.RemoveEmptyEntries);
if (!correlationIdPaths.Any())
@@ -235,8 +231,8 @@ private void CaptureCorrelationId(object eventArg)
if (eventArg is null)
{
- if (IsDebug())
- _systemWrapper.LogLine(
+ if (_isDebug)
+ Debug.WriteLine(
"Skipping CorrelationId capture because event parameter not found.");
return;
}
@@ -254,23 +250,25 @@ private void CaptureCorrelationId(object eventArg)
{
// For casing parsing to be removed from Logging v2 when we get rid of outputcase
// without this CorrelationIdPaths.ApiGatewayRest would not work
- var pathWithOutputCase =
- _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase);
- if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
- break;
-
- element = childElement;
+
+ // TODO: fix this
+ // var pathWithOutputCase =
+ // _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase);
+ // if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
+ // break;
+ //
+ // element = childElement;
if (i == correlationIdPaths.Length - 1)
correlationId = element.ToString();
}
if (!string.IsNullOrWhiteSpace(correlationId))
- Logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId);
+ _logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId);
}
catch (Exception e)
{
- if (IsDebug())
- _systemWrapper.LogLine(
+ if (_isDebug)
+ Debug.WriteLine(
$"Skipping CorrelationId capture because of error caused while parsing the event object {e.Message}.");
}
}
@@ -285,30 +283,30 @@ private void LogEvent(object eventArg)
{
case null:
{
- if (IsDebug())
- _systemWrapper.LogLine(
+ if (_isDebug)
+ Debug.WriteLine(
"Skipping Event Log because event parameter not found.");
break;
}
case Stream:
try
{
- Logger.LogInformation(eventArg);
+ _logger.LogInformation(eventArg);
}
catch (Exception e)
{
- Logger.LogError(e, "Failed to log event from supplied input stream.");
+ _logger.LogError(e, "Failed to log event from supplied input stream.");
}
break;
default:
try
{
- Logger.LogInformation(eventArg);
+ _logger.LogInformation(eventArg);
}
catch (Exception e)
{
- Logger.LogError(e, "Failed to log event from supplied input object.");
+ _logger.LogError(e, "Failed to log event from supplied input object.");
}
break;
@@ -321,8 +319,6 @@ private void LogEvent(object eventArg)
internal static void ResetForTest()
{
LoggingLambdaContext.Clear();
- Logger.LoggerProvider = null;
- Logger.RemoveAllKeys();
- Logger.ClearLoggerInstance();
+ // _logger.RemoveAllKeys();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
index 5feae3cf..72ea5645 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
@@ -30,6 +30,6 @@ internal static class LoggingAspectFactory
/// An instance of the LoggingAspect class.
public static object GetInstance(Type type)
{
- return new LoggingAspect(PowertoolsConfigurations.Instance, SystemWrapper.Instance);
+ return new LoggingAspect(PowertoolsConfigurations.Instance);
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs
index 148bb540..d513c185 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs
@@ -29,7 +29,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
internal static class PowertoolsConfigurationsExtension
{
private static readonly object _lock = new object();
- private static LoggerConfiguration _config;
+ private static PowertoolsLoggerConfiguration _config;
///
/// Maps AWS log level to .NET log level
@@ -95,37 +95,37 @@ internal static LoggerOutputCase GetLoggerOutputCase(this IPowertoolsConfigurati
/// Gets the current configuration.
///
/// AWS.Lambda.Powertools.Logging.LoggerConfiguration.
- internal static void SetCurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations, LoggerConfiguration config, ISystemWrapper systemWrapper)
- {
- lock (_lock)
- {
- _config = config ?? new LoggerConfiguration();
-
- var logLevel = powertoolsConfigurations.GetLogLevel(_config.MinimumLevel);
- var lambdaLogLevel = powertoolsConfigurations.GetLambdaLogLevel();
- var lambdaLogLevelEnabled = powertoolsConfigurations.LambdaLogLevelEnabled();
-
- if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel)
- {
- systemWrapper.LogLine($"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.");
- }
-
- // Set service
- _config.Service = _config.Service ?? powertoolsConfigurations.Service;
-
- // Set output case
- var loggerOutputCase = powertoolsConfigurations.GetLoggerOutputCase(_config.LoggerOutputCase);
- _config.LoggerOutputCase = loggerOutputCase;
- PowertoolsLoggingSerializer.ConfigureNamingPolicy(loggerOutputCase);
-
- // Set log level
- var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
- _config.MinimumLevel = minLogLevel;
-
- // Set sampling rate
- SetSamplingRate(powertoolsConfigurations, systemWrapper, minLogLevel);
- }
- }
+ // internal static void SetCurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations, LoggerConfiguration config, ISystemWrapper systemWrapper)
+ // {
+ // lock (_lock)
+ // {
+ // _config = config ?? new LoggerConfiguration();
+ //
+ // var logLevel = powertoolsConfigurations.GetLogLevel(_config.MinimumLevel);
+ // var lambdaLogLevel = powertoolsConfigurations.GetLambdaLogLevel();
+ // var lambdaLogLevelEnabled = powertoolsConfigurations.LambdaLogLevelEnabled();
+ //
+ // if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel)
+ // {
+ // systemWrapper.LogLine($"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.");
+ // }
+ //
+ // // Set service
+ // _config.Service = _config.Service ?? powertoolsConfigurations.Service;
+ //
+ // // Set output case
+ // var loggerOutputCase = powertoolsConfigurations.GetLoggerOutputCase(_config.LoggerOutputCase);
+ // _config.LoggerOutputCase = loggerOutputCase;
+ // PowertoolsLoggingSerializer.ConfigureNamingPolicy(loggerOutputCase);
+ //
+ // // Set log level
+ // var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
+ // _config.MinimumLevel = minLogLevel;
+ //
+ // // Set sampling rate
+ // SetSamplingRate(powertoolsConfigurations, systemWrapper, minLogLevel);
+ // }
+ // }
///
/// Set sampling rate
@@ -134,24 +134,24 @@ internal static void SetCurrentConfig(this IPowertoolsConfigurations powertoolsC
///
///
///
- private static void SetSamplingRate(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper, LogLevel minLogLevel)
- {
- var samplingRate = _config.SamplingRate > 0 ? _config.SamplingRate : powertoolsConfigurations.LoggerSampleRate;
- samplingRate = ValidateSamplingRate(samplingRate, minLogLevel, systemWrapper);
-
- _config.SamplingRate = samplingRate;
-
- if (samplingRate > 0)
- {
- double sample = systemWrapper.GetRandom();
-
- if (sample <= samplingRate)
- {
- systemWrapper.LogLine($"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
- _config.MinimumLevel = LogLevel.Debug;
- }
- }
- }
+ // private static void SetSamplingRate(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper, LogLevel minLogLevel)
+ // {
+ // var samplingRate = _config.SamplingRate > 0 ? _config.SamplingRate : powertoolsConfigurations.LoggerSampleRate;
+ // samplingRate = ValidateSamplingRate(samplingRate, minLogLevel, systemWrapper);
+ //
+ // _config.SamplingRate = samplingRate;
+ //
+ // if (samplingRate > 0)
+ // {
+ // double sample = systemWrapper.GetRandom();
+ //
+ // if (sample <= samplingRate)
+ // {
+ // systemWrapper.LogLine($"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
+ // _config.MinimumLevel = LogLevel.Debug;
+ // }
+ // }
+ // }
///
/// Validate Sampling rate
@@ -309,23 +309,4 @@ private static string ToCamelCase(string input)
return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1);
}
- ///
- /// Determines whether [is log level enabled].
- ///
- /// The Powertools for AWS Lambda (.NET) configurations.
- /// The log level.
- /// true if [is log level enabled]; otherwise, false.
- internal static bool IsLogLevelEnabled(this IPowertoolsConfigurations powertoolsConfigurations, LogLevel logLevel)
- {
- return logLevel != LogLevel.None && logLevel >= _config.MinimumLevel;
- }
-
- ///
- /// Gets the current configuration.
- ///
- /// AWS.Lambda.Powertools.Logging.LoggerConfiguration.
- internal static LoggerConfiguration CurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations)
- {
- return _config;
- }
}
\ 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 6e72d102..3c6ae09a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -34,18 +34,19 @@ internal sealed class PowertoolsLogger : ILogger
///
/// The name
///
- private readonly string _name;
+ private readonly string _categoryName;
///
/// The current configuration
///
- private readonly IPowertoolsConfigurations _powertoolsConfigurations;
+ private readonly PowertoolsLoggerConfiguration _currentConfig;
///
/// The system wrapper
///
private readonly ISystemWrapper _systemWrapper;
+
///
/// The current scope
///
@@ -54,32 +55,20 @@ internal sealed class PowertoolsLogger : ILogger
///
/// Private constructor - Is initialized on CreateLogger
///
- /// The name.
+ /// The name.
/// The Powertools for AWS Lambda (.NET) configurations.
/// The system wrapper.
- private PowertoolsLogger(
- string name,
- IPowertoolsConfigurations powertoolsConfigurations,
+ public PowertoolsLogger(
+ string categoryName,
+ Func getCurrentConfig,
ISystemWrapper systemWrapper)
{
- _name = name;
- _powertoolsConfigurations = powertoolsConfigurations;
+ _categoryName = categoryName;
+ _currentConfig = getCurrentConfig();
_systemWrapper = systemWrapper;
- _powertoolsConfigurations.SetExecutionEnvironment(this);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name.
- /// The Powertools for AWS Lambda (.NET) configurations.
- /// The system wrapper.
- internal static PowertoolsLogger CreateLogger(string name,
- IPowertoolsConfigurations powertoolsConfigurations,
- ISystemWrapper systemWrapper)
- {
- return new PowertoolsLogger(name, powertoolsConfigurations, systemWrapper);
+ // TODO: Fix
+ // _powertoolsConfigurations.SetExecutionEnvironment(this);
}
///
@@ -108,7 +97,7 @@ internal void EndScope()
/// The log level.
/// bool.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsEnabled(LogLevel logLevel) => _powertoolsConfigurations.IsLogLevelEnabled(logLevel);
+ public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None && logLevel >= _currentConfig.MinimumLevel;
///
/// Writes a log entry.
@@ -175,15 +164,13 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
}
}
- var keyLogLevel = GetLogLevelKey();
-
logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString("o"));
- logEntry.TryAdd(keyLogLevel, logLevel.ToString());
- logEntry.TryAdd(LoggingConstants.KeyService, _powertoolsConfigurations.CurrentConfig().Service);
- logEntry.TryAdd(LoggingConstants.KeyLoggerName, _name);
+ logEntry.TryAdd(_currentConfig.LogLevelKey, logLevel.ToString());
+ logEntry.TryAdd(LoggingConstants.KeyService, _currentConfig.Service);
+ logEntry.TryAdd(LoggingConstants.KeyLoggerName, _categoryName);
logEntry.TryAdd(LoggingConstants.KeyMessage, message);
- if (_powertoolsConfigurations.CurrentConfig().SamplingRate > 0)
- logEntry.TryAdd(LoggingConstants.KeySamplingRate, _powertoolsConfigurations.CurrentConfig().SamplingRate);
+ if (_currentConfig.SamplingRate > 0)
+ logEntry.TryAdd(LoggingConstants.KeySamplingRate, _currentConfig.SamplingRate);
if (exception != null)
logEntry.TryAdd(LoggingConstants.KeyException, exception);
@@ -208,11 +195,11 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
{
Timestamp = timestamp,
Level = logLevel,
- Service = _powertoolsConfigurations.CurrentConfig().Service,
- Name = _name,
+ Service = _currentConfig.Service,
+ Name = _categoryName,
Message = message,
Exception = exception,
- SamplingRate = _powertoolsConfigurations.CurrentConfig().SamplingRate,
+ SamplingRate = _currentConfig.SamplingRate,
};
var extraKeys = new Dictionary();
@@ -311,19 +298,6 @@ private static bool CustomFormatter(TState state, Exception exception, o
return true;
}
- ///
- /// Gets the log level key.
- ///
- /// System.String.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private string GetLogLevelKey()
- {
- return _powertoolsConfigurations.LambdaLogLevelEnabled() &&
- _powertoolsConfigurations.CurrentConfig().LoggerOutputCase == LoggerOutputCase.PascalCase
- ? "LogLevel"
- : LoggingConstants.KeyLogLevel;
- }
-
///
/// Adds the lambda context keys.
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
index 62a5bb51..515ff2f0 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
@@ -18,8 +18,10 @@
namespace AWS.Lambda.Powertools.Logging;
-public partial class Logger
+public static partial class Logger
{
+ private static ILogFormatter _logFormatter;
+
#region Custom Log Formatter
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs
index 680f766e..1221a282 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs
@@ -18,7 +18,7 @@
namespace AWS.Lambda.Powertools.Logging;
-public partial class Logger
+public static partial class Logger
{
#region JSON Logger Methods
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
index d5561327..94642122 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
@@ -20,9 +20,13 @@
namespace AWS.Lambda.Powertools.Logging;
-public partial class Logger
+public static partial class Logger
{
- #region Scope Variables
+ ///
+ /// Gets the scope.
+ ///
+ /// The scope.
+ private static IDictionary Scope { get; } = new Dictionary(StringComparer.Ordinal);
///
/// Appending additional key to the log context.
@@ -68,7 +72,7 @@ public static void AppendKeys(IEnumerable> keys)
/// Remove additional keys from the log context.
///
/// The list of keys.
- public static void RemoveKeys(params string[] keys)
+ public static void RemoveKeys(params string[] keys)
{
if (keys == null) return;
foreach (var key in keys)
@@ -92,6 +96,4 @@ internal static void RemoveAllKeys()
{
Scope.Clear();
}
-
- #endregion
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs
index c403a8ef..f157c0cb 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs
@@ -18,12 +18,8 @@
namespace AWS.Lambda.Powertools.Logging;
-public partial class Logger
+public static partial class Logger
{
- #region Core Logger Methods
-
- #region Debug
-
///
/// Formats and writes a debug log message.
///
@@ -84,10 +80,6 @@ public static void LogDebug(string message, params object[] args)
LoggerInstance.LogDebug(message, args);
}
- #endregion
-
- #region Trace
-
///
/// Formats and writes a trace log message.
///
@@ -148,10 +140,6 @@ public static void LogTrace(string message, params object[] args)
LoggerInstance.LogTrace(message, args);
}
- #endregion
-
- #region Information
-
///
/// Formats and writes an informational log message.
///
@@ -212,10 +200,6 @@ public static void LogInformation(string message, params object[] args)
LoggerInstance.LogInformation(message, args);
}
- #endregion
-
- #region Warning
-
///
/// Formats and writes a warning log message.
///
@@ -276,10 +260,6 @@ public static void LogWarning(string message, params object[] args)
LoggerInstance.LogWarning(message, args);
}
- #endregion
-
- #region Error
-
///
/// Formats and writes an error log message.
///
@@ -340,10 +320,6 @@ public static void LogError(string message, params object[] args)
LoggerInstance.LogError(message, args);
}
- #endregion
-
- #region Critical
-
///
/// Formats and writes a critical log message.
///
@@ -404,10 +380,6 @@ public static void LogCritical(string message, params object[] args)
LoggerInstance.LogCritical(message, args);
}
- #endregion
-
- #region Log
-
///
/// Formats and writes a log message at the specified log level.
///
@@ -457,7 +429,4 @@ public static void Log(LogLevel logLevel, EventId eventId, Exception exception,
LoggerInstance.Log(logLevel, eventId, exception, message, args);
}
- #endregion
-
- #endregion
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index e0aa1266..9059feaf 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -14,10 +14,10 @@
*/
using System;
-using System.Collections.Generic;
-using System.Linq;
+using System.Threading;
using AWS.Lambda.Powertools.Logging.Internal;
-using AWS.Lambda.Powertools.Logging.Internal.Helpers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging;
@@ -25,108 +25,118 @@ namespace AWS.Lambda.Powertools.Logging;
///
/// Class Logger.
///
-public partial class Logger : ILogger
+public static partial class Logger
{
- ///
- /// The logger instance
- ///
- private static ILogger _loggerInstance;
-
- ///
- /// Gets the logger instance.
- ///
- /// The logger instance.
- private static ILogger LoggerInstance => _loggerInstance ??= Create();
-
- ///
- /// Gets or sets the logger provider.
- ///
- /// The logger provider.
- internal static ILoggerProvider LoggerProvider { get; set; }
+ // Use Lazy for thread-safe initialization
+ private static Lazy _factoryLazy;
+ private static Lazy _defaultLoggerLazy;
+
+ // Static constructor to ensure initialization
+ // static Logger()
+ // {
+ // // Create default configuration with sensible defaults
+ // var defaultConfig = new PowertoolsLoggerConfiguration
+ // {
+ // MinimumLevel = LogLevel.Information, // Default to Information level
+ // Service = "LambdaFunction", // Default service name
+ // LoggerOutputCase = LoggerOutputCase.SnakeCase, // Default case
+ // SamplingRate = 1.0 // Default to log everything
+ // };
+ //
+ // // Initialize with default factory
+ // _factoryLazy = new Lazy(() =>
+ // LoggerFactory.Create(builder =>
+ // builder.AddPowertoolsLogger(config =>
+ // {
+ // config.MinimumLevel = defaultConfig.MinimumLevel;
+ // config.Service = defaultConfig.Service;
+ // config.LoggerOutputCase = defaultConfig.LoggerOutputCase;
+ // config.SamplingRate = defaultConfig.SamplingRate;
+ // })),
+ // LazyThreadSafetyMode.ExecutionAndPublication);
+ //
+ // _defaultLoggerLazy = new Lazy(() =>
+ // _factoryLazy.Value.CreateLogger("PowertoolsLogger"));
+ //
+ // // Not yet explicitly configured
+ // _isConfigured = false;
+ // }
+
+ // Flag to track if custom configuration has been applied
+ private static bool _isConfigured;
+
+ // Properties to access the lazy-initialized instances
+ private static ILoggerFactory Factory => _factoryLazy.Value;
+ private static ILogger LoggerInstance => _defaultLoggerLazy.Value;
///
- /// The logger formatter instance
+ /// Indicates whether the Logger has been configured with custom settings
///
- private static ILogFormatter _logFormatter;
-
- ///
- /// Gets the scope.
- ///
- /// The scope.
- private static IDictionary Scope { get; } = new Dictionary(StringComparer.Ordinal);
-
- ///
- /// Creates a new instance.
- ///
- /// The category name for messages produced by the logger.
- /// The instance of that was created.
- /// categoryName
- public static ILogger Create(string categoryName)
- {
- if (string.IsNullOrWhiteSpace(categoryName))
- throw new ArgumentNullException(nameof(categoryName));
+ public static bool IsConfigured => _isConfigured;
- // Needed for when using Logger directly with decorator
- LoggerProvider ??= new LoggerProvider(null);
- return LoggerProvider.CreateLogger(categoryName);
- }
-
- ///
- /// Creates a new instance.
- ///
- ///
- /// The instance of that was created.
- public static ILogger Create()
+ // Allow manual configuration using options
+ public static void Configure(Action configureOptions)
{
- return Create(typeof(T).FullName);
+ var options = new PowertoolsLoggerConfiguration();
+ configureOptions(options);
+ Configure(options);
}
-
- internal static void ClearLoggerInstance()
+
+ // Configure with existing factory
+ public static void Configure(ILoggerFactory loggerFactory)
{
- _loggerInstance = null;
- }
+ Interlocked.Exchange(ref _factoryLazy,
+ new Lazy(() => loggerFactory));
- #region ILogger Interface Implementation
+ Interlocked.Exchange(ref _defaultLoggerLazy,
+ new Lazy(() => Factory.CreateLogger("PowertoolsLogger")));
- ///
- /// Begins a logical operation scope.
- ///
- /// The type of state to begin scope for.
- /// The identifier for the scope.
- /// An that ends the logical operation scope on dispose.
- public IDisposable BeginScope(TState state)
- {
- return LoggerInstance.BeginScope(state);
+ _isConfigured = true;
}
- ///
- /// Checks if the given is enabled.
- ///
- /// Level to be checked.
- /// true if enabled.
- public bool IsEnabled(LogLevel logLevel)
+ // Directly configure from a PowertoolsLoggerConfiguration
+ internal static void Configure(PowertoolsLoggerConfiguration options)
{
- return LoggerInstance.IsEnabled(logLevel);
+ if (options == null) throw new ArgumentNullException(nameof(options));
+
+ // Create a factory with our provider
+ var factory = LoggerFactory.Create(builder =>
+ {
+ // Use AddPowertoolsLogger but with fromLoggerConfigure=true to prevent recursion
+ builder.AddPowertoolsLogger(config =>
+ {
+ config.Service = options.Service;
+ config.MinimumLevel = options.MinimumLevel;
+ config.LoggerOutputCase = options.LoggerOutputCase;
+ config.SamplingRate = options.SamplingRate;
+ // Copy other properties as needed
+ }, true);
+ });
+
+ // Update factory and logger
+ Interlocked.Exchange(ref _factoryLazy,
+ new Lazy(() => factory));
+
+ Interlocked.Exchange(ref _defaultLoggerLazy,
+ new Lazy(() => Factory.CreateLogger("PowertoolsLogger")));
+
+ _isConfigured = true;
}
- ///
- /// Writes a log entry.
- ///
- /// The type of the object to be written.
- /// Entry will be written on this level.
- /// Id of the event.
- /// The entry to be written. Can be also an object.
- /// The exception related to this entry.
- ///
- /// Function to create a message of the
- /// and .
- ///
- public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception,
- Func formatter)
- {
- LoggerInstance.Log(logLevel, eventId, state, exception, formatter);
- }
- #endregion
+ // Get a logger for a specific category
+ public static ILogger GetLogger() => GetLogger(typeof(T).Name);
+
+ public static ILogger GetLogger(string category) => Factory.CreateLogger(category);
+
+ // For testing purposes
+ // internal static void Reset()
+ // {
+ // Interlocked.Exchange(ref _factoryLazy,
+ // new Lazy(() => new PowertoolsLoggerFactory()));
+
+ // Interlocked.Exchange(ref _defaultLoggerLazy,
+ // new Lazy(() => Factory.CreateLogger()));
+ // }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
index 200cf46e..e04a8fd7 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
@@ -652,4 +652,19 @@ public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, s
#endregion
#endregion
+
+ public static void AppendKey(this ILogger logger, string key, object value)
+ {
+ Logger.AppendKey(key, value);
+ }
+
+ internal static void RemoveAllKeys(this ILogger logger)
+ {
+ Logger.RemoveAllKeys();
+ }
+
+ public static void RemoveKeys(this ILogger logger, params string[] keys)
+ {
+ Logger.RemoveKeys(keys);
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
similarity index 88%
rename from libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index aab959af..b1d8dd84 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -24,14 +24,14 @@ namespace AWS.Lambda.Powertools.Logging;
///
///
///
-public class LoggerConfiguration : IOptions
+public class PowertoolsLoggerConfiguration : IOptions
{
///
/// Service name is used for logging.
/// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME.
///
/// The service.
- public string Service { get; set; }
+ public string? Service { get; set; } = null;
///
/// Specify the minimum log level for logging (Information, by default).
@@ -51,7 +51,7 @@ public class LoggerConfiguration : IOptions
/// The default configured options instance
///
/// The value.
- LoggerConfiguration IOptions.Value => this;
+ PowertoolsLoggerConfiguration IOptions.Value => this;
///
/// The logger output case.
@@ -59,4 +59,8 @@ public class LoggerConfiguration : IOptions
///
/// The logger output case.
public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default;
+
+ internal string LogLevelKey { get; set; }
+
+
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
new file mode 100644
index 00000000..a939ab9c
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
@@ -0,0 +1,73 @@
+using System;
+using AWS.Lambda.Powertools.Logging.Internal;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public sealed class PowertoolsLoggerFactory : IDisposable
+{
+ private readonly ILoggerFactory _factory;
+
+ public PowertoolsLoggerFactory(ILoggerFactory? loggerFactory = null)
+ {
+ _factory = loggerFactory ?? LoggerFactory.Create(builder =>
+ {
+ builder.AddPowertoolsLogger();
+ });
+ }
+
+ public PowertoolsLoggerFactory() : this(LoggerFactory.Create(builder => { builder.AddPowertoolsLogger(); }))
+ {
+ }
+
+ public static PowertoolsLoggerFactory Create(Action configureOptions)
+ {
+ var options = new PowertoolsLoggerConfiguration();
+ configureOptions(options);
+
+ var factory = LoggerFactory.Create(builder =>
+ {
+ builder.AddPowertoolsLogger(config =>
+ {
+ // Copy basic properties
+ config.Service = options.Service;
+ config.MinimumLevel = options.MinimumLevel;
+ config.LoggerOutputCase = options.LoggerOutputCase;
+ config.SamplingRate = options.SamplingRate;
+
+ // // Copy additional contexts using the public API
+ // foreach (var ctx in options.GetAdditionalContexts())
+ // {
+ // config.AddJsonContext(ctx);
+ // }
+ //
+ // // Copy log level colors
+ // foreach (var kvp in options.LogLevelToColorMap)
+ // {
+ // config.LogLevelToColorMap[kvp.Key] = kvp.Value;
+ // }
+ });
+ });
+
+ Logger.Configure(factory);
+ return new PowertoolsLoggerFactory(factory);
+ }
+
+ // Add builder pattern support
+ public static PowertoolsLoggerFactoryBuilder CreateBuilder()
+ {
+ return new PowertoolsLoggerFactoryBuilder();
+ }
+
+ public ILogger CreateLogger() => CreateLogger(typeof(T).FullName ?? typeof(T).Name);
+
+ public ILogger CreateLogger(string category)
+ {
+ return _factory.CreateLogger(category);
+ }
+
+ public void Dispose()
+ {
+ _factory?.Dispose();
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs
new file mode 100644
index 00000000..870c8ada
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs
@@ -0,0 +1,83 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public class PowertoolsLoggerFactoryBuilder
+{
+ private readonly PowertoolsLoggerConfiguration _configuration = new();
+
+ // public PowertoolsLoggerFactoryBuilder UseEnvironmentVariables(bool enabled)
+ // {
+ // _configuration.UseEnvironmentVariables = enabled;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder SetLogLevelColor(AppLogLevel level, ConsoleColor color)
+ // {
+ // _configuration.LogLevelToColorMap[level] = color;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder SetEventId(int eventId)
+ // {
+ // _configuration.EventId = eventId;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder SetJsonOptions(JsonSerializerOptions options)
+ // {
+ // _configuration.JsonOptions = options;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder UseJsonOutput(bool enabled = true)
+ // {
+ // _configuration.UseJsonOutput = enabled;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder SetTimestampFormat(string format)
+ // {
+ // _configuration.TimestampFormat = format;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder UseJsonContext(JsonSerializerContext context)
+ // {
+ // _configuration.JsonContext = context;
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactoryBuilder AddJsonContext(JsonSerializerContext context)
+ // {
+ // _configuration.AddJsonContext(context);
+ // return this;
+ // }
+ //
+ // public PowertoolsLoggerFactory Build()
+ // {
+ // var factory = LoggerFactory.Create(builder =>
+ // {
+ // builder.AddPowertoolsLogger(config =>
+ // {
+ // config.UseEnvironmentVariables = _configuration.UseEnvironmentVariables;
+ // config.EventId = _configuration.EventId;
+ // config.JsonOptions = _configuration.JsonOptions;
+ // config.UseJsonOutput = _configuration.UseJsonOutput; // Add this line
+ // config.TimestampFormat = _configuration.TimestampFormat; // Add this line
+ // config.JsonContext = _configuration.JsonContext;
+ //
+ // foreach (var kvp in _configuration.LogLevelToColorMap)
+ // {
+ // config.LogLevelToColorMap[kvp.Key] = kvp.Value;
+ // }
+ // });
+ // });
+ //
+ // Logger.Configure(factory); // Configure the static logger
+ // return new PowertoolsLoggerFactory(factory);
+ // }
+}
\ No newline at end of file
diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props
index c5af6311..a28320f2 100644
--- a/libraries/src/Directory.Packages.props
+++ b/libraries/src/Directory.Packages.props
@@ -4,16 +4,16 @@
-
+
-
+
-
+
@@ -22,5 +22,6 @@
+
\ No newline at end of file
From 1e1716e2e1b9a6ac2746cb787d503f02a56a695f Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:10:08 +0000
Subject: [PATCH 03/49] feat(logging): add GetLogOutput method and
CompositeJsonTypeInfoResolver for enhanced logging capabilities
---
.../Core/ISystemWrapper.cs | 11 +
.../Core/SystemWrapper.cs | 37 ++-
.../BuilderExtensions.cs | 48 +++-
.../Internal/LoggerProvider.cs | 85 +++---
.../Internal/LoggingAspect.cs | 23 +-
.../AWS.Lambda.Powertools.Logging/Logger.cs | 79 +++---
.../PowertoolsLoggerConfiguration.cs | 255 +++++++++++++++++-
.../CompositeJsonTypeInfoResolver.cs | 59 ++++
.../PowertoolsLoggingSerializer.cs | 83 +++++-
libraries/tests/Directory.Packages.props | 6 +-
10 files changed, 551 insertions(+), 135 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
index 8a035984..4e7ad4f0 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
@@ -70,4 +70,15 @@ public interface ISystemWrapper
///
/// The TextWriter instance where to write to
void SetOut(TextWriter writeTo);
+
+ ///
+ /// Sets console error output
+ /// Useful for testing and checking the console error output
+ ///
+ /// var consoleError = new StringWriter();
+ /// SystemWrapper.Instance.SetError(consoleError);
+ ///
+ ///
+ ///
+ string GetLogOutput();
}
\ 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..d972955b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
@@ -27,10 +27,6 @@ namespace AWS.Lambda.Powertools.Common;
public class SystemWrapper : ISystemWrapper
{
private static IPowertoolsEnvironment _powertoolsEnvironment;
-
- ///
- /// The instance
- ///
private static ISystemWrapper _instance;
///
@@ -56,38 +52,26 @@ public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
/// 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)
{
Console.Write(value);
}
- ///
- /// Logs the line.
- ///
- /// The value.
+ ///
public void LogLine(string value)
{
Console.WriteLine(value);
}
- ///
- /// Gets random number
- ///
- /// System.Double.
+ ///
public double GetRandom()
{
return new Random().NextDouble();
@@ -152,4 +136,15 @@ private string ParseAssemblyName(string assemblyName)
return $"{Constants.FeatureContextIdentifier}/{assemblyName}";
}
+
+ ///
+ public string GetLogOutput()
+ {
+ if (Console.Out is StringWriter sw)
+ {
+ return sw.ToString();
+ }
+
+ return "Console.Out is not a StringWriter - no captured output available";
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 0b977275..e8d7a669 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -1,6 +1,7 @@
using System;
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
+using AWS.Lambda.Powertools.Logging.Serializers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
@@ -23,23 +24,29 @@ public static ILoggingBuilder AddPowertoolsLogger(
// Add configuration
builder.AddConfiguration();
- // Register the provider
- builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
-
- LoggerProviderOptions.RegisterProviderOptions
- (builder.Services);
-
// Apply configuration if provided
if (configure != null)
{
- // Create and apply configuration
+ // Create initial configuration
var options = new PowertoolsLoggerConfiguration();
configure(options);
+
+ // IMPORTANT: Set the minimum level directly on the builder
+ if (options.MinimumLevel != LogLevel.None)
+ {
+ builder.SetMinimumLevel(options.MinimumLevel);
+ }
+
// Configure options for DI
builder.Services.Configure(configure);
+ // Register services
+ RegisterServices(builder);
+
+ // Apply the output case configuration
+ PowertoolsLoggingSerializer.ConfigureNamingPolicy(options.LoggerOutputCase);
+
// Configure static Logger (if not already in a configuration cycle)
if (!fromLoggerConfigure && !_configuring)
{
@@ -54,7 +61,32 @@ public static ILoggingBuilder AddPowertoolsLogger(
}
}
}
+ else
+ {
+ // Register services even if no configuration was provided
+ RegisterServices(builder);
+ }
return builder;
}
+
+ private static void RegisterServices(ILoggingBuilder builder)
+ {
+ // Register ISystemWrapper if not already registered
+ builder.Services.TryAddSingleton();
+
+ // Register IPowertoolsEnvironment if it exists
+ builder.Services.TryAddSingleton();
+
+ // Register IPowertoolsConfigurations with all its dependencies
+ builder.Services.TryAddSingleton(sp =>
+ new PowertoolsConfigurations(sp.GetRequiredService()));
+
+ // Register the provider
+ builder.Services.TryAddEnumerable(
+ ServiceDescriptor.Singleton());
+
+ LoggerProviderOptions.RegisterProviderOptions
+ (builder.Services);
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
index 09735162..f241cb47 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
@@ -100,59 +100,78 @@ private PowertoolsLoggerConfiguration GetCurrentConfig()
private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
{
- var logLevel = _powertoolsConfigurations.GetLogLevel(config.MinimumLevel);
+ var logLevel = _powertoolsConfigurations.GetLogLevel(LogLevel.None);
var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
- if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel)
+ // Check for explicit config
+ bool hasExplicitLevel = config.MinimumLevel != LogLevel.None;
+
+ // Warn if Lambda log level doesn't match
+ if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLevel < lambdaLogLevel)
{
_systemWrapper.LogLine(
- $"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.");
+ $"Current log level ({config.MinimumLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
}
- // // Set service
- config.Service ??= _powertoolsConfigurations.Service;
-
+ // Set service from environment if not explicitly set
+ if (string.IsNullOrEmpty(config.Service))
+ {
+ config.Service = _powertoolsConfigurations.Service;
+ }
- // // Set output case
+ // Set output case from environment if not explicitly set
if (config.LoggerOutputCase == LoggerOutputCase.Default)
{
var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(config.LoggerOutputCase);
config.LoggerOutputCase = loggerOutputCase;
- // TODO: Fix this
}
+ // Set log level from environment ONLY if not explicitly set
+ if (!hasExplicitLevel)
+ {
+ var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
+ config.MinimumLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
+ }
+
+ // Always configure the serializer with the output case
PowertoolsLoggingSerializer.ConfigureNamingPolicy(config.LoggerOutputCase);
- //
-
- //
- // // Set log level
- // var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
- // config.MinimumLevel = minLogLevel;
-
+ // Configure the log level key based on output case
config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
- config.LoggerOutputCase == LoggerOutputCase.PascalCase
+ config.LoggerOutputCase == LoggerOutputCase.PascalCase
? "LogLevel"
: LoggingConstants.KeyLogLevel;
+
+ // Handle sampling rate - BUT DON'T MODIFY MINIMUM LEVEL
+ ProcessSamplingRate(config);
+ }
- // Set sampling rate
- // var samplingRate = config.SamplingRate > 0 ? config.SamplingRate : _powertoolsConfigurations.LoggerSampleRate;
- // samplingRate = ValidateSamplingRate(samplingRate, minLogLevel, _systemWrapper);
- //
- // config.SamplingRate = samplingRate;
- //
- // if (samplingRate > 0)
- // {
- // double sample = _systemWrapper.GetRandom();
- //
- // if (sample <= samplingRate)
- // {
- // _systemWrapper.LogLine(
- // $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
- // config.MinimumLevel = LogLevel.Debug;
- // }
- // }
+ private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
+ {
+ var samplingRate = config.SamplingRate > 0
+ ? config.SamplingRate
+ : _powertoolsConfigurations.LoggerSampleRate;
+
+ samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLevel, _systemWrapper);
+ config.SamplingRate = samplingRate;
+
+ // Only notify if sampling is configured
+ if (samplingRate > 0)
+ {
+ double sample = _systemWrapper.GetRandom();
+
+ // Instead of changing log level, just indicate sampling status
+ if (sample <= samplingRate)
+ {
+ _systemWrapper.LogLine(
+ $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
+ config.MinimumLevel = LogLevel.Debug;
+
+ // Store sampling decision in config without changing log level
+ config.SamplingEnabled = true;
+ }
+ }
}
private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index bf875281..f733608a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -79,7 +79,7 @@ public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
private void InitializeLogger(LoggingAttribute trigger)
{
- // Always configure when we have explicit trigger settings
+ // Always check for explicit settings
bool hasExplicitSettings = (trigger.LogLevel != LogLevel.None ||
!string.IsNullOrEmpty(trigger.Service) ||
trigger.LoggerOutputCase != default ||
@@ -89,21 +89,24 @@ private void InitializeLogger(LoggingAttribute trigger)
if (!Logger.IsConfigured || hasExplicitSettings)
{
// Create configuration with default values when not explicitly specified
- Logger.Configure(new PowertoolsLoggerConfiguration
+ var config = new PowertoolsLoggerConfiguration
{
// Use sensible defaults if not specified in the attribute
MinimumLevel = trigger.LogLevel != LogLevel.None ? trigger.LogLevel : LogLevel.Information,
Service = !string.IsNullOrEmpty(trigger.Service) ? trigger.Service : "service_undefined",
LoggerOutputCase = trigger.LoggerOutputCase != default ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
SamplingRate = trigger.SamplingRate > 0 ? trigger.SamplingRate : 1.0
- });
+ };
+
+ // Configure the logger with our configuration
+ Logger.Configure(config);
}
// Get logger after configuration
_logger = Logger.GetLogger();
- // TODO: Fix this
- // _isDebug = config.MinimumLevel <= LogLevel.Debug;
+ // Set debug flag based on the minimum level from Logger
+ _isDebug = Logger.GetConfiguration().MinimumLevel <= LogLevel.Debug;
}
///
@@ -150,8 +153,6 @@ public void OnEntry(
if (!_initializeContext)
return;
-
- _isDebug = LogLevel.Debug >= trigger.LogLevel;
_logger.AppendKey(LoggingConstants.KeyColdStart, _isColdStart);
@@ -210,7 +211,7 @@ private void CaptureLambdaContext(AspectEventArgs eventArgs)
{
_clearLambdaContext = LoggingLambdaContext.Extract(eventArgs);
if (LoggingLambdaContext.Instance is null && _isDebug)
- Debug.WriteLine(
+ _logger.LogDebug(
"Skipping Lambda Context injection because ILambdaContext context parameter not found.");
}
@@ -232,7 +233,7 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
if (eventArg is null)
{
if (_isDebug)
- Debug.WriteLine(
+ _logger.LogDebug(
"Skipping CorrelationId capture because event parameter not found.");
return;
}
@@ -268,7 +269,7 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
catch (Exception e)
{
if (_isDebug)
- Debug.WriteLine(
+ _logger.LogDebug(
$"Skipping CorrelationId capture because of error caused while parsing the event object {e.Message}.");
}
}
@@ -284,7 +285,7 @@ private void LogEvent(object eventArg)
case null:
{
if (_isDebug)
- Debug.WriteLine(
+ _logger.LogDebug(
"Skipping Event Log because event parameter not found.");
break;
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index 9059feaf..0f13d9a2 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -31,49 +31,21 @@ public static partial class Logger
private static Lazy _factoryLazy;
private static Lazy _defaultLoggerLazy;
- // Static constructor to ensure initialization
- // static Logger()
- // {
- // // Create default configuration with sensible defaults
- // var defaultConfig = new PowertoolsLoggerConfiguration
- // {
- // MinimumLevel = LogLevel.Information, // Default to Information level
- // Service = "LambdaFunction", // Default service name
- // LoggerOutputCase = LoggerOutputCase.SnakeCase, // Default case
- // SamplingRate = 1.0 // Default to log everything
- // };
- //
- // // Initialize with default factory
- // _factoryLazy = new Lazy(() =>
- // LoggerFactory.Create(builder =>
- // builder.AddPowertoolsLogger(config =>
- // {
- // config.MinimumLevel = defaultConfig.MinimumLevel;
- // config.Service = defaultConfig.Service;
- // config.LoggerOutputCase = defaultConfig.LoggerOutputCase;
- // config.SamplingRate = defaultConfig.SamplingRate;
- // })),
- // LazyThreadSafetyMode.ExecutionAndPublication);
- //
- // _defaultLoggerLazy = new Lazy(() =>
- // _factoryLazy.Value.CreateLogger("PowertoolsLogger"));
- //
- // // Not yet explicitly configured
- // _isConfigured = false;
- // }
-
- // Flag to track if custom configuration has been applied
- private static bool _isConfigured;
+ // Add a backing field
+ private static bool _isConfigured = false;
// Properties to access the lazy-initialized instances
private static ILoggerFactory Factory => _factoryLazy.Value;
private static ILogger LoggerInstance => _defaultLoggerLazy.Value;
///
- /// Indicates whether the Logger has been configured with custom settings
+ /// Gets a value indicating whether the logger is configured.
///
+ /// true if the logger is configured; otherwise, false.
public static bool IsConfigured => _isConfigured;
+ // Add this field to the Logger class
+ private static PowertoolsLoggerConfiguration _currentConfig;
// Allow manual configuration using options
public static void Configure(Action configureOptions)
@@ -100,17 +72,27 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
- // Create a factory with our provider
+ // Store the configuration
+ _currentConfig = options.Clone();
+
+ // Create a factory with our provider - CRITICAL PART
var factory = LoggerFactory.Create(builder =>
{
- // Use AddPowertoolsLogger but with fromLoggerConfigure=true to prevent recursion
+ // IMPORTANT - Set the minimum level directly on the logging builder!
+ // This ensures it's respected by the logging infrastructure
+ if (_currentConfig.MinimumLevel != LogLevel.None)
+ {
+ builder.SetMinimumLevel(_currentConfig.MinimumLevel);
+ }
+
+ // Add PowertoolsLogger with the same configuration
builder.AddPowertoolsLogger(config =>
{
- config.Service = options.Service;
- config.MinimumLevel = options.MinimumLevel;
- config.LoggerOutputCase = options.LoggerOutputCase;
- config.SamplingRate = options.SamplingRate;
- // Copy other properties as needed
+ // Transfer all settings
+ config.Service = _currentConfig.Service;
+ config.MinimumLevel = _currentConfig.MinimumLevel;
+ config.LoggerOutputCase = _currentConfig.LoggerOutputCase;
+ config.SamplingRate = _currentConfig.SamplingRate;
}, true);
});
@@ -124,6 +106,21 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
_isConfigured = true;
}
+ // Add this method to the Logger class
+ // Get the current configuration
+ public static PowertoolsLoggerConfiguration GetConfiguration()
+ {
+ // Ensure logger is initialized
+ _ = LoggerInstance;
+
+ // Create a new configuration with current settings
+ if (_currentConfig == null)
+ {
+ _currentConfig = new PowertoolsLoggerConfiguration();
+ }
+
+ return _currentConfig;
+ }
// Get a logger for a specific category
public static ILogger GetLogger() => GetLogger(typeof(T).Name);
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index b1d8dd84..4643fe06 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -13,54 +13,281 @@
* permissions and limitations under the License.
*/
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.Json.Serialization.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Serializers;
namespace AWS.Lambda.Powertools.Logging;
///
-/// Class LoggerConfiguration.
-/// Implements the
-///
+/// Class PowertoolsLoggerConfiguration.
+/// Implements the
///
-///
public class PowertoolsLoggerConfiguration : IOptions
{
+ public const string ConfigurationSectionName = "PowertoolsLogger";
+
///
/// Service name is used for logging.
/// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME.
///
- /// The service.
public string? Service { get; set; } = null;
///
/// Specify the minimum log level for logging (Information, by default).
/// This can be also set using the environment variable POWERTOOLS_LOG_LEVEL.
///
- /// The minimum level.
public LogLevel MinimumLevel { get; set; } = LogLevel.None;
///
/// Dynamically set a percentage of logs to DEBUG level.
/// This can be also set using the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE.
///
- /// The sampling rate.
public double SamplingRate { get; set; }
///
- /// The default configured options instance
+ /// Whether sampling was enabled for the current request
///
- /// The value.
- PowertoolsLoggerConfiguration IOptions.Value => this;
+ public bool SamplingEnabled { get; set; }
///
/// The logger output case.
/// This can be also set using the environment variable POWERTOOLS_LOGGER_CASE.
///
- /// The logger output case.
public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default;
- internal string LogLevelKey { get; set; }
-
-
+ ///
+ /// Internal key used for log level in output
+ ///
+ internal string LogLevelKey { get; set; } = "level";
+
+ ///
+ /// JSON serializer options to use for log serialization
+ ///
+ private JsonSerializerOptions? _jsonOptions;
+ public JsonSerializerOptions? JsonOptions
+ {
+ get => _jsonOptions;
+ set
+ {
+ _jsonOptions = value;
+ if (_jsonOptions != null)
+ {
+#if NET8_0_OR_GREATER
+ HandleJsonOptionsTypeResolver(_jsonOptions);
+#endif
+ ApplyJsonOptions();
+ }
+ }
+ }
+
+#if NET8_0_OR_GREATER
+ ///
+ /// Default JSON serializer context
+ ///
+ private JsonSerializerContext? _jsonContext = PowertoolsLoggingSerializationContext.Default;
+ private readonly List _additionalContexts = new();
+
+ ///
+ /// Main JSON context to use for serialization
+ ///
+ public JsonSerializerContext? JsonContext
+ {
+ get => _jsonContext;
+ set
+ {
+ _jsonContext = value;
+ ApplyJsonContext();
+ }
+ }
+
+ ///
+ /// Add additional JsonSerializerContext for client types
+ ///
+ public void AddJsonContext(JsonSerializerContext context)
+ {
+ if (context != null && !_additionalContexts.Contains(context))
+ {
+ _additionalContexts.Add(context);
+ ApplyAdditionalJsonContext(context);
+ }
+ }
+
+ ///
+ /// Get all additional contexts
+ ///
+ public IReadOnlyList GetAdditionalContexts()
+ {
+ return _additionalContexts.AsReadOnly();
+ }
+
+ private IJsonTypeInfoResolver? _customTypeInfoResolver = null;
+ private List? _customTypeInfoResolvers;
+
+ ///
+ /// Process JSON options type resolver information
+ ///
+ public void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
+ {
+ if (options == null) return;
+
+ // Check for TypeInfoResolver
+ if (options.TypeInfoResolver != null &&
+ options.TypeInfoResolver != GetCompositeResolver())
+ {
+ _customTypeInfoResolver = options.TypeInfoResolver;
+ }
+
+ // Check for TypeInfoResolverChain
+ if (options.TypeInfoResolverChain != null && options.TypeInfoResolverChain.Count > 0)
+ {
+ foreach (var resolver in options.TypeInfoResolverChain)
+ {
+ // If it's a JsonSerializerContext, add it to our additional contexts
+ if (resolver is JsonSerializerContext context)
+ {
+ AddJsonContext(context);
+ }
+ // Otherwise store it as a custom resolver
+ else if (resolver is IJsonTypeInfoResolver customResolver &&
+ customResolver != GetCompositeResolver() &&
+ _customTypeInfoResolver != customResolver)
+ {
+ // If we already have a different custom resolver, we need to store multiple
+ if (_customTypeInfoResolver != null)
+ {
+ _customTypeInfoResolvers ??= new List();
+ if (!_customTypeInfoResolvers.Contains(customResolver))
+ {
+ _customTypeInfoResolvers.Add(customResolver);
+ }
+ }
+ else
+ {
+ _customTypeInfoResolver = customResolver;
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ /// Get a composite resolver that includes all configured resolvers
+ ///
+ public IJsonTypeInfoResolver GetCompositeResolver()
+ {
+ var resolvers = new List();
+
+ // Add custom resolver if provided
+ if (_customTypeInfoResolver != null)
+ {
+ resolvers.Add(_customTypeInfoResolver);
+ }
+
+ // Add additional custom resolvers if any
+ if (_customTypeInfoResolvers != null)
+ {
+ foreach (var resolver in _customTypeInfoResolvers)
+ {
+ resolvers.Add(resolver);
+ }
+ }
+
+ // Add default context
+ if (_jsonContext != null)
+ {
+ resolvers.Add(_jsonContext);
+ }
+
+ // Add additional contexts
+ foreach (var context in _additionalContexts)
+ {
+ resolvers.Add(context);
+ }
+
+ return new CompositeJsonTypeInfoResolver(resolvers.ToArray());
+ }
+
+ ///
+ /// Apply JSON context to serializer
+ ///
+ private void ApplyJsonContext()
+ {
+ if (_jsonContext != null)
+ {
+ PowertoolsLoggingSerializer.SetDefaultContext(_jsonContext);
+ }
+ }
+
+ ///
+ /// Apply additional JSON context to serializer
+ ///
+ private void ApplyAdditionalJsonContext(JsonSerializerContext context)
+ {
+ PowertoolsLoggingSerializer.AddSerializerContext(context);
+ }
+#endif
+
+ ///
+ /// Apply JSON options to the serializer
+ ///
+ private void ApplyJsonOptions()
+ {
+ if (_jsonOptions != null)
+ {
+ PowertoolsLoggingSerializer.ConfigureJsonOptions(_jsonOptions);
+ }
+ }
+
+ ///
+ /// Apply output case configuration
+ ///
+ public void ApplyOutputCase()
+ {
+ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase);
+ }
+
+ ///
+ /// Clone this configuration
+ ///
+ public PowertoolsLoggerConfiguration Clone()
+ {
+ var clone = new PowertoolsLoggerConfiguration
+ {
+ Service = Service,
+ MinimumLevel = MinimumLevel,
+ SamplingRate = SamplingRate,
+ SamplingEnabled = SamplingEnabled, // Copy the sampling decision
+ LoggerOutputCase = LoggerOutputCase,
+ LogLevelKey = LogLevelKey,
+ JsonOptions = JsonOptions
+ };
+
+#if NET8_0_OR_GREATER
+ clone._jsonContext = _jsonContext;
+ foreach (var context in _additionalContexts)
+ {
+ clone._additionalContexts.Add(context);
+ }
+
+ clone._customTypeInfoResolver = _customTypeInfoResolver;
+
+ if (_customTypeInfoResolvers != null)
+ {
+ clone._customTypeInfoResolvers = new List(_customTypeInfoResolvers);
+ }
+#endif
+
+ return clone;
+ }
+
+ // IOptions implementation
+ PowertoolsLoggerConfiguration IOptions.Value => this;
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs
new file mode 100644
index 00000000..c665f960
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#if NET8_0_OR_GREATER
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
+
+namespace AWS.Lambda.Powertools.Logging.Serializers
+{
+ ///
+ /// Combines multiple IJsonTypeInfoResolver instances into one
+ ///
+ internal class CompositeJsonTypeInfoResolver : IJsonTypeInfoResolver
+ {
+ private readonly IJsonTypeInfoResolver[] _resolvers;
+
+ ///
+ /// Creates a new composite resolver from multiple resolvers
+ ///
+ /// Array of resolvers to use
+ public CompositeJsonTypeInfoResolver(IJsonTypeInfoResolver[] resolvers)
+ {
+ _resolvers = resolvers ?? throw new ArgumentNullException(nameof(resolvers));
+ }
+
+
+ ///
+ /// Gets JSON type info by trying each resolver in order (.NET Standard 2.0 version)
+ ///
+ public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
+ {
+ foreach (var resolver in _resolvers)
+ {
+ var typeInfo = resolver?.GetTypeInfo(type, options);
+ if (typeInfo != null)
+ {
+ return typeInfo;
+ }
+ }
+
+ return null;
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index 97aabc06..ef54d3a5 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -36,6 +36,7 @@ internal static class PowertoolsLoggingSerializer
{
private static LoggerOutputCase _currentOutputCase;
private static JsonSerializerOptions _jsonOptions;
+ private static readonly object _lock = new object();
private static readonly ConcurrentBag AdditionalContexts =
new ConcurrentBag();
@@ -45,7 +46,19 @@ internal static class PowertoolsLoggingSerializer
///
internal static JsonSerializerOptions GetSerializerOptions()
{
- return _jsonOptions ?? BuildJsonSerializerOptions();
+ // Double-checked locking pattern for thread safety while ensuring we only build once
+ if (_jsonOptions == null)
+ {
+ lock (_lock)
+ {
+ if (_jsonOptions == null)
+ {
+ BuildJsonSerializerOptions();
+ }
+ }
+ }
+
+ return _jsonOptions;
}
///
@@ -54,7 +67,19 @@ internal static JsonSerializerOptions GetSerializerOptions()
/// The case to use for serialization.
internal static void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase)
{
- _currentOutputCase = loggerOutputCase;
+ if (_currentOutputCase != loggerOutputCase)
+ {
+ lock (_lock)
+ {
+ _currentOutputCase = loggerOutputCase;
+
+ // Only rebuild options if they already exist
+ if (_jsonOptions != null)
+ {
+ BuildJsonSerializerOptions();
+ }
+ }
+ }
}
///
@@ -120,8 +145,9 @@ internal static JsonTypeInfo GetTypeInfo(Type type)
/// Builds and configures the JsonSerializerOptions.
///
/// A configured JsonSerializerOptions instance.
- private static JsonSerializerOptions BuildJsonSerializerOptions()
+ private static void BuildJsonSerializerOptions()
{
+ // This should already be in a lock when called
_jsonOptions = new JsonSerializerOptions();
switch (_currentOutputCase)
@@ -173,7 +199,6 @@ private static JsonSerializerOptions BuildJsonSerializerOptions()
}
}
#endif
- return _jsonOptions;
}
#if NET8_0_OR_GREATER
@@ -195,4 +220,54 @@ internal static void ClearOptions()
{
_jsonOptions = null;
}
+
+ ///
+ /// Sets the default JSON context to use
+ ///
+ internal static void SetDefaultContext(JsonSerializerContext context)
+ {
+ lock (_lock)
+ {
+ // Reset options to ensure they're rebuilt with the new context
+ _jsonOptions = null;
+ }
+ }
+
+ ///
+ /// Configure the serializer with specific JSON options
+ ///
+ internal static void ConfigureJsonOptions(JsonSerializerOptions options)
+ {
+ if (options == null) return;
+
+ lock (_lock)
+ {
+ _jsonOptions = options;
+
+ // Add required converters if they're not already present
+ var converters = new[]
+ {
+ typeof(ByteArrayConverter),
+ typeof(ExceptionConverter),
+ typeof(MemoryStreamConverter),
+ typeof(ConstantClassConverter),
+ typeof(DateOnlyConverter),
+ typeof(TimeOnlyConverter),
+ typeof(LogLevelJsonConverter),
+ };
+
+ foreach (var converterType in converters)
+ {
+ if (!_jsonOptions.Converters.Any(c => c.GetType() == converterType))
+ {
+ // Add the converter through reflection to avoid direct instantiation
+ var converter = Activator.CreateInstance(converterType) as JsonConverter;
+ if (converter != null)
+ {
+ _jsonOptions.Converters.Add(converter);
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/libraries/tests/Directory.Packages.props b/libraries/tests/Directory.Packages.props
index 516a0e93..c6868210 100644
--- a/libraries/tests/Directory.Packages.props
+++ b/libraries/tests/Directory.Packages.props
@@ -4,7 +4,7 @@
-
+
@@ -13,13 +13,13 @@
-
+
-
+
\ No newline at end of file
From a494c2b955d12f4b4a8416bced9f73f976ff7666 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Tue, 18 Mar 2025 13:56:05 +0000
Subject: [PATCH 04/49] refactor(logging): enhance IsEnabled method for
improved log level handling
---
.../Internal/PowertoolsLogger.cs | 27 +++++++++++++++----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
index 3c6ae09a..3e9100d7 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -56,7 +56,7 @@ internal sealed class PowertoolsLogger : ILogger
/// Private constructor - Is initialized on CreateLogger
///
/// The name.
- /// The Powertools for AWS Lambda (.NET) configurations.
+ ///
/// The system wrapper.
public PowertoolsLogger(
string categoryName,
@@ -97,7 +97,22 @@ internal void EndScope()
/// The log level.
/// bool.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None && logLevel >= _currentConfig.MinimumLevel;
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ // If we have no explicit minimum level, use the default
+ var effectiveMinLevel = _currentConfig.MinimumLevel != LogLevel.None
+ ? _currentConfig.MinimumLevel
+ : LoggingConstants.DefaultLogLevel;
+
+ // Log diagnostic info for Debug/Trace levels
+ if (logLevel <= LogLevel.Debug)
+ {
+ return logLevel >= effectiveMinLevel;
+ }
+
+ // Standard check
+ return logLevel >= effectiveMinLevel;
+ }
///
/// Writes a log entry.
@@ -111,11 +126,13 @@ internal void EndScope()
public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func formatter)
{
- if (formatter is null)
- throw new ArgumentNullException(nameof(formatter));
-
if (!IsEnabled(logLevel))
+ {
return;
+ }
+
+ if (formatter is null)
+ throw new ArgumentNullException(nameof(formatter));
var timestamp = DateTime.UtcNow;
var message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null
From 8c0386664d6b9a8e7414255e6477deeb51188fa8 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Tue, 18 Mar 2025 15:55:26 +0000
Subject: [PATCH 05/49] feat(logging): introduce custom logger output and
enhance configuration options
---
.../Core/IPowertoolsEnvironment.cs | 6 ++
.../Core/ISystemWrapper.cs | 31 -------
.../Core/PowertoolsConfigurations.cs | 25 +++---
.../Core/PowertoolsEnvironment.cs | 48 ++++++++++
.../Core/SystemWrapper.cs | 89 +------------------
.../Core/TestLoggerOutput.cs | 51 +++++++++++
.../BuilderExtensions.cs | 2 +-
.../Internal/LoggerProvider.cs | 26 ++----
.../AWS.Lambda.Powertools.Logging/Logger.cs | 25 ++++--
.../PowertoolsLoggerConfiguration.cs | 7 ++
10 files changed, 152 insertions(+), 158 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs
index 059cfb7e..6f57aabb 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs
@@ -34,4 +34,10 @@ public interface IPowertoolsEnvironment
///
/// Assembly Version in the Major.Minor.Build format
string GetAssemblyVersion(T type);
+
+ ///
+ /// 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/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
index 4e7ad4f0..745118d7 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
@@ -22,13 +22,6 @@ namespace AWS.Lambda.Powertools.Common;
///
public interface ISystemWrapper
{
- ///
- /// Gets the environment variable.
- ///
- /// The variable.
- /// System.String.
- string GetEnvironmentVariable(string variable);
-
///
/// Logs the specified value.
///
@@ -47,19 +40,6 @@ 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);
-
///
/// Sets console output
/// Useful for testing and checking the console output
@@ -70,15 +50,4 @@ public interface ISystemWrapper
///
/// The TextWriter instance where to write to
void SetOut(TextWriter writeTo);
-
- ///
- /// Sets console error output
- /// Useful for testing and checking the console error output
- ///
- /// var consoleError = new StringWriter();
- /// SystemWrapper.Instance.SetError(consoleError);
- ///
- ///
- ///
- string GetLogOutput();
}
\ 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 3933972d..2fce2191 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs
@@ -24,6 +24,8 @@ namespace AWS.Lambda.Powertools.Common;
///
public class PowertoolsConfigurations : IPowertoolsConfigurations
{
+ private readonly IPowertoolsEnvironment _powertoolsEnvironment;
+
///
/// The maximum dimensions
///
@@ -39,18 +41,13 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations
///
private static IPowertoolsConfigurations _instance;
- ///
- /// The system wrapper
- ///
- private readonly ISystemWrapper _systemWrapper;
-
///
/// Initializes a new instance of the class.
///
/// The system wrapper.
- internal PowertoolsConfigurations(ISystemWrapper systemWrapper)
+ internal PowertoolsConfigurations(IPowertoolsEnvironment powertoolsEnvironment)
{
- _systemWrapper = systemWrapper;
+ _powertoolsEnvironment = powertoolsEnvironment;
}
///
@@ -58,7 +55,7 @@ internal PowertoolsConfigurations(ISystemWrapper systemWrapper)
///
/// The instance.
public static IPowertoolsConfigurations Instance =>
- _instance ??= new PowertoolsConfigurations(SystemWrapper.Instance);
+ _instance ??= new PowertoolsConfigurations(PowertoolsEnvironment.Instance);
///
/// Gets the environment variable.
@@ -67,7 +64,7 @@ internal PowertoolsConfigurations(ISystemWrapper systemWrapper)
/// System.String.
public string GetEnvironmentVariable(string variable)
{
- return _systemWrapper.GetEnvironmentVariable(variable);
+ return _powertoolsEnvironment.GetEnvironmentVariable(variable);
}
///
@@ -78,7 +75,7 @@ public string GetEnvironmentVariable(string variable)
/// System.String.
public string GetEnvironmentVariableOrDefault(string variable, string defaultValue)
{
- var result = _systemWrapper.GetEnvironmentVariable(variable);
+ var result = _powertoolsEnvironment.GetEnvironmentVariable(variable);
return string.IsNullOrWhiteSpace(result) ? defaultValue : result;
}
@@ -90,7 +87,7 @@ public string GetEnvironmentVariableOrDefault(string variable, string defaultVal
/// System.Int32.
public int GetEnvironmentVariableOrDefault(string variable, int defaultValue)
{
- var result = _systemWrapper.GetEnvironmentVariable(variable);
+ var result = _powertoolsEnvironment.GetEnvironmentVariable(variable);
return int.TryParse(result, out var parsedValue) ? parsedValue : defaultValue;
}
@@ -102,7 +99,7 @@ public int GetEnvironmentVariableOrDefault(string variable, int defaultValue)
/// true if XXXX, false otherwise.
public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue)
{
- return bool.TryParse(_systemWrapper.GetEnvironmentVariable(variable), out var result)
+ return bool.TryParse(_powertoolsEnvironment.GetEnvironmentVariable(variable), out var result)
? result
: defaultValue;
}
@@ -160,7 +157,7 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue)
///
/// The logger sample rate.
public double LoggerSampleRate =>
- double.TryParse(_systemWrapper.GetEnvironmentVariable(Constants.LoggerSampleRateNameEnv), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result)
+ double.TryParse(_powertoolsEnvironment.GetEnvironmentVariable(Constants.LoggerSampleRateNameEnv), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result)
? result
: 0;
@@ -201,7 +198,7 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue)
///
public void SetExecutionEnvironment(T type)
{
- _systemWrapper.SetExecutionEnvironment(type);
+ _powertoolsEnvironment.SetExecutionEnvironment(type);
}
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs
index 3ad5317c..7abb379a 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.Text;
namespace AWS.Lambda.Powertools.Common;
@@ -40,4 +41,51 @@ public string GetAssemblyVersion(T type)
var version = type.GetType().Assembly.GetName().Version;
return version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : string.Empty;
}
+
+ 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))
+ {
+ // Avoid duplication - should not happen since the calling Instances are Singletons - defensive purposes
+ if (currentEnvValue.Contains(assemblyName))
+ {
+ return;
+ }
+
+ envValue.Append($"{currentEnvValue} ");
+ }
+
+ var assemblyVersion = 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.Common/Core/SystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
index d972955b..cc7da82d 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
@@ -15,7 +15,6 @@
using System;
using System.IO;
-using System.Text;
namespace AWS.Lambda.Powertools.Common;
@@ -24,19 +23,13 @@ namespace AWS.Lambda.Powertools.Common;
/// Implements the
///
///
-public class SystemWrapper : ISystemWrapper
+internal class SystemWrapper : ISystemWrapper
{
- private static IPowertoolsEnvironment _powertoolsEnvironment;
- private static ISystemWrapper _instance;
-
///
/// Prevents a default instance of the class from being created.
///
- public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
+ public SystemWrapper()
{
- _powertoolsEnvironment = powertoolsEnvironment;
- _instance ??= this;
-
// Clear AWS SDK Console injected parameters StdOut and StdErr
var standardOutput = new StreamWriter(Console.OpenStandardOutput());
standardOutput.AutoFlush = true;
@@ -46,19 +39,6 @@ public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
Console.SetError(errordOutput);
}
- ///
- /// Gets the instance.
- ///
- /// The instance.
- public static ISystemWrapper Instance => _instance ??= new SystemWrapper(PowertoolsEnvironment.Instance);
-
-
- ///
- public string GetEnvironmentVariable(string variable)
- {
- return _powertoolsEnvironment.GetEnvironmentVariable(variable);
- }
-
///
public void Log(string value)
{
@@ -77,74 +57,9 @@ 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());
- }
-
///
public void SetOut(TextWriter writeTo)
{
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}";
- }
-
- ///
- public string GetLogOutput()
- {
- if (Console.Out is StringWriter sw)
- {
- return sw.ToString();
- }
-
- return "Console.Out is not a StringWriter - no captured output available";
- }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
new file mode 100644
index 00000000..5a70b529
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
@@ -0,0 +1,51 @@
+using System;
+using System.IO;
+using System.Text;
+
+namespace AWS.Lambda.Powertools.Common.Tests;
+
+///
+/// Test logger output
+///
+public class TestLoggerOutput : ISystemWrapper
+{
+ ///
+ /// Buffer for all the log messages written to the logger.
+ ///
+ public StringBuilder Buffer { get; } = new StringBuilder();
+
+ ///
+ /// Logs the specified value.
+ ///
+ ///
+ public void Log(string value)
+ {
+ Buffer.Append(value);
+ Console.Write(value);
+ }
+
+ ///
+ /// Logs the line.
+ ///
+ public void LogLine(string value)
+ {
+ Buffer.AppendLine(value);
+ Console.WriteLine(value);
+ }
+
+ ///
+ /// Gets random number
+ ///
+ public double GetRandom()
+ {
+ return 0.7;
+ }
+
+ ///
+ /// Sets console output
+ ///
+ public void SetOut(TextWriter writeTo)
+ {
+ Console.SetOut(writeTo);
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index e8d7a669..923d432a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -80,7 +80,7 @@ private static void RegisterServices(ILoggingBuilder builder)
// Register IPowertoolsConfigurations with all its dependencies
builder.Services.TryAddSingleton(sp =>
- new PowertoolsConfigurations(sp.GetRequiredService()));
+ new PowertoolsConfigurations(sp.GetRequiredService()));
// Register the provider
builder.Services.TryAddEnumerable(
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
index f241cb47..ffddbfc4 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
@@ -56,25 +56,17 @@ internal sealed class LoggerProvider : ILoggerProvider
///
public LoggerProvider(IOptionsMonitor config,
IPowertoolsConfigurations powertoolsConfigurations,
- ISystemWrapper systemWrapper)
+ ISystemWrapper? systemWrapper = null)
{
- _currentConfig = config.CurrentValue;
+ // Use custom system wrapper if provided through config
+ var currentConfig = config.CurrentValue;
+ _systemWrapper = currentConfig.LoggerOutput ?? systemWrapper ?? new SystemWrapper();
+
_powertoolsConfigurations = powertoolsConfigurations;
- _systemWrapper = systemWrapper;
- _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
+ _currentConfig = currentConfig;
- // TODO: FIx this
- // It was moved bellow
- // _powertoolsConfigurations.SetCurrentConfig(_currentConfig, systemWrapper);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The configuration.
- public LoggerProvider(IOptionsMonitor config)
- : this(config, PowertoolsConfigurations.Instance, SystemWrapper.Instance)
- {
+ _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
+ ApplyPowertoolsConfig(_currentConfig);
}
///
@@ -85,7 +77,7 @@ public LoggerProvider(IOptionsMonitor config)
public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger(name,
- GetCurrentConfig,
+ () => _currentConfig,
_systemWrapper));
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index 0f13d9a2..457d87d5 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -15,6 +15,7 @@
using System;
using System.Threading;
+using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -74,25 +75,33 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
// Store the configuration
_currentConfig = options.Clone();
+
+ // Create a system wrapper if needed
+ var systemWrapper = options.LoggerOutput ?? new SystemWrapper();
- // Create a factory with our provider - CRITICAL PART
+ // Create a factory with our provider
var factory = LoggerFactory.Create(builder =>
{
- // IMPORTANT - Set the minimum level directly on the logging builder!
- // This ensures it's respected by the logging infrastructure
- if (_currentConfig.MinimumLevel != LogLevel.None)
+ // Set minimum level directly on builder
+ if (options.MinimumLevel != LogLevel.None)
{
- builder.SetMinimumLevel(_currentConfig.MinimumLevel);
+ builder.SetMinimumLevel(options.MinimumLevel);
}
-
- // Add PowertoolsLogger with the same configuration
+
+ // Add our provider - the config's OutputLogger will be used
+ builder.Services.AddSingleton(systemWrapper);
+
builder.AddPowertoolsLogger(config =>
{
- // Transfer all settings
config.Service = _currentConfig.Service;
config.MinimumLevel = _currentConfig.MinimumLevel;
config.LoggerOutputCase = _currentConfig.LoggerOutputCase;
config.SamplingRate = _currentConfig.SamplingRate;
+ config.LoggerOutput = _currentConfig.LoggerOutput;
+ config.JsonOptions = _currentConfig.JsonOptions;
+#if NET8_0_OR_GREATER
+ config.JsonContext = _currentConfig.JsonContext;
+#endif
}, true);
});
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index 4643fe06..de6543e1 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -68,6 +68,12 @@ public class PowertoolsLoggerConfiguration : IOptions
internal string LogLevelKey { get; set; } = "level";
+ ///
+ /// Custom output logger to use instead of Console
+ ///
+ public ISystemWrapper? LoggerOutput { get; set; }
+
+
///
/// JSON serializer options to use for log serialization
///
@@ -266,6 +272,7 @@ public PowertoolsLoggerConfiguration Clone()
SamplingRate = SamplingRate,
SamplingEnabled = SamplingEnabled, // Copy the sampling decision
LoggerOutputCase = LoggerOutputCase,
+ LoggerOutput = LoggerOutput,
LogLevelKey = LogLevelKey,
JsonOptions = JsonOptions
};
From cfd2b289fad6c8bc4473c44ec6b8d909badb1d38 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Wed, 19 Mar 2025 11:30:16 +0000
Subject: [PATCH 06/49] add log formatting
---
.../Internal/LoggerProvider.cs | 3 -
.../Internal/PowertoolsLogger.cs | 197 +++++++++++++++++-
.../PowertoolsLoggerConfiguration.cs | 6 -
3 files changed, 191 insertions(+), 15 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
index ffddbfc4..6b1ddcbb 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
@@ -159,9 +159,6 @@ private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
_systemWrapper.LogLine(
$"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
config.MinimumLevel = LogLevel.Debug;
-
- // Store sampling decision in config without changing log level
- config.SamplingEnabled = true;
}
}
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
index 3e9100d7..8793113e 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -135,14 +135,34 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
throw new ArgumentNullException(nameof(formatter));
var timestamp = DateTime.UtcNow;
+
+ // Extract structured logging parameters
+ var structuredParameters = ExtractStructuredParameters(state, out string messageTemplate);
+
+ // Format the message using the provided formatter
var message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null
? customMessage
: formatter(state, exception);
+
+ // // For better object representation in messages
+ // if (message != null && message.ToString().Contains(".") &&
+ // message.ToString().EndsWith("Class") &&
+ // structuredParameters.Count > 0)
+ // {
+ // // This might be a ToString() of an object class - let's try to use a better representation
+ // var objName = message.ToString().Split('.').Last();
+ // if (structuredParameters.Count == 1)
+ // {
+ // // If we have a single parameter, try to represent it nicely in the log
+ // var param = structuredParameters.First();
+ // message = $"{param.Key}: {(param.Value != null ? "[object]" : "null")}";
+ // }
+ // }
var logFormatter = Logger.GetFormatter();
var logEntry = logFormatter is null
- ? GetLogEntry(logLevel, timestamp, message, exception)
- : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter);
+ ? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters)
+ : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters);
_systemWrapper.LogLine(PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object)));
}
@@ -155,7 +175,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
/// The message to be written. Can be also an object.
/// The exception related to this entry.
private Dictionary GetLogEntry(LogLevel logLevel, DateTime timestamp, object message,
- Exception exception)
+ Exception exception, Dictionary structuredParameters = null)
{
var logEntry = new Dictionary();
@@ -181,6 +201,16 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
}
}
+ // Add structured parameters
+ if (structuredParameters != null)
+ {
+ foreach (var (key, value) in structuredParameters)
+ {
+ if (!string.IsNullOrWhiteSpace(key))
+ logEntry.TryAdd(key, value);
+ }
+ }
+
logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString("o"));
logEntry.TryAdd(_currentConfig.LogLevelKey, logLevel.ToString());
logEntry.TryAdd(LoggingConstants.KeyService, _currentConfig.Service);
@@ -188,8 +218,12 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
logEntry.TryAdd(LoggingConstants.KeyMessage, message);
if (_currentConfig.SamplingRate > 0)
logEntry.TryAdd(LoggingConstants.KeySamplingRate, _currentConfig.SamplingRate);
+
+ // Use the AddExceptionDetails method instead of adding exception directly
if (exception != null)
- logEntry.TryAdd(LoggingConstants.KeyException, exception);
+ {
+ AddExceptionDetails(logEntry, exception);
+ }
return logEntry;
}
@@ -203,7 +237,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
/// The exception related to this entry.
/// The custom log entry formatter.
private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, object message,
- Exception exception, ILogFormatter logFormatter)
+ Exception exception, ILogFormatter logFormatter, Dictionary structuredParameters)
{
if (logFormatter is null)
return null;
@@ -215,7 +249,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
Service = _currentConfig.Service,
Name = _categoryName,
Message = message,
- Exception = exception,
+ Exception = exception, // Keep this to maintain compatibility
SamplingRate = _currentConfig.SamplingRate,
};
@@ -250,6 +284,29 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
extraKeys.TryAdd(key, value);
}
}
+
+ // Add structured parameters
+ if (structuredParameters != null)
+ {
+ foreach (var (key, value) in structuredParameters)
+ {
+ if (!string.IsNullOrWhiteSpace(key))
+ extraKeys.TryAdd(key, value);
+ }
+ }
+
+ // Add detailed exception information
+ if (exception != null)
+ {
+ var exceptionDetails = new Dictionary();
+ AddExceptionDetails(exceptionDetails, exception);
+
+ // Add exception details to extra keys
+ foreach (var (key, value) in exceptionDetails)
+ {
+ extraKeys.TryAdd(key, value);
+ }
+ }
if (extraKeys.Any())
logEntry.ExtraKeys = extraKeys;
@@ -390,4 +447,132 @@ private static Dictionary GetScopeKeys(TState state)
return keys;
}
+
+ ///
+ /// Extracts structured parameter key-value pairs from the log state
+ ///
+ private Dictionary ExtractStructuredParameters(TState state, out string messageTemplate)
+ {
+ messageTemplate = string.Empty;
+ var parameters = new Dictionary();
+
+ if (state is IEnumerable> stateProps)
+ {
+ // Dictionary to store format specifiers for each parameter
+ var formatSpecifiers = new Dictionary();
+
+ // First pass - extract template and identify format specifiers
+ foreach (var prop in stateProps)
+ {
+ // The original message template is stored with key "{OriginalFormat}"
+ if (prop.Key == "{OriginalFormat}" && prop.Value is string template)
+ {
+ messageTemplate = template;
+
+ // Extract format specifiers from the template
+ var matches = System.Text.RegularExpressions.Regex.Matches(
+ template,
+ @"{([@\w]+)(?::([^{}]+))?}");
+
+ foreach (System.Text.RegularExpressions.Match match in matches)
+ {
+ string paramName = match.Groups[1].Value;
+ if (match.Groups.Count > 2 && match.Groups[2].Success)
+ {
+ formatSpecifiers[paramName] = match.Groups[2].Value;
+ }
+ }
+
+ continue;
+ }
+ }
+
+ // Second pass - process values with extracted format specifiers
+ foreach (var prop in stateProps)
+ {
+ if (prop.Key == "{OriginalFormat}")
+ continue;
+
+ // Extract parameter name without braces
+ string paramName = ExtractParameterName(prop.Key);
+ if (string.IsNullOrEmpty(paramName))
+ continue;
+
+ // Handle special serialization designators (like @)
+ bool useStructuredSerialization = paramName.StartsWith("@");
+ string actualParamName = useStructuredSerialization ? paramName.Substring(1) : paramName;
+
+ // Apply formatting if a format specifier exists and the value can be formatted
+ if (!useStructuredSerialization &&
+ formatSpecifiers.TryGetValue(paramName, out string format) &&
+ prop.Value is IFormattable formattable)
+ {
+ // Format the value using the specified format
+ string formattedValue = formattable.ToString(format, System.Globalization.CultureInfo.InvariantCulture);
+
+ // Try to preserve the numeric type if possible
+ if (double.TryParse(formattedValue, out double numericValue))
+ {
+ parameters[actualParamName] = numericValue;
+ }
+ else
+ {
+ parameters[actualParamName] = formattedValue;
+ }
+ }
+ else if (useStructuredSerialization)
+ {
+ // Serialize the entire object
+ parameters[actualParamName] = prop.Value;
+ }
+ else
+ {
+ // For regular objects, use the value directly
+ parameters[actualParamName] = prop.Value;
+ }
+ }
+ }
+
+ return parameters;
+ }
+
+ ///
+ /// Extracts the parameter name from a template placeholder (e.g. "{paramName}" or "{paramName:format}")
+ ///
+ private string ExtractParameterName(string key)
+ {
+ // If it's already a proper parameter name without braces, return it
+ if (!key.StartsWith("{") || !key.EndsWith("}"))
+ return key;
+
+ // Remove the braces
+ var nameWithPossibleFormat = key.Substring(1, key.Length - 2);
+
+ // If there's a format specifier, remove it
+ var colonIndex = nameWithPossibleFormat.IndexOf(':');
+ return colonIndex > 0
+ ? nameWithPossibleFormat.Substring(0, colonIndex)
+ : nameWithPossibleFormat;
+ }
+
+ private void AddExceptionDetails(Dictionary logEntry, Exception exception)
+ {
+ if (exception == null)
+ return;
+
+ logEntry.TryAdd("errorType", exception.GetType().FullName);
+ logEntry.TryAdd("errorMessage", exception.Message);
+
+ // Add stack trace as array of strings for better readability
+ var stackFrames = exception.StackTrace?.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
+ if (stackFrames?.Length > 0)
+ {
+ var cleanedStackTrace = new List
+ {
+ $"{exception.GetType().FullName}: {exception.Message}"
+ };
+ cleanedStackTrace.AddRange(stackFrames);
+ logEntry.TryAdd("stackTrace", cleanedStackTrace);
+ }
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index de6543e1..2185ec68 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -52,11 +52,6 @@ public class PowertoolsLoggerConfiguration : IOptions
public double SamplingRate { get; set; }
- ///
- /// Whether sampling was enabled for the current request
- ///
- public bool SamplingEnabled { get; set; }
-
///
/// The logger output case.
/// This can be also set using the environment variable POWERTOOLS_LOGGER_CASE.
@@ -270,7 +265,6 @@ public PowertoolsLoggerConfiguration Clone()
Service = Service,
MinimumLevel = MinimumLevel,
SamplingRate = SamplingRate,
- SamplingEnabled = SamplingEnabled, // Copy the sampling decision
LoggerOutputCase = LoggerOutputCase,
LoggerOutput = LoggerOutput,
LogLevelKey = LogLevelKey,
From 70ba886ed3aa2fd3293a02617ef60e3dbc4d01cb Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Wed, 19 Mar 2025 13:01:11 +0000
Subject: [PATCH 07/49] combine context, remove json key
---
.../Internal/PowertoolsLogger.cs | 85 ++++++++++++-------
.../Logger.Scope.cs | 9 ++
.../LoggerExtensions.cs | 59 ++++++++++++-
.../PowertoolsLoggerConfiguration.cs | 29 ++++++-
.../PowertoolsLoggingSerializer.cs | 62 ++++++++++++--
5 files changed, 203 insertions(+), 41 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
index 8793113e..1657d8b5 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -136,34 +136,37 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
var timestamp = DateTime.UtcNow;
- // Extract structured logging parameters
- var structuredParameters = ExtractStructuredParameters(state, out string messageTemplate);
+ // Check if state is a direct object (not structured logging)
+ bool isDirectObjectLog = state != null &&
+ !(state is IEnumerable>) &&
+ !(state is string);
- // Format the message using the provided formatter
- var message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null
- ? customMessage
- : formatter(state, exception);
-
- // // For better object representation in messages
- // if (message != null && message.ToString().Contains(".") &&
- // message.ToString().EndsWith("Class") &&
- // structuredParameters.Count > 0)
- // {
- // // This might be a ToString() of an object class - let's try to use a better representation
- // var objName = message.ToString().Split('.').Last();
- // if (structuredParameters.Count == 1)
- // {
- // // If we have a single parameter, try to represent it nicely in the log
- // var param = structuredParameters.First();
- // message = $"{param.Key}: {(param.Value != null ? "[object]" : "null")}";
- // }
- // }
+ // Extract structured parameters for template-style logging
+ var structuredParameters = isDirectObjectLog
+ ? new Dictionary()
+ : ExtractStructuredParameters(state, out string messageTemplate);
+
+ // Format the message
+ object message;
+ if (isDirectObjectLog)
+ {
+ // For direct object logging, use the object itself
+ message = state;
+ }
+ else
+ {
+ // For structured logging or regular string messages
+ message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null
+ ? customMessage
+ : formatter(state, exception);
+ }
+ // Get log entry
var logFormatter = Logger.GetFormatter();
var logEntry = logFormatter is null
? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters)
: GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters);
-
+
_systemWrapper.LogLine(PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object)));
}
@@ -182,7 +185,10 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
// Add Custom Keys
foreach (var (key, value) in Logger.GetAllKeys())
{
- logEntry.TryAdd(key, value);
+ if (key != "json") // Skip the json key
+ {
+ logEntry.TryAdd(key, value);
+ }
}
// Add Lambda Context Keys
@@ -196,18 +202,22 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
{
foreach (var (key, value) in CurrentScope.ExtraKeys)
{
- if (!string.IsNullOrWhiteSpace(key))
+ if (!string.IsNullOrWhiteSpace(key) && key != "json")
+ {
logEntry.TryAdd(key, value);
+ }
}
}
// Add structured parameters
- if (structuredParameters != null)
+ if (structuredParameters != null && structuredParameters.Count > 0)
{
foreach (var (key, value) in structuredParameters)
{
- if (!string.IsNullOrWhiteSpace(key))
+ if (!string.IsNullOrWhiteSpace(key) && key != "json")
+ {
logEntry.TryAdd(key, value);
+ }
}
}
@@ -236,6 +246,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
/// The message to be written. Can be also an object.
/// The exception related to this entry.
/// The custom log entry formatter.
+ /// The structured parameters.
private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, object message,
Exception exception, ILogFormatter logFormatter, Dictionary structuredParameters)
{
@@ -258,6 +269,8 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
// Add Custom Keys
foreach (var (key, value) in Logger.GetAllKeys())
{
+ if (key == "json") continue; // Skip json key
+
switch (key)
{
case LoggingConstants.KeyColdStart:
@@ -280,18 +293,22 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
{
foreach (var (key, value) in CurrentScope.ExtraKeys)
{
- if (!string.IsNullOrWhiteSpace(key))
+ if (!string.IsNullOrWhiteSpace(key) && key != "json")
+ {
extraKeys.TryAdd(key, value);
+ }
}
}
// Add structured parameters
- if (structuredParameters != null)
+ if (structuredParameters != null && structuredParameters.Count > 0)
{
foreach (var (key, value) in structuredParameters)
{
- if (!string.IsNullOrWhiteSpace(key))
+ if (!string.IsNullOrWhiteSpace(key) && key != "json")
+ {
extraKeys.TryAdd(key, value);
+ }
}
}
@@ -322,6 +339,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
var logObject = logFormatter.FormatLogEntry(logEntry);
if (logObject is null)
throw new LogFormatException($"{logFormatter.GetType().FullName} returned Null value.");
+
#if NET8_0_OR_GREATER
return PowertoolsLoggerHelpers.ObjectToDictionary(logObject);
#else
@@ -456,6 +474,15 @@ private Dictionary ExtractStructuredParameters(TState st
messageTemplate = string.Empty;
var parameters = new Dictionary();
+ // Handle direct object logging - when an object is passed directly without a message template
+ if (state != null &&
+ !(state is IEnumerable>) &&
+ !(state is string))
+ {
+ // No structured parameters for direct object logging
+ return parameters;
+ }
+
if (state is IEnumerable> stateProps)
{
// Dictionary to store format specifiers for each parameter
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
index 94642122..23526cd5 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs
@@ -96,4 +96,13 @@ internal static void RemoveAllKeys()
{
Scope.Clear();
}
+
+ ///
+ /// Removes a key from the log context.
+ ///
+ public static void RemoveKey(string key)
+ {
+ if (Scope.ContainsKey(key))
+ Scope.Remove(key);
+ }
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
index e04a8fd7..0c1c6de8 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
@@ -14,6 +14,7 @@
*/
using System;
+using System.Collections.Generic;
using AWS.Lambda.Powertools.Logging.Internal;
using Microsoft.Extensions.Logging;
@@ -652,19 +653,73 @@ public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, s
#endregion
#endregion
-
+
+
+ ///
+ /// Appending additional key to the log context.
+ ///
+ ///
+ /// The list of keys.
+ public static void AppendKeys(this ILogger logger,IEnumerable> keys)
+ {
+ Logger.AppendKeys(keys);
+ }
+
+ ///
+ /// Appending additional key to the log context.
+ ///
+ ///
+ /// The list of keys.
+ public static void AppendKeys(this ILogger logger,IEnumerable> keys)
+ {
+ Logger.AppendKeys(keys);
+ }
+
+ ///
+ /// Appending additional key to the log context.
+ ///
+ ///
+ /// The key.
+ /// The value.
+ /// key
+ /// value
public static void AppendKey(this ILogger logger, string key, object value)
{
Logger.AppendKey(key, value);
}
+ ///
+ /// Returns all additional keys added to the log context.
+ ///
+ /// IEnumerable<KeyValuePair<System.String, System.Object>>.
+ public static IEnumerable> GetAllKeys(this ILogger logger)
+ {
+ return Logger.GetAllKeys();
+ }
+
+ ///
+ /// Removes all additional keys from the log context.
+ ///
internal static void RemoveAllKeys(this ILogger logger)
{
Logger.RemoveAllKeys();
}
-
+
+ ///
+ /// Remove additional keys from the log context.
+ ///
+ ///
+ /// The list of keys.
public static void RemoveKeys(this ILogger logger, params string[] keys)
{
Logger.RemoveKeys(keys);
}
+
+ ///
+ /// Removes a key from the log context.
+ ///
+ public static void RemoveKey(this ILogger logger, string key)
+ {
+ Logger.RemoveKey(key);
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index 2185ec68..54aaf0a7 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -22,6 +22,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Common.Utils;
using AWS.Lambda.Powertools.Logging.Serializers;
namespace AWS.Lambda.Powertools.Logging;
@@ -106,6 +107,13 @@ public JsonSerializerContext? JsonContext
{
_jsonContext = value;
ApplyJsonContext();
+
+ // If we have existing JSON options, update their type resolver
+ if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported)
+ {
+ // Reset the type resolver chain to rebuild it
+ _jsonOptions.TypeInfoResolver = GetCompositeResolver();
+ }
}
}
@@ -114,10 +122,21 @@ public JsonSerializerContext? JsonContext
///
public void AddJsonContext(JsonSerializerContext context)
{
- if (context != null && !_additionalContexts.Contains(context))
+ if (context == null)
+ return;
+
+ // Don't add duplicates
+ if (!_additionalContexts.Contains(context))
{
_additionalContexts.Add(context);
ApplyAdditionalJsonContext(context);
+
+ // If we have existing JSON options, update their type resolver
+ if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported)
+ {
+ // Reset the type resolver chain to rebuild it
+ _jsonOptions.TypeInfoResolver = GetCompositeResolver();
+ }
}
}
@@ -139,11 +158,17 @@ public void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
{
if (options == null) return;
- // Check for TypeInfoResolver
+ // Check for TypeInfoResolver and ensure it's not lost
if (options.TypeInfoResolver != null &&
options.TypeInfoResolver != GetCompositeResolver())
{
_customTypeInfoResolver = options.TypeInfoResolver;
+
+ // If it's a JsonSerializerContext, also add it to our contexts
+ if (_customTypeInfoResolver is JsonSerializerContext jsonContext)
+ {
+ AddJsonContext(jsonContext);
+ }
}
// Check for TypeInfoResolverChain
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index ef54d3a5..cc207191 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -97,19 +97,42 @@ internal static string Serialize(object value, Type inputType)
#else
if (RuntimeFeatureWrapper.IsDynamicCodeSupported)
{
- var options = GetSerializerOptions();
+ var jsonSerializerOptions = GetSerializerOptions();
#pragma warning disable
- return JsonSerializer.Serialize(value, options);
+ return JsonSerializer.Serialize(value, jsonSerializerOptions);
}
-
- var typeInfo = GetTypeInfo(inputType);
- if (typeInfo == null)
+
+ var options = GetSerializerOptions();
+
+ // Try to serialize using the configured TypeInfoResolver
+ try
+ {
+ var typeInfo = GetTypeInfo(inputType);
+ if (typeInfo != null)
+ {
+ return JsonSerializer.Serialize(value, typeInfo);
+ }
+ }
+ catch (InvalidOperationException)
+ {
+ // Failed to get typeinfo, will fall back to trying the serializer directly
+ }
+
+ // Fall back to direct serialization which may work if the resolver chain can handle it
+ try
+ {
+ return JsonSerializer.Serialize(value, inputType, options);
+ }
+ catch (JsonException ex)
{
throw new JsonSerializerException(
- $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.");
+ $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.", ex);
+ }
+ catch (InvalidOperationException ex)
+ {
+ throw new JsonSerializerException(
+ $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.", ex);
}
-
- return JsonSerializer.Serialize(value, typeInfo);
#endif
}
@@ -139,6 +162,26 @@ internal static JsonTypeInfo GetTypeInfo(Type type)
var options = GetSerializerOptions();
return options.TypeInfoResolver?.GetTypeInfo(type, options);
}
+
+ ///
+ /// Checks if a type is supported by any of the configured type resolvers
+ ///
+ internal static bool IsTypeSupportedByAnyResolver(Type type)
+ {
+ var options = GetSerializerOptions();
+ if (options.TypeInfoResolver == null)
+ return false;
+
+ try
+ {
+ var typeInfo = options.TypeInfoResolver.GetTypeInfo(type, options);
+ return typeInfo != null;
+ }
+ catch
+ {
+ return false;
+ }
+ }
#endif
///
@@ -192,7 +235,10 @@ private static void BuildJsonSerializerOptions()
// Only add TypeInfoResolver if AOT mode
if (!RuntimeFeatureWrapper.IsDynamicCodeSupported)
{
+ // Always ensure our default context is in the chain first
_jsonOptions.TypeInfoResolverChain.Add(PowertoolsLoggingSerializationContext.Default);
+
+ // Add all registered contexts
foreach (var context in AdditionalContexts)
{
_jsonOptions.TypeInfoResolverChain.Add(context);
From 1f5ea65d3078ace1ef8cc4a00ab2d8d9495cf47c Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Wed, 19 Mar 2025 15:02:22 +0000
Subject: [PATCH 08/49] add new LoggerFactory, add builder
---
.../BuilderExtensions.cs | 4 +-
.../Internal/LoggerProvider.cs | 14 ++--
.../Internal/LoggingAspect.cs | 6 +-
.../Internal/PowertoolsLogger.cs | 52 ++----------
.../AWS.Lambda.Powertools.Logging/Logger.cs | 21 +++--
.../PowertoolsLoggerBuilder.cs | 79 ++++++++++++++++++
.../PowertoolsLoggerConfiguration.cs | 51 +++---------
.../PowertoolsLoggerFactory.cs | 18 ++--
.../PowertoolsLoggerFactoryBuilder.cs | 83 -------------------
.../PowertoolsLoggerFactoryExtensions.cs | 19 +++++
10 files changed, 149 insertions(+), 198 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
delete mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 923d432a..5f627b2d 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -33,9 +33,9 @@ public static ILoggingBuilder AddPowertoolsLogger(
// IMPORTANT: Set the minimum level directly on the builder
- if (options.MinimumLevel != LogLevel.None)
+ if (options.MinimumLogLevel != LogLevel.None)
{
- builder.SetMinimumLevel(options.MinimumLevel);
+ builder.SetMinimumLevel(options.MinimumLogLevel);
}
// Configure options for DI
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
index 6b1ddcbb..852d9962 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
@@ -76,6 +76,8 @@ public LoggerProvider(IOptionsMonitor config,
/// The instance of that was created.
public ILogger CreateLogger(string categoryName)
{
+ _powertoolsConfigurations.SetExecutionEnvironment(typeof(PowertoolsLogger));
+
return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger(name,
() => _currentConfig,
_systemWrapper));
@@ -97,13 +99,13 @@ private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
// Check for explicit config
- bool hasExplicitLevel = config.MinimumLevel != LogLevel.None;
+ bool hasExplicitLevel = config.MinimumLogLevel != LogLevel.None;
// Warn if Lambda log level doesn't match
- if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLevel < lambdaLogLevel)
+ if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLogLevel < lambdaLogLevel)
{
_systemWrapper.LogLine(
- $"Current log level ({config.MinimumLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
+ $"Current log level ({config.MinimumLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
}
// Set service from environment if not explicitly set
@@ -123,7 +125,7 @@ private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
if (!hasExplicitLevel)
{
var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
- config.MinimumLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
+ config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
}
// Always configure the serializer with the output case
@@ -145,7 +147,7 @@ private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
? config.SamplingRate
: _powertoolsConfigurations.LoggerSampleRate;
- samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLevel, _systemWrapper);
+ samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLogLevel, _systemWrapper);
config.SamplingRate = samplingRate;
// Only notify if sampling is configured
@@ -158,7 +160,7 @@ private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
{
_systemWrapper.LogLine(
$"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
- config.MinimumLevel = LogLevel.Debug;
+ config.MinimumLogLevel = LogLevel.Debug;
}
}
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index f733608a..4e41fc82 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -92,7 +92,7 @@ private void InitializeLogger(LoggingAttribute trigger)
var config = new PowertoolsLoggerConfiguration
{
// Use sensible defaults if not specified in the attribute
- MinimumLevel = trigger.LogLevel != LogLevel.None ? trigger.LogLevel : LogLevel.Information,
+ MinimumLogLevel = trigger.LogLevel != LogLevel.None ? trigger.LogLevel : LogLevel.Information,
Service = !string.IsNullOrEmpty(trigger.Service) ? trigger.Service : "service_undefined",
LoggerOutputCase = trigger.LoggerOutputCase != default ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
SamplingRate = trigger.SamplingRate > 0 ? trigger.SamplingRate : 1.0
@@ -103,10 +103,10 @@ private void InitializeLogger(LoggingAttribute trigger)
}
// Get logger after configuration
- _logger = Logger.GetLogger();
+ _logger = Logger.GetPowertoolsLogger();
// Set debug flag based on the minimum level from Logger
- _isDebug = Logger.GetConfiguration().MinimumLevel <= LogLevel.Debug;
+ _isDebug = Logger.GetConfiguration().MinimumLogLevel <= LogLevel.Debug;
}
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
index 1657d8b5..396839df 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -66,9 +66,6 @@ public PowertoolsLogger(
_categoryName = categoryName;
_currentConfig = getCurrentConfig();
_systemWrapper = systemWrapper;
-
- // TODO: Fix
- // _powertoolsConfigurations.SetExecutionEnvironment(this);
}
///
@@ -100,8 +97,8 @@ internal void EndScope()
public bool IsEnabled(LogLevel logLevel)
{
// If we have no explicit minimum level, use the default
- var effectiveMinLevel = _currentConfig.MinimumLevel != LogLevel.None
- ? _currentConfig.MinimumLevel
+ var effectiveMinLevel = _currentConfig.MinimumLogLevel != LogLevel.None
+ ? _currentConfig.MinimumLogLevel
: LoggingConstants.DefaultLogLevel;
// Log diagnostic info for Debug/Trace levels
@@ -136,30 +133,13 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
var timestamp = DateTime.UtcNow;
- // Check if state is a direct object (not structured logging)
- bool isDirectObjectLog = state != null &&
- !(state is IEnumerable>) &&
- !(state is string);
-
// Extract structured parameters for template-style logging
- var structuredParameters = isDirectObjectLog
- ? new Dictionary()
- : ExtractStructuredParameters(state, out string messageTemplate);
+ var structuredParameters = ExtractStructuredParameters(state, out string messageTemplate);
// Format the message
- object message;
- if (isDirectObjectLog)
- {
- // For direct object logging, use the object itself
- message = state;
- }
- else
- {
- // For structured logging or regular string messages
- message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null
- ? customMessage
- : formatter(state, exception);
- }
+ var message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null
+ ? customMessage
+ : formatter(state, exception);
// Get log entry
var logFormatter = Logger.GetFormatter();
@@ -185,10 +165,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
// Add Custom Keys
foreach (var (key, value) in Logger.GetAllKeys())
{
- if (key != "json") // Skip the json key
- {
- logEntry.TryAdd(key, value);
- }
+ logEntry.TryAdd(key, value);
}
// Add Lambda Context Keys
@@ -202,7 +179,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
{
foreach (var (key, value) in CurrentScope.ExtraKeys)
{
- if (!string.IsNullOrWhiteSpace(key) && key != "json")
+ if (!string.IsNullOrWhiteSpace(key))
{
logEntry.TryAdd(key, value);
}
@@ -269,8 +246,6 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
// Add Custom Keys
foreach (var (key, value) in Logger.GetAllKeys())
{
- if (key == "json") continue; // Skip json key
-
switch (key)
{
case LoggingConstants.KeyColdStart:
@@ -293,7 +268,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
{
foreach (var (key, value) in CurrentScope.ExtraKeys)
{
- if (!string.IsNullOrWhiteSpace(key) && key != "json")
+ if (!string.IsNullOrWhiteSpace(key))
{
extraKeys.TryAdd(key, value);
}
@@ -474,15 +449,6 @@ private Dictionary ExtractStructuredParameters(TState st
messageTemplate = string.Empty;
var parameters = new Dictionary();
- // Handle direct object logging - when an object is passed directly without a message template
- if (state != null &&
- !(state is IEnumerable>) &&
- !(state is string))
- {
- // No structured parameters for direct object logging
- return parameters;
- }
-
if (state is IEnumerable> stateProps)
{
// Dictionary to store format specifiers for each parameter
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index 457d87d5..d25fb151 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -57,13 +57,13 @@ public static void Configure(Action configureOpti
}
// Configure with existing factory
- public static void Configure(ILoggerFactory loggerFactory)
+ internal static void Configure(ILoggerFactory loggerFactory)
{
Interlocked.Exchange(ref _factoryLazy,
new Lazy(() => loggerFactory));
Interlocked.Exchange(ref _defaultLoggerLazy,
- new Lazy(() => Factory.CreateLogger("PowertoolsLogger")));
+ new Lazy(() => Factory.CreatePowertoolsLogger()));
_isConfigured = true;
}
@@ -83,9 +83,9 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
var factory = LoggerFactory.Create(builder =>
{
// Set minimum level directly on builder
- if (options.MinimumLevel != LogLevel.None)
+ if (options.MinimumLogLevel != LogLevel.None)
{
- builder.SetMinimumLevel(options.MinimumLevel);
+ builder.SetMinimumLevel(options.MinimumLogLevel);
}
// Add our provider - the config's OutputLogger will be used
@@ -94,14 +94,12 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
builder.AddPowertoolsLogger(config =>
{
config.Service = _currentConfig.Service;
- config.MinimumLevel = _currentConfig.MinimumLevel;
+ config.MinimumLogLevel = _currentConfig.MinimumLogLevel;
config.LoggerOutputCase = _currentConfig.LoggerOutputCase;
config.SamplingRate = _currentConfig.SamplingRate;
config.LoggerOutput = _currentConfig.LoggerOutput;
config.JsonOptions = _currentConfig.JsonOptions;
-#if NET8_0_OR_GREATER
- config.JsonContext = _currentConfig.JsonContext;
-#endif
+
}, true);
});
@@ -110,14 +108,13 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
new Lazy(() => factory));
Interlocked.Exchange(ref _defaultLoggerLazy,
- new Lazy(() => Factory.CreateLogger("PowertoolsLogger")));
+ new Lazy(() => Factory.CreatePowertoolsLogger()));
_isConfigured = true;
}
- // Add this method to the Logger class
// Get the current configuration
- public static PowertoolsLoggerConfiguration GetConfiguration()
+ internal static PowertoolsLoggerConfiguration GetConfiguration()
{
// Ensure logger is initialized
_ = LoggerInstance;
@@ -136,6 +133,8 @@ public static PowertoolsLoggerConfiguration GetConfiguration()
public static ILogger GetLogger(string category) => Factory.CreateLogger(category);
+ public static ILogger GetPowertoolsLogger() => Factory.CreatePowertoolsLogger();
+
// For testing purposes
// internal static void Reset()
// {
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
new file mode 100644
index 00000000..b5d10747
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using AWS.Lambda.Powertools.Common;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public class PowertoolsLoggerBuilder
+{
+ private readonly PowertoolsLoggerConfiguration _configuration = new();
+
+ public PowertoolsLoggerBuilder WithService(string service)
+ {
+ _configuration.Service = service;
+ return this;
+ }
+
+ public PowertoolsLoggerBuilder WithSamplingRate(double rate)
+ {
+ _configuration.SamplingRate = rate;
+ return this;
+ }
+
+ public PowertoolsLoggerBuilder WithMinimumLogLevel(LogLevel level)
+ {
+ _configuration.MinimumLogLevel = level;
+ return this;
+ }
+
+ public PowertoolsLoggerBuilder WithJsonOptions(JsonSerializerOptions options)
+ {
+ _configuration.JsonOptions = options;
+ return this;
+ }
+
+ public PowertoolsLoggerBuilder WithTimestampFormat(string format)
+ {
+ _configuration.TimestampFormat = format;
+ return this;
+ }
+
+ public PowertoolsLoggerBuilder WithOutputCase(LoggerOutputCase outputCase)
+ {
+ _configuration.LoggerOutputCase = outputCase;
+ return this;
+ }
+
+ public PowertoolsLoggerBuilder WithOutput(ISystemWrapper output)
+ {
+ _configuration.LoggerOutput = output;
+ return this;
+ }
+
+ public ILogger Build()
+ {
+ var factory = LoggerFactory.Create(builder =>
+ {
+ builder.AddPowertoolsLogger(config =>
+ {
+ config.Service = _configuration.Service;
+ config.SamplingRate = _configuration.SamplingRate;
+ config.MinimumLogLevel = _configuration.MinimumLogLevel;
+ config.LoggerOutputCase = _configuration.LoggerOutputCase;
+ config.LoggerOutput = _configuration.LoggerOutput;
+ config.JsonOptions = _configuration.JsonOptions;
+ config.TimestampFormat = _configuration.TimestampFormat; // Add this line
+
+ // foreach (var context in _configuration.GetAdditionalContexts())
+ // {
+ // config.AddJsonContext(context);
+ // }
+ });
+ });
+
+ Logger.Configure(factory); // Configure the static logger
+ return factory.CreatePowertoolsLogger();
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index 54aaf0a7..242226c9 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -33,7 +33,7 @@ namespace AWS.Lambda.Powertools.Logging;
///
public class PowertoolsLoggerConfiguration : IOptions
{
- public const string ConfigurationSectionName = "PowertoolsLogger";
+ public const string ConfigurationSectionName = "AWS.Lambda.Powertools.Logging.Logger";
///
/// Service name is used for logging.
@@ -45,7 +45,7 @@ public class PowertoolsLoggerConfiguration : IOptionsPOWERTOOLS_LOG_LEVEL.
///
- public LogLevel MinimumLevel { get; set; } = LogLevel.None;
+ public LogLevel MinimumLogLevel { get; set; } = LogLevel.None;
///
/// Dynamically set a percentage of logs to DEBUG level.
@@ -69,7 +69,6 @@ public class PowertoolsLoggerConfiguration : IOptions
public ISystemWrapper? LoggerOutput { get; set; }
-
///
/// JSON serializer options to use for log serialization
///
@@ -97,30 +96,10 @@ public JsonSerializerOptions? JsonOptions
private JsonSerializerContext? _jsonContext = PowertoolsLoggingSerializationContext.Default;
private readonly List _additionalContexts = new();
- ///
- /// Main JSON context to use for serialization
- ///
- public JsonSerializerContext? JsonContext
- {
- get => _jsonContext;
- set
- {
- _jsonContext = value;
- ApplyJsonContext();
-
- // If we have existing JSON options, update their type resolver
- if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported)
- {
- // Reset the type resolver chain to rebuild it
- _jsonOptions.TypeInfoResolver = GetCompositeResolver();
- }
- }
- }
-
///
/// Add additional JsonSerializerContext for client types
///
- public void AddJsonContext(JsonSerializerContext context)
+ internal void AddJsonContext(JsonSerializerContext context)
{
if (context == null)
return;
@@ -143,7 +122,7 @@ public void AddJsonContext(JsonSerializerContext context)
///
/// Get all additional contexts
///
- public IReadOnlyList GetAdditionalContexts()
+ internal IReadOnlyList GetAdditionalContexts()
{
return _additionalContexts.AsReadOnly();
}
@@ -154,7 +133,7 @@ public IReadOnlyList GetAdditionalContexts()
///
/// Process JSON options type resolver information
///
- public void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
+ internal void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
{
if (options == null) return;
@@ -207,7 +186,7 @@ public void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
///
/// Get a composite resolver that includes all configured resolvers
///
- public IJsonTypeInfoResolver GetCompositeResolver()
+ internal IJsonTypeInfoResolver GetCompositeResolver()
{
var resolvers = new List();
@@ -241,17 +220,6 @@ public IJsonTypeInfoResolver GetCompositeResolver()
return new CompositeJsonTypeInfoResolver(resolvers.ToArray());
}
- ///
- /// Apply JSON context to serializer
- ///
- private void ApplyJsonContext()
- {
- if (_jsonContext != null)
- {
- PowertoolsLoggingSerializer.SetDefaultContext(_jsonContext);
- }
- }
-
///
/// Apply additional JSON context to serializer
///
@@ -275,7 +243,7 @@ private void ApplyJsonOptions()
///
/// Apply output case configuration
///
- public void ApplyOutputCase()
+ internal void ApplyOutputCase()
{
PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase);
}
@@ -283,12 +251,12 @@ public void ApplyOutputCase()
///
/// Clone this configuration
///
- public PowertoolsLoggerConfiguration Clone()
+ internal PowertoolsLoggerConfiguration Clone()
{
var clone = new PowertoolsLoggerConfiguration
{
Service = Service,
- MinimumLevel = MinimumLevel,
+ MinimumLogLevel = MinimumLogLevel,
SamplingRate = SamplingRate,
LoggerOutputCase = LoggerOutputCase,
LoggerOutput = LoggerOutput,
@@ -316,4 +284,5 @@ public PowertoolsLoggerConfiguration Clone()
// IOptions implementation
PowertoolsLoggerConfiguration IOptions.Value => this;
+ public string TimestampFormat { get; set; }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
index a939ab9c..7794b787 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
@@ -4,7 +4,7 @@
namespace AWS.Lambda.Powertools.Logging;
-public sealed class PowertoolsLoggerFactory : IDisposable
+internal sealed class PowertoolsLoggerFactory : IDisposable
{
private readonly ILoggerFactory _factory;
@@ -31,7 +31,7 @@ public static PowertoolsLoggerFactory Create(Action() => CreateLogger(typeof(T).FullName ?? typeof(T).Name);
@@ -66,6 +61,11 @@ public ILogger CreateLogger(string category)
return _factory.CreateLogger(category);
}
+ public ILogger CreatePowertoolsLogger()
+ {
+ return _factory.CreatePowertoolsLogger();
+ }
+
public void Dispose()
{
_factory?.Dispose();
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs
deleted file mode 100644
index 870c8ada..00000000
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryBuilder.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using Microsoft.Extensions.Logging;
-
-namespace AWS.Lambda.Powertools.Logging;
-
-public class PowertoolsLoggerFactoryBuilder
-{
- private readonly PowertoolsLoggerConfiguration _configuration = new();
-
- // public PowertoolsLoggerFactoryBuilder UseEnvironmentVariables(bool enabled)
- // {
- // _configuration.UseEnvironmentVariables = enabled;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder SetLogLevelColor(AppLogLevel level, ConsoleColor color)
- // {
- // _configuration.LogLevelToColorMap[level] = color;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder SetEventId(int eventId)
- // {
- // _configuration.EventId = eventId;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder SetJsonOptions(JsonSerializerOptions options)
- // {
- // _configuration.JsonOptions = options;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder UseJsonOutput(bool enabled = true)
- // {
- // _configuration.UseJsonOutput = enabled;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder SetTimestampFormat(string format)
- // {
- // _configuration.TimestampFormat = format;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder UseJsonContext(JsonSerializerContext context)
- // {
- // _configuration.JsonContext = context;
- // return this;
- // }
- //
- // public PowertoolsLoggerFactoryBuilder AddJsonContext(JsonSerializerContext context)
- // {
- // _configuration.AddJsonContext(context);
- // return this;
- // }
- //
- // public PowertoolsLoggerFactory Build()
- // {
- // var factory = LoggerFactory.Create(builder =>
- // {
- // builder.AddPowertoolsLogger(config =>
- // {
- // config.UseEnvironmentVariables = _configuration.UseEnvironmentVariables;
- // config.EventId = _configuration.EventId;
- // config.JsonOptions = _configuration.JsonOptions;
- // config.UseJsonOutput = _configuration.UseJsonOutput; // Add this line
- // config.TimestampFormat = _configuration.TimestampFormat; // Add this line
- // config.JsonContext = _configuration.JsonContext;
- //
- // foreach (var kvp in _configuration.LogLevelToColorMap)
- // {
- // config.LogLevelToColorMap[kvp.Key] = kvp.Value;
- // }
- // });
- // });
- //
- // Logger.Configure(factory); // Configure the static logger
- // return new PowertoolsLoggerFactory(factory);
- // }
-}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs
new file mode 100644
index 00000000..edec07fd
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+///
+/// Extensions for ILoggerFactory
+///
+public static class PowertoolsLoggerFactoryExtensions
+{
+ ///
+ /// Creates a new Powertools Logger instance using the Powertools full name.
+ ///
+ /// The factory.
+ /// The that was created.
+ public static ILogger CreatePowertoolsLogger(this ILoggerFactory factory)
+ {
+ return new PowertoolsLoggerFactory(factory).CreateLogger(PowertoolsLoggerConfiguration.ConfigurationSectionName);
+ }
+}
\ No newline at end of file
From d865fa26ff9c134ad2388f936861ba167abab2ec Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Wed, 19 Mar 2025 16:29:17 +0000
Subject: [PATCH 09/49] refactor builder, configurations add timestamp format
---
.../BuilderExtensions.cs | 18 +--
.../Helpers/ConfigurationExtensions.cs | 29 ++++
.../Internal/PowertoolsLogger.cs | 2 +-
.../AWS.Lambda.Powertools.Logging/Logger.cs | 32 +----
.../PowertoolsLoggerBuilder.cs | 16 +--
.../PowertoolsLoggerConfiguration.cs | 73 +---------
.../PowertoolsLoggerFactory.cs | 24 ++--
.../PowertoolsLoggingSerializer.cs | 130 ++++++------------
8 files changed, 104 insertions(+), 220 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 5f627b2d..33143f3c 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -18,8 +18,7 @@ public static class BuilderExtensions
// Single base method that all other overloads call
public static ILoggingBuilder AddPowertoolsLogger(
this ILoggingBuilder builder,
- Action? configure = null,
- bool fromLoggerConfigure = false)
+ Action? configure = null)
{
// Add configuration
builder.AddConfiguration();
@@ -30,14 +29,15 @@ public static ILoggingBuilder AddPowertoolsLogger(
// Create initial configuration
var options = new PowertoolsLoggerConfiguration();
configure(options);
-
-
+
// IMPORTANT: Set the minimum level directly on the builder
- if (options.MinimumLogLevel != LogLevel.None)
+ if (options.MinimumLogLevel != LogLevel.None)
{
builder.SetMinimumLevel(options.MinimumLogLevel);
}
+ // Add filters here
+
// Configure options for DI
builder.Services.Configure(configure);
@@ -48,7 +48,7 @@ public static ILoggingBuilder AddPowertoolsLogger(
PowertoolsLoggingSerializer.ConfigureNamingPolicy(options.LoggerOutputCase);
// Configure static Logger (if not already in a configuration cycle)
- if (!fromLoggerConfigure && !_configuring)
+ if (!_configuring)
{
try
{
@@ -74,12 +74,12 @@ private static void RegisterServices(ILoggingBuilder builder)
{
// Register ISystemWrapper if not already registered
builder.Services.TryAddSingleton();
-
+
// Register IPowertoolsEnvironment if it exists
builder.Services.TryAddSingleton();
-
+
// Register IPowertoolsConfigurations with all its dependencies
- builder.Services.TryAddSingleton(sp =>
+ builder.Services.TryAddSingleton(sp =>
new PowertoolsConfigurations(sp.GetRequiredService()));
// Register the provider
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
new file mode 100644
index 00000000..4a8f6c8e
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
+
+///
+/// Extension methods for handling configuration copying between PowertoolsLogger configurations
+///
+internal static class ConfigurationExtensions
+{
+ ///
+ /// Copies configuration values from source to destination configuration
+ ///
+ /// The destination configuration to copy values to
+ /// The source configuration to copy values from
+ /// The updated destination configuration
+ public static PowertoolsLoggerConfiguration CopyFrom(this PowertoolsLoggerConfiguration destination, PowertoolsLoggerConfiguration source)
+ {
+ destination.Service = source.Service;
+ destination.SamplingRate = source.SamplingRate;
+ destination.MinimumLogLevel = source.MinimumLogLevel;
+ destination.LoggerOutputCase = source.LoggerOutputCase;
+ destination.LoggerOutput = source.LoggerOutput;
+ destination.JsonOptions = source.JsonOptions;
+ destination.TimestampFormat = source.TimestampFormat;
+
+ return destination;
+ }
+}
\ 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 396839df..902b40af 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -198,7 +198,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
}
}
- logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString("o"));
+ logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString( _currentConfig.TimestampFormat ?? "o"));
logEntry.TryAdd(_currentConfig.LogLevelKey, logLevel.ToString());
logEntry.TryAdd(LoggingConstants.KeyService, _currentConfig.Service);
logEntry.TryAdd(LoggingConstants.KeyLoggerName, _categoryName);
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index d25fb151..bdcdbe72 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -73,39 +73,9 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
- // Store the configuration
- _currentConfig = options.Clone();
-
- // Create a system wrapper if needed
- var systemWrapper = options.LoggerOutput ?? new SystemWrapper();
-
- // Create a factory with our provider
- var factory = LoggerFactory.Create(builder =>
- {
- // Set minimum level directly on builder
- if (options.MinimumLogLevel != LogLevel.None)
- {
- builder.SetMinimumLevel(options.MinimumLogLevel);
- }
-
- // Add our provider - the config's OutputLogger will be used
- builder.Services.AddSingleton(systemWrapper);
-
- builder.AddPowertoolsLogger(config =>
- {
- config.Service = _currentConfig.Service;
- config.MinimumLogLevel = _currentConfig.MinimumLogLevel;
- config.LoggerOutputCase = _currentConfig.LoggerOutputCase;
- config.SamplingRate = _currentConfig.SamplingRate;
- config.LoggerOutput = _currentConfig.LoggerOutput;
- config.JsonOptions = _currentConfig.JsonOptions;
-
- }, true);
- });
-
// Update factory and logger
Interlocked.Exchange(ref _factoryLazy,
- new Lazy(() => factory));
+ new Lazy(() => PowertoolsLoggerFactory.Create(options)));
Interlocked.Exchange(ref _defaultLoggerLazy,
new Lazy(() => Factory.CreatePowertoolsLogger()));
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
index b5d10747..b4411f74 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
@@ -2,6 +2,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Internal.Helpers;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging;
@@ -58,21 +59,10 @@ public ILogger Build()
{
builder.AddPowertoolsLogger(config =>
{
- config.Service = _configuration.Service;
- config.SamplingRate = _configuration.SamplingRate;
- config.MinimumLogLevel = _configuration.MinimumLogLevel;
- config.LoggerOutputCase = _configuration.LoggerOutputCase;
- config.LoggerOutput = _configuration.LoggerOutput;
- config.JsonOptions = _configuration.JsonOptions;
- config.TimestampFormat = _configuration.TimestampFormat; // Add this line
-
- // foreach (var context in _configuration.GetAdditionalContexts())
- // {
- // config.AddJsonContext(context);
- // }
+ config.CopyFrom(_configuration);
});
});
-
+
Logger.Configure(factory); // Configure the static logger
return factory.CreatePowertoolsLogger();
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index 242226c9..27e3ef2a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -40,6 +40,11 @@ public class PowertoolsLoggerConfiguration : IOptionsPOWERTOOLS_SERVICE_NAME.
///
public string? Service { get; set; } = null;
+
+ ///
+ /// Timestamp format for logging.
+ ///
+ public string? TimestampFormat { get; set; }
///
/// Specify the minimum log level for logging (Information, by default).
@@ -149,38 +154,6 @@ internal void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
AddJsonContext(jsonContext);
}
}
-
- // Check for TypeInfoResolverChain
- if (options.TypeInfoResolverChain != null && options.TypeInfoResolverChain.Count > 0)
- {
- foreach (var resolver in options.TypeInfoResolverChain)
- {
- // If it's a JsonSerializerContext, add it to our additional contexts
- if (resolver is JsonSerializerContext context)
- {
- AddJsonContext(context);
- }
- // Otherwise store it as a custom resolver
- else if (resolver is IJsonTypeInfoResolver customResolver &&
- customResolver != GetCompositeResolver() &&
- _customTypeInfoResolver != customResolver)
- {
- // If we already have a different custom resolver, we need to store multiple
- if (_customTypeInfoResolver != null)
- {
- _customTypeInfoResolvers ??= new List();
- if (!_customTypeInfoResolvers.Contains(customResolver))
- {
- _customTypeInfoResolvers.Add(customResolver);
- }
- }
- else
- {
- _customTypeInfoResolver = customResolver;
- }
- }
- }
- }
}
///
@@ -236,7 +209,7 @@ private void ApplyJsonOptions()
{
if (_jsonOptions != null)
{
- PowertoolsLoggingSerializer.ConfigureJsonOptions(_jsonOptions);
+ PowertoolsLoggingSerializer.BuildJsonSerializerOptions(_jsonOptions);
}
}
@@ -248,41 +221,7 @@ internal void ApplyOutputCase()
PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase);
}
- ///
- /// Clone this configuration
- ///
- internal PowertoolsLoggerConfiguration Clone()
- {
- var clone = new PowertoolsLoggerConfiguration
- {
- Service = Service,
- MinimumLogLevel = MinimumLogLevel,
- SamplingRate = SamplingRate,
- LoggerOutputCase = LoggerOutputCase,
- LoggerOutput = LoggerOutput,
- LogLevelKey = LogLevelKey,
- JsonOptions = JsonOptions
- };
-
-#if NET8_0_OR_GREATER
- clone._jsonContext = _jsonContext;
- foreach (var context in _additionalContexts)
- {
- clone._additionalContexts.Add(context);
- }
-
- clone._customTypeInfoResolver = _customTypeInfoResolver;
-
- if (_customTypeInfoResolvers != null)
- {
- clone._customTypeInfoResolvers = new List(_customTypeInfoResolvers);
- }
-#endif
-
- return clone;
- }
// IOptions implementation
PowertoolsLoggerConfiguration IOptions.Value => this;
- public string TimestampFormat { get; set; }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
index 7794b787..7a0793a9 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
@@ -1,5 +1,6 @@
using System;
using AWS.Lambda.Powertools.Logging.Internal;
+using AWS.Lambda.Powertools.Logging.Internal.Helpers;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging;
@@ -25,27 +26,24 @@ public static PowertoolsLoggerFactory Create(Action
{
builder.AddPowertoolsLogger(config =>
{
- // Copy basic properties
- config.Service = options.Service;
- config.MinimumLogLevel = options.MinimumLogLevel;
- config.LoggerOutputCase = options.LoggerOutputCase;
- config.SamplingRate = options.SamplingRate;
-
- // // Copy additional contexts using the public API
- // foreach (var ctx in options.GetAdditionalContexts())
- // {
- // config.AddJsonContext(ctx);
- // }
- //
+ config.CopyFrom(options);
});
});
Logger.Configure(factory);
- return new PowertoolsLoggerFactory(factory);
+ return factory;
}
// Add builder pattern support
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index cc207191..9407dcfa 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -188,32 +188,59 @@ internal static bool IsTypeSupportedByAnyResolver(Type type)
/// Builds and configures the JsonSerializerOptions.
///
/// A configured JsonSerializerOptions instance.
- private static void BuildJsonSerializerOptions()
+ internal static void BuildJsonSerializerOptions(JsonSerializerOptions options = null)
{
- // This should already be in a lock when called
- _jsonOptions = new JsonSerializerOptions();
-
- switch (_currentOutputCase)
+ lock (_lock)
{
- case LoggerOutputCase.CamelCase:
- _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
- _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
- break;
- case LoggerOutputCase.PascalCase:
- _jsonOptions.PropertyNamingPolicy = PascalCaseNamingPolicy.Instance;
- _jsonOptions.DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance;
- break;
- default: // Snake case
+ // This should already be in a lock when called
+ _jsonOptions = options ?? new JsonSerializerOptions();
+
+ switch (_currentOutputCase)
+ {
+ case LoggerOutputCase.CamelCase:
+ _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+ break;
+ case LoggerOutputCase.PascalCase:
+ _jsonOptions.PropertyNamingPolicy = PascalCaseNamingPolicy.Instance;
+ _jsonOptions.DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance;
+ break;
+ default: // Snake case
#if NET8_0_OR_GREATER
- _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
- _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
+ _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
+ _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
#else
_jsonOptions.PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance;
_jsonOptions.DictionaryKeyPolicy = SnakeCaseNamingPolicy.Instance;
#endif
- break;
+ break;
+ }
+
+ AddConverters();
+
+ _jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
+ _jsonOptions.PropertyNameCaseInsensitive = true;
+
+#if NET8_0_OR_GREATER
+
+ // Only add TypeInfoResolver if AOT mode
+ if (!RuntimeFeatureWrapper.IsDynamicCodeSupported)
+ {
+ // Always ensure our default context is in the chain first
+ _jsonOptions.TypeInfoResolverChain.Add(PowertoolsLoggingSerializationContext.Default);
+
+ // Add all registered contexts
+ foreach (var context in AdditionalContexts)
+ {
+ _jsonOptions.TypeInfoResolverChain.Add(context);
+ }
+ }
+#endif
}
+ }
+ private static void AddConverters()
+ {
_jsonOptions.Converters.Add(new ByteArrayConverter());
_jsonOptions.Converters.Add(new ExceptionConverter());
_jsonOptions.Converters.Add(new MemoryStreamConverter());
@@ -226,25 +253,6 @@ private static void BuildJsonSerializerOptions()
#elif NET6_0
_jsonOptions.Converters.Add(new LogLevelJsonConverter());
#endif
-
- _jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping;
- _jsonOptions.PropertyNameCaseInsensitive = true;
-
-#if NET8_0_OR_GREATER
-
- // Only add TypeInfoResolver if AOT mode
- if (!RuntimeFeatureWrapper.IsDynamicCodeSupported)
- {
- // Always ensure our default context is in the chain first
- _jsonOptions.TypeInfoResolverChain.Add(PowertoolsLoggingSerializationContext.Default);
-
- // Add all registered contexts
- foreach (var context in AdditionalContexts)
- {
- _jsonOptions.TypeInfoResolverChain.Add(context);
- }
- }
-#endif
}
#if NET8_0_OR_GREATER
@@ -266,54 +274,4 @@ internal static void ClearOptions()
{
_jsonOptions = null;
}
-
- ///
- /// Sets the default JSON context to use
- ///
- internal static void SetDefaultContext(JsonSerializerContext context)
- {
- lock (_lock)
- {
- // Reset options to ensure they're rebuilt with the new context
- _jsonOptions = null;
- }
- }
-
- ///
- /// Configure the serializer with specific JSON options
- ///
- internal static void ConfigureJsonOptions(JsonSerializerOptions options)
- {
- if (options == null) return;
-
- lock (_lock)
- {
- _jsonOptions = options;
-
- // Add required converters if they're not already present
- var converters = new[]
- {
- typeof(ByteArrayConverter),
- typeof(ExceptionConverter),
- typeof(MemoryStreamConverter),
- typeof(ConstantClassConverter),
- typeof(DateOnlyConverter),
- typeof(TimeOnlyConverter),
- typeof(LogLevelJsonConverter),
- };
-
- foreach (var converterType in converters)
- {
- if (!_jsonOptions.Converters.Any(c => c.GetType() == converterType))
- {
- // Add the converter through reflection to avoid direct instantiation
- var converter = Activator.CreateInstance(converterType) as JsonConverter;
- if (converter != null)
- {
- _jsonOptions.Converters.Add(converter);
- }
- }
- }
- }
- }
}
\ No newline at end of file
From c635752400ac7da4e2be979b7f5da59d620e0d00 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Wed, 19 Mar 2025 19:52:59 +0000
Subject: [PATCH 10/49] log formatter
---
.../Helpers/ConfigurationExtensions.cs | 1 +
.../Internal/Helpers/LoggerFactoryHelper.cs | 36 +++++++++++++++++++
.../PowertoolsLoggerBuilder.cs | 16 ++++-----
.../PowertoolsLoggerConfiguration.cs | 5 +++
.../PowertoolsLoggerFactory.cs | 11 +-----
5 files changed, 50 insertions(+), 19 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
index 4a8f6c8e..914c36cb 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
@@ -23,6 +23,7 @@ public static PowertoolsLoggerConfiguration CopyFrom(this PowertoolsLoggerConfig
destination.LoggerOutput = source.LoggerOutput;
destination.JsonOptions = source.JsonOptions;
destination.TimestampFormat = source.TimestampFormat;
+ destination.LogFormatter = source.LogFormatter;
return destination;
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
new file mode 100644
index 00000000..aaa4f684
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
@@ -0,0 +1,36 @@
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
+
+///
+/// Helper class for creating and configuring logger factories
+///
+internal static class LoggerFactoryHelper
+{
+ ///
+ /// Creates and configures a logger factory with the provided configuration
+ ///
+ /// The Powertools logger configuration to apply
+ /// The configured logger factory
+ public static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfiguration configuration)
+ {
+ var factory = LoggerFactory.Create(builder =>
+ {
+ builder.AddPowertoolsLogger(config =>
+ {
+ config.CopyFrom(configuration);
+ });
+ });
+
+ // Configure the static logger with the factory
+ Logger.Configure(factory);
+
+ // Apply formatter if one is specified
+ if (configuration.LogFormatter != null)
+ {
+ Logger.UseFormatter(configuration.LogFormatter);
+ }
+
+ return factory;
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
index b4411f74..32084e08 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
@@ -52,18 +52,16 @@ public PowertoolsLoggerBuilder WithOutput(ISystemWrapper output)
_configuration.LoggerOutput = output;
return this;
}
+
+ public PowertoolsLoggerBuilder WithFormatter(ILogFormatter formatter)
+ {
+ _configuration.LogFormatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
+ return this;
+ }
public ILogger Build()
{
- var factory = LoggerFactory.Create(builder =>
- {
- builder.AddPowertoolsLogger(config =>
- {
- config.CopyFrom(_configuration);
- });
- });
-
- Logger.Configure(factory); // Configure the static logger
+ var factory = LoggerFactoryHelper.CreateAndConfigureFactory(_configuration);
return factory.CreatePowertoolsLogger();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index 27e3ef2a..5bf25db4 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -74,6 +74,11 @@ public class PowertoolsLoggerConfiguration : IOptions
public ISystemWrapper? LoggerOutput { get; set; }
+ ///
+ /// Custom log formatter to use for formatting log entries
+ ///
+ public ILogFormatter? LogFormatter { get; set; }
+
///
/// JSON serializer options to use for log serialization
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
index 7a0793a9..18640b7b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
@@ -34,16 +34,7 @@ public static PowertoolsLoggerFactory Create(Action
- {
- builder.AddPowertoolsLogger(config =>
- {
- config.CopyFrom(options);
- });
- });
-
- Logger.Configure(factory);
- return factory;
+ return LoggerFactoryHelper.CreateAndConfigureFactory(options);
}
// Add builder pattern support
From 490a4495cb785fcaef463e3ca461713560a20469 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Thu, 20 Mar 2025 12:39:49 +0000
Subject: [PATCH 11/49] update to PowertoolsLoggerProvider
---
.../src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs | 4 ++--
.../{LoggerProvider.cs => PowertoolsLoggerProvider.cs} | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
rename libraries/src/AWS.Lambda.Powertools.Logging/Internal/{LoggerProvider.cs => PowertoolsLoggerProvider.cs} (96%)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 33143f3c..16a873ae 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -84,9 +84,9 @@ private static void RegisterServices(ILoggingBuilder builder)
// Register the provider
builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
+ ServiceDescriptor.Singleton());
LoggerProviderOptions.RegisterProviderOptions
- (builder.Services);
+ (builder.Services);
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
similarity index 96%
rename from libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index 852d9962..440b8985 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -28,7 +28,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
///
///
[ProviderAlias("PowertoolsLogger")]
-internal sealed class LoggerProvider : ILoggerProvider
+internal sealed class PowertoolsLoggerProvider : ILoggerProvider
{
///
/// The powertools configurations
@@ -49,12 +49,12 @@ internal sealed class LoggerProvider : ILoggerProvider
private PowertoolsLoggerConfiguration _currentConfig;
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The configuration.
///
///
- public LoggerProvider(IOptionsMonitor config,
+ public PowertoolsLoggerProvider(IOptionsMonitor config,
IPowertoolsConfigurations powertoolsConfigurations,
ISystemWrapper? systemWrapper = null)
{
From 498f3e1d83933fa4d0061714d4e5de1e81202fc4 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Thu, 20 Mar 2025 18:49:46 +0000
Subject: [PATCH 12/49] log buffering 1
---
.../BuilderExtensions.cs | 96 +++++++----
.../Internal/BufferingLoggerProvider.cs | 87 ++++++++++
.../Internal/LogBuffer.cs | 101 ++++++++++++
.../Internal/LogBufferManager.cs | 65 ++++++++
.../Internal/LoggingAspect.cs | 1 +
.../Internal/PowertoolsBufferingLogger.cs | 155 ++++++++++++++++++
.../Internal/PowertoolsLogger.cs | 23 ++-
.../LogBufferingOptions.cs | 44 +++++
.../Logger.Buffer.cs | 40 +++++
.../LoggerExtensions.cs | 20 +++
.../PowertoolsLoggerBuilder.cs | 18 ++
.../PowertoolsLoggerConfiguration.cs | 5 +
12 files changed, 614 insertions(+), 41 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 16a873ae..36cabbdf 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
using AWS.Lambda.Powertools.Logging.Serializers;
@@ -23,54 +24,50 @@ public static ILoggingBuilder AddPowertoolsLogger(
// Add configuration
builder.AddConfiguration();
- // Apply configuration if provided
- if (configure != null)
+ // If no configuration was provided, register services with defaults
+ if (configure == null)
{
- // Create initial configuration
- var options = new PowertoolsLoggerConfiguration();
- configure(options);
+ RegisterServices(builder);
+ return builder;
+ }
+
+ // Create initial configuration
+ var options = new PowertoolsLoggerConfiguration();
+ configure(options);
- // IMPORTANT: Set the minimum level directly on the builder
- if (options.MinimumLogLevel != LogLevel.None)
- {
- builder.SetMinimumLevel(options.MinimumLogLevel);
- }
-
- // Add filters here
+ // IMPORTANT: Set the minimum level directly on the builder
+ if (options.MinimumLogLevel != LogLevel.None)
+ {
+ builder.SetMinimumLevel(options.MinimumLogLevel);
+ }
- // Configure options for DI
- builder.Services.Configure(configure);
+ // Configure options for DI
+ builder.Services.Configure(configure);
- // Register services
- RegisterServices(builder);
+ // Register services with the options
+ RegisterServices(builder, options);
- // Apply the output case configuration
- PowertoolsLoggingSerializer.ConfigureNamingPolicy(options.LoggerOutputCase);
+ // Apply the output case configuration
+ PowertoolsLoggingSerializer.ConfigureNamingPolicy(options.LoggerOutputCase);
- // Configure static Logger (if not already in a configuration cycle)
- if (!_configuring)
+ // Configure static Logger (if not already in a configuration cycle)
+ if (!_configuring)
+ {
+ try
{
- try
- {
- _configuring = true;
- Logger.Configure(options);
- }
- finally
- {
- _configuring = false;
- }
+ _configuring = true;
+ Logger.Configure(options);
+ }
+ finally
+ {
+ _configuring = false;
}
- }
- else
- {
- // Register services even if no configuration was provided
- RegisterServices(builder);
}
return builder;
}
- private static void RegisterServices(ILoggingBuilder builder)
+ private static void RegisterServices(ILoggingBuilder builder, PowertoolsLoggerConfiguration options = null)
{
// Register ISystemWrapper if not already registered
builder.Services.TryAddSingleton();
@@ -82,7 +79,34 @@ private static void RegisterServices(ILoggingBuilder builder)
builder.Services.TryAddSingleton(sp =>
new PowertoolsConfigurations(sp.GetRequiredService()));
- // Register the provider
+ // If buffering is enabled, register it before the standard provider
+ if (options?.LogBufferingOptions?.Enabled == true)
+ {
+ // Add a filter for the buffer provider to capture logs at the buffer threshold
+ builder.AddFilter(
+ null,
+ options.LogBufferingOptions.BufferAtLogLevel);
+
+ // Register the buffering provider
+ builder.Services.AddSingleton(sp =>
+ {
+ var optionsMonitor = sp.GetRequiredService>();
+ var powertoolsConfigs = sp.GetService() ??
+ new PowertoolsConfigurations(sp.GetService() ??
+ new PowertoolsEnvironment());
+ var output = sp.GetService() ?? new SystemWrapper();
+
+ // Create a dedicated provider for buffering
+ var powerToolsProvider = new PowertoolsLoggerProvider(optionsMonitor, powertoolsConfigs, output);
+
+ // Return the buffering provider
+ return new BufferingLoggerProvider(
+ powerToolsProvider,
+ optionsMonitor);
+ });
+ }
+
+ // Register the regular provider
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton());
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs
new file mode 100644
index 00000000..3f0f6c23
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs
@@ -0,0 +1,87 @@
+/*
+ * 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;
+using System.Collections.Concurrent;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+/// Logger provider that supports buffering logs
+///
+[ProviderAlias("PowertoolsBuffering")]
+internal partial class BufferingLoggerProvider : ILoggerProvider
+{
+ private readonly ILoggerProvider _innerProvider;
+ private readonly ConcurrentDictionary _loggers = new();
+ private readonly IOptionsMonitor _options;
+
+ public BufferingLoggerProvider(
+ ILoggerProvider innerProvider,
+ IOptionsMonitor options)
+ {
+ _innerProvider = innerProvider ?? throw new ArgumentNullException(nameof(innerProvider));
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+
+ // Register with the buffer manager
+ LogBufferManager.RegisterProvider(this);
+ }
+
+ public ILogger CreateLogger(string categoryName)
+ {
+ return _loggers.GetOrAdd(
+ categoryName,
+ name => new BufferingLogger(
+ _innerProvider.CreateLogger(name),
+ _options,
+ name));
+ }
+
+ public void Dispose()
+ {
+ // Flush all buffers before disposing
+ foreach (var logger in _loggers.Values)
+ {
+ logger.FlushBuffer();
+ }
+
+ _innerProvider.Dispose();
+ _loggers.Clear();
+ }
+
+ ///
+ /// Flush all buffered logs
+ ///
+ public void FlushBuffers()
+ {
+ foreach (var logger in _loggers.Values)
+ {
+ logger.FlushBuffer();
+ }
+ }
+
+ ///
+ /// Clear all buffered logs
+ ///
+ public void ClearBuffers()
+ {
+ foreach (var logger in _loggers.Values)
+ {
+ logger.ClearBuffer();
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs
new file mode 100644
index 00000000..435d7133
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs
@@ -0,0 +1,101 @@
+/*
+ * 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;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+/// A simplified buffer for storing log entries
+///
+internal class LogBuffer
+{
+ // Simple storage for buffered messages
+ private readonly ConcurrentQueue _buffer = new();
+
+ // Keep track of approximate buffer size
+ private int _currentSize = 0;
+
+ ///
+ /// Add a log entry to the buffer
+ ///
+ public void Add(string logEntry, int maxBytes)
+ {
+ // Estimate size (very roughly)
+ var size = 100 + (logEntry?.Length ?? 0) * 2;
+
+ // Check if buffer is full - drop oldest entries until we have space
+ if (_currentSize + size > maxBytes)
+ {
+ while (_buffer.TryDequeue(out _) && _currentSize + size > maxBytes)
+ {
+ _currentSize -= 100; // Rough size per entry
+ }
+
+ // Safety check - don't allow negative sizes
+ if (_currentSize < 0) _currentSize = 0;
+ }
+
+ // Add to buffer
+ _buffer.Enqueue(logEntry);
+ _currentSize += size;
+ }
+
+ ///
+ /// Get all entries and clear the buffer
+ ///
+ public IReadOnlyCollection GetAndClear()
+ {
+ var entries = new List();
+
+ try
+ {
+ while (_buffer.TryDequeue(out var entry))
+ {
+ entries.Add(entry);
+ }
+ }
+ catch (Exception)
+ {
+ // If dequeuing fails, just clear and return what we have
+ Clear();
+ }
+
+ _currentSize = 0;
+ return entries;
+ }
+
+ ///
+ /// Clear the buffer without returning entries
+ ///
+ public void Clear()
+ {
+ while (_buffer.TryDequeue(out _)) { }
+ _currentSize = 0;
+ }
+
+ ///
+ /// Check if the buffer has any entries
+ ///
+ public bool HasEntries => !_buffer.IsEmpty;
+
+ ///
+ /// Get the current estimated size of the buffer
+ ///
+ public int CurrentSize => _currentSize;
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs
new file mode 100644
index 00000000..48e2ad7e
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs
@@ -0,0 +1,65 @@
+/*
+ * 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;
+using System.Collections.Concurrent;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+/// Singleton manager for log buffer operations
+///
+internal static class LogBufferManager
+{
+ private static BufferingLoggerProvider _provider;
+
+ ///
+ /// Register a buffering provider with the manager
+ ///
+ internal static void RegisterProvider(BufferingLoggerProvider provider)
+ {
+ _provider = provider;
+ }
+
+ ///
+ /// Flush all buffered logs
+ ///
+ internal static void FlushAllBuffers()
+ {
+ try
+ {
+ _provider?.FlushBuffers();
+ }
+ catch (Exception)
+ {
+ // Suppress errors
+ }
+ }
+
+ ///
+ /// Clear all buffered logs
+ ///
+ internal static void ClearAllBuffers()
+ {
+ try
+ {
+ _provider?.ClearBuffers();
+ }
+ catch (Exception)
+ {
+ // Suppress errors
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index 4e41fc82..4089a0ad 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -169,6 +169,7 @@ public void OnEntry(
}
catch (Exception exception)
{
+ _logger.FlushBuffer();
// 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();
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs
new file mode 100644
index 00000000..90bc2688
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs
@@ -0,0 +1,155 @@
+using System;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+ /// Logger implementation that supports buffering
+ ///
+ internal class BufferingLogger : ILogger
+ {
+ private readonly ILogger _innerLogger;
+ private readonly IOptionsMonitor _options;
+ private readonly string _categoryName;
+ private readonly LogBuffer _buffer = new();
+
+ public BufferingLogger(
+ ILogger innerLogger,
+ IOptionsMonitor options,
+ string categoryName)
+ {
+ _innerLogger = innerLogger;
+ _options = options;
+ _categoryName = categoryName;
+ }
+
+ public IDisposable BeginScope(TState state)
+ {
+ return _innerLogger.BeginScope(state);
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ var options = _options.CurrentValue;
+
+ // If buffering is disabled, defer to inner logger
+ if (!options.LogBufferingOptions.Enabled)
+ {
+ return _innerLogger.IsEnabled(logLevel);
+ }
+
+ // If the log level is at or above the configured minimum log level,
+ // let the inner logger decide
+ if (logLevel >= options.MinimumLogLevel)
+ {
+ return _innerLogger.IsEnabled(logLevel);
+ }
+
+ // For logs below minimum level but at or above buffer threshold,
+ // we should handle them (buffer them)
+ if (logLevel >= options.LogBufferingOptions.BufferAtLogLevel)
+ {
+ return true;
+ }
+
+ // Otherwise, the log level is below our buffer threshold
+ return false;
+ }
+
+ public void Log(
+ LogLevel logLevel,
+ EventId eventId,
+ TState state,
+ Exception exception,
+ Func formatter)
+ {
+ // Skip if logger is not enabled for this level
+ if (!IsEnabled(logLevel))
+ return;
+
+ var options = _options.CurrentValue;
+ var bufferOptions = options.LogBufferingOptions;
+
+ // Check if this log should be buffered
+ bool shouldBuffer = bufferOptions.Enabled &&
+ logLevel >= bufferOptions.BufferAtLogLevel &&
+ logLevel < options.MinimumLogLevel;
+
+ if (shouldBuffer)
+ {
+ // Add to buffer instead of logging
+ try
+ {
+ if (_innerLogger is PowertoolsLogger powertoolsLogger)
+ {
+ var logEntry = powertoolsLogger.LogEntryString(logLevel, state, exception, formatter);
+ _buffer.Add(logEntry, bufferOptions.MaxBytes);
+ }
+ }
+ catch (Exception ex)
+ {
+ // If buffering fails, try to log an error about it
+ try
+ {
+ _innerLogger.LogError(ex, "Failed to buffer log entry");
+ }
+ catch
+ {
+ // Last resort: if even that fails, just suppress the error
+ }
+ }
+ }
+ else
+ {
+ // If this is an error and we should flush on error
+ if (bufferOptions.Enabled &&
+ bufferOptions.FlushOnErrorLog &&
+ logLevel >= LogLevel.Error)
+ {
+ FlushBuffer();
+ }
+ }
+ }
+
+ ///
+ /// Flush buffered logs to the inner logger
+ ///
+ public void FlushBuffer()
+ {
+ try
+ {
+ // Get all buffered entries
+ var entries = _buffer.GetAndClear();
+
+ if (_innerLogger is PowertoolsLogger powertoolsLogger)
+ {
+ // Log each entry directly
+ foreach (var entry in entries)
+ {
+ powertoolsLogger.LogLine(entry);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // If the entire flush operation fails, try to log an error
+ try
+ {
+ _innerLogger.LogError(ex, "Failed to flush log buffer");
+ }
+ catch
+ {
+ // If even that fails, just suppress the error
+ }
+ }
+ }
+
+ ///
+ /// Clear the buffer without logging
+ ///
+ public void ClearBuffer()
+ {
+ _buffer.Clear();
+ }
+ }
\ 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 902b40af..4aac91b5 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -46,7 +46,6 @@ internal sealed class PowertoolsLogger : ILogger
///
private readonly ISystemWrapper _systemWrapper;
-
///
/// The current scope
///
@@ -127,12 +126,27 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
{
return;
}
+
+ _systemWrapper.LogLine(LogEntryString(logLevel, state, exception, formatter));
+ }
+
+ internal void LogLine(string message)
+ {
+ _systemWrapper.LogLine(message);
+ }
+
+ internal string LogEntryString(LogLevel logLevel, TState state, Exception exception, Func formatter)
+ {
+ var logEntry = LogEntry(logLevel, state, exception, formatter);
+ return PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object));
+ }
+ internal object LogEntry(LogLevel logLevel, TState state, Exception exception, Func formatter)
+ {
+ var timestamp = DateTime.UtcNow;
if (formatter is null)
throw new ArgumentNullException(nameof(formatter));
- var timestamp = DateTime.UtcNow;
-
// Extract structured parameters for template-style logging
var structuredParameters = ExtractStructuredParameters(state, out string messageTemplate);
@@ -146,8 +160,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
var logEntry = logFormatter is null
? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters)
: GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters);
-
- _systemWrapper.LogLine(PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object)));
+ return logEntry;
}
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs
new file mode 100644
index 00000000..93852420
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs
@@ -0,0 +1,44 @@
+/*
+ * 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 Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+///
+/// Configuration options for log buffering
+///
+public class LogBufferingOptions
+{
+ ///
+ /// Gets or sets whether buffering is enabled
+ ///
+ public bool Enabled { get; set; } = false;
+
+ ///
+ /// Gets or sets the maximum size of the buffer in bytes
+ ///
+ public int MaxBytes { get; set; } = 20480;
+
+ ///
+ /// Gets or sets the minimum log level to buffer
+ ///
+ public LogLevel BufferAtLogLevel { get; set; } = LogLevel.Debug;
+
+ ///
+ /// Gets or sets whether to flush the buffer when logging an error
+ ///
+ public bool FlushOnErrorLog { get; set; } = true;
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs
new file mode 100644
index 00000000..52a5c94a
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs
@@ -0,0 +1,40 @@
+/* * 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 * .cs
+/*
+ * 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.Logging.Internal;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+public static partial class Logger
+{
+ ///
+ /// Flush any buffered logs
+ ///
+ public static void FlushBuffer()
+ {
+ // Use the buffer manager directly
+ LogBufferManager.FlushAllBuffers();
+ }
+
+ ///
+ /// Clear any buffered logs without writing them
+ ///
+ internal static void ClearBuffer()
+ {
+ // Use the buffer manager directly
+ LogBufferManager.ClearAllBuffers();
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
index 0c1c6de8..23302cc4 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
@@ -722,4 +722,24 @@ public static void RemoveKey(this ILogger logger, string key)
{
Logger.RemoveKey(key);
}
+
+ // Replace the buffer methods with direct calls to the manager
+
+ ///
+ /// Flush any buffered logs
+ ///
+ public static void FlushBuffer(this ILogger logger)
+ {
+ // Direct call to the buffer manager to avoid any recursion
+ LogBufferManager.FlushAllBuffers();
+ }
+
+ ///
+ /// Clear any buffered logs without writing them
+ ///
+ internal static void ClearBuffer(this ILogger logger)
+ {
+ // Direct call to the buffer manager to avoid any recursion
+ LogBufferManager.ClearAllBuffers();
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
index 32084e08..c583e42a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
@@ -58,6 +58,24 @@ public PowertoolsLoggerBuilder WithFormatter(ILogFormatter formatter)
_configuration.LogFormatter = formatter ?? throw new ArgumentNullException(nameof(formatter));
return this;
}
+
+ ///
+ /// Enable log buffering with default options
+ ///
+ public PowertoolsLoggerBuilder WithLogBuffering(bool enabled = true)
+ {
+ _configuration.LogBufferingOptions.Enabled = enabled;
+ return this;
+ }
+
+ ///
+ /// Configure log buffering options
+ ///
+ public PowertoolsLoggerBuilder WithLogBuffering(Action configure)
+ {
+ configure?.Invoke(_configuration.LogBufferingOptions);
+ return this;
+ }
public ILogger Build()
{
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index 5bf25db4..b4d226ed 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -99,6 +99,11 @@ public JsonSerializerOptions? JsonOptions
}
}
+ ///
+ /// Options for log buffering
+ ///
+ public LogBufferingOptions LogBufferingOptions { get; set; } = new LogBufferingOptions();
+
#if NET8_0_OR_GREATER
///
/// Default JSON serializer context
From 7a28645006947f2e9afef0966e3c6de967568ba3 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Fri, 21 Mar 2025 11:31:26 +0000
Subject: [PATCH 13/49] buffer 2
---
.../BuilderExtensions.cs | 35 ++--
.../{ => Buffer}/BufferingLoggerProvider.cs | 15 +-
.../Internal/Buffer/LogBuffer.cs | 150 ++++++++++++++++++
.../Internal/{ => Buffer}/LogBufferManager.cs | 21 ++-
.../{ => Internal/Buffer}/Logger.Buffer.cs | 6 +-
.../{ => Buffer}/PowertoolsBufferingLogger.cs | 12 +-
.../Helpers/ConfigurationExtensions.cs | 2 +
.../Internal/LogBuffer.cs | 101 ------------
.../Internal/LoggingAspect.cs | 31 +++-
.../LoggerExtensions.cs | 6 +-
.../LoggingAttribute.cs | 6 +
11 files changed, 240 insertions(+), 145 deletions(-)
rename libraries/src/AWS.Lambda.Powertools.Logging/Internal/{ => Buffer}/BufferingLoggerProvider.cs (85%)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs
rename libraries/src/AWS.Lambda.Powertools.Logging/Internal/{ => Buffer}/LogBufferManager.cs (69%)
rename libraries/src/AWS.Lambda.Powertools.Logging/{ => Internal/Buffer}/Logger.Buffer.cs (90%)
rename libraries/src/AWS.Lambda.Powertools.Logging/Internal/{ => Buffer}/PowertoolsBufferingLogger.cs (94%)
delete mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 36cabbdf..5efd2103 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -79,31 +79,26 @@ private static void RegisterServices(ILoggingBuilder builder, PowertoolsLoggerCo
builder.Services.TryAddSingleton(sp =>
new PowertoolsConfigurations(sp.GetRequiredService()));
- // If buffering is enabled, register it before the standard provider
+ // If buffering is enabled, register buffer providers
if (options?.LogBufferingOptions?.Enabled == true)
{
- // Add a filter for the buffer provider to capture logs at the buffer threshold
+ // Add a filter for the buffer provider
builder.AddFilter(
null,
options.LogBufferingOptions.BufferAtLogLevel);
-
- // Register the buffering provider
- builder.Services.AddSingleton(sp =>
- {
- var optionsMonitor = sp.GetRequiredService>();
- var powertoolsConfigs = sp.GetService() ??
- new PowertoolsConfigurations(sp.GetService() ??
- new PowertoolsEnvironment());
- var output = sp.GetService() ?? new SystemWrapper();
-
- // Create a dedicated provider for buffering
- var powerToolsProvider = new PowertoolsLoggerProvider(optionsMonitor, powertoolsConfigs, output);
-
- // Return the buffering provider
- return new BufferingLoggerProvider(
- powerToolsProvider,
- optionsMonitor);
- });
+
+ // Register the inner provider factory
+ builder.Services.TryAddSingleton(sp =>
+ new BufferingLoggerProvider(
+ // Create a new PowertoolsLoggerProvider specifically for buffering
+ new PowertoolsLoggerProvider(
+ sp.GetRequiredService>(),
+ sp.GetRequiredService(),
+ sp.GetRequiredService()
+ ),
+ sp.GetRequiredService>()
+ )
+ );
}
// Register the regular provider
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
similarity index 85%
rename from libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
index 3f0f6c23..3802f042 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/BufferingLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
@@ -27,7 +27,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
internal partial class BufferingLoggerProvider : ILoggerProvider
{
private readonly ILoggerProvider _innerProvider;
- private readonly ConcurrentDictionary _loggers = new();
+ private readonly ConcurrentDictionary _loggers = new();
private readonly IOptionsMonitor _options;
public BufferingLoggerProvider(
@@ -45,7 +45,7 @@ public ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(
categoryName,
- name => new BufferingLogger(
+ name => new PowertoolsBufferingLogger(
_innerProvider.CreateLogger(name),
_options,
name));
@@ -84,4 +84,15 @@ public void ClearBuffers()
logger.ClearBuffer();
}
}
+
+ ///
+ /// Clear buffered logs for the current invocation only
+ ///
+ public void ClearCurrentBuffer()
+ {
+ foreach (var logger in _loggers.Values)
+ {
+ logger.ClearCurrentInvocation();
+ }
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs
new file mode 100644
index 00000000..fd233c70
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs
@@ -0,0 +1,150 @@
+/*
+ * 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;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Threading;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+/// A buffer for storing log entries, with isolation per Lambda invocation
+///
+internal class LogBuffer
+{
+ // Use AsyncLocal for automatic context flow across async calls
+ private static readonly AsyncLocal _currentInvocationId = new AsyncLocal();
+
+ // Dictionary of buffers by invocation ID
+ private readonly ConcurrentDictionary _buffersByInvocation = new();
+
+ // Get the current invocation ID or create a fallback
+ private string CurrentInvocationId => _currentInvocationId.Value;
+
+ ///
+ /// Set the current invocation ID (call this at the start of a Lambda invocation)
+ ///
+ public static void SetCurrentInvocationId(string invocationId)
+ {
+ _currentInvocationId.Value = invocationId;
+ }
+
+ ///
+ /// Add a log entry to the buffer for the current invocation
+ ///
+ public void Add(string logEntry, int maxBytes)
+ {
+ var invocationId = CurrentInvocationId;
+ var buffer = _buffersByInvocation.GetOrAdd(invocationId, _ => new InvocationBuffer());
+ buffer.Add(logEntry, maxBytes);
+ }
+
+ ///
+ /// Get all entries for the current invocation and clear that buffer
+ ///
+ public IReadOnlyCollection GetAndClear()
+ {
+ var invocationId = CurrentInvocationId;
+
+ // Try to get and remove the buffer for this invocation
+ if (_buffersByInvocation.TryRemove(invocationId, out var buffer))
+ {
+ return buffer.GetAndClear();
+ }
+
+ return Array.Empty();
+ }
+
+ ///
+ /// Clear all buffers
+ ///
+ public void Clear()
+ {
+ _buffersByInvocation.Clear();
+ }
+
+ ///
+ /// Clear buffer for the current invocation
+ ///
+ public void ClearCurrentInvocation()
+ {
+ var invocationId = CurrentInvocationId;
+ if (_buffersByInvocation.TryRemove(invocationId, out _)) {}
+ }
+
+ ///
+ /// Check if the current invocation has any buffered entries
+ ///
+ public bool HasEntries
+ {
+ get
+ {
+ var invocationId = CurrentInvocationId;
+ return _buffersByInvocation.TryGetValue(invocationId, out var buffer) && buffer.HasEntries;
+ }
+ }
+
+ ///
+ /// Buffer for a specific invocation
+ ///
+ private class InvocationBuffer
+ {
+ private readonly ConcurrentQueue _buffer = new();
+ private int _currentSize = 0;
+
+ public void Add(string logEntry, int maxBytes)
+ {
+ // Same implementation as before
+ var size = 100 + (logEntry?.Length ?? 0) * 2;
+
+ if (_currentSize + size > maxBytes)
+ {
+ while (_buffer.TryDequeue(out _) && _currentSize + size > maxBytes)
+ {
+ _currentSize -= 100;
+ }
+
+ if (_currentSize < 0) _currentSize = 0;
+ }
+
+ _buffer.Enqueue(logEntry);
+ _currentSize += size;
+ }
+
+ public IReadOnlyCollection GetAndClear()
+ {
+ var entries = new List();
+
+ try
+ {
+ while (_buffer.TryDequeue(out var entry))
+ {
+ entries.Add(entry);
+ }
+ }
+ catch (Exception)
+ {
+ _buffer.Clear();
+ }
+
+ _currentSize = 0;
+ return entries;
+ }
+
+ public bool HasEntries => !_buffer.IsEmpty;
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs
similarity index 69%
rename from libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs
index 48e2ad7e..5eea9ec5 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBufferManager.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs
@@ -14,12 +14,11 @@
*/
using System;
-using System.Collections.Concurrent;
namespace AWS.Lambda.Powertools.Logging.Internal;
///
-/// Singleton manager for log buffer operations
+/// Singleton manager for log buffer operations with invocation context awareness
///
internal static class LogBufferManager
{
@@ -34,9 +33,17 @@ internal static void RegisterProvider(BufferingLoggerProvider provider)
}
///
- /// Flush all buffered logs
+ /// Set the current invocation ID to isolate logs between Lambda invocations
///
- internal static void FlushAllBuffers()
+ public static void SetInvocationId(string invocationId)
+ {
+ LogBuffer.SetCurrentInvocationId(invocationId);
+ }
+
+ ///
+ /// Flush buffered logs for the current invocation
+ ///
+ internal static void FlushCurrentBuffer()
{
try
{
@@ -49,13 +56,13 @@ internal static void FlushAllBuffers()
}
///
- /// Clear all buffered logs
+ /// Clear buffered logs for the current invocation
///
- internal static void ClearAllBuffers()
+ internal static void ClearCurrentBuffer()
{
try
{
- _provider?.ClearBuffers();
+ _provider?.ClearCurrentBuffer();
}
catch (Exception)
{
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs
similarity index 90%
rename from libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs
index 52a5c94a..9e715c55 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Buffer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs
@@ -26,15 +26,15 @@ public static partial class Logger
public static void FlushBuffer()
{
// Use the buffer manager directly
- LogBufferManager.FlushAllBuffers();
+ LogBufferManager.FlushCurrentBuffer();
}
///
/// Clear any buffered logs without writing them
///
- internal static void ClearBuffer()
+ public static void ClearBuffer()
{
// Use the buffer manager directly
- LogBufferManager.ClearAllBuffers();
+ LogBufferManager.ClearCurrentBuffer();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
similarity index 94%
rename from libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
index 90bc2688..b2c9da5a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsBufferingLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
@@ -7,14 +7,14 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
///
/// Logger implementation that supports buffering
///
- internal class BufferingLogger : ILogger
+ internal class PowertoolsBufferingLogger : ILogger
{
private readonly ILogger _innerLogger;
private readonly IOptionsMonitor _options;
private readonly string _categoryName;
private readonly LogBuffer _buffer = new();
- public BufferingLogger(
+ public PowertoolsBufferingLogger(
ILogger innerLogger,
IOptionsMonitor options,
string categoryName)
@@ -152,4 +152,12 @@ public void ClearBuffer()
{
_buffer.Clear();
}
+
+ ///
+ /// Clear buffered logs only for the current invocation
+ ///
+ public void ClearCurrentInvocation()
+ {
+ _buffer.ClearCurrentInvocation();
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
index 914c36cb..c8206123 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
@@ -24,6 +24,8 @@ public static PowertoolsLoggerConfiguration CopyFrom(this PowertoolsLoggerConfig
destination.JsonOptions = source.JsonOptions;
destination.TimestampFormat = source.TimestampFormat;
destination.LogFormatter = source.LogFormatter;
+ destination.LogLevelKey = source.LogLevelKey;
+ destination.LogBufferingOptions = source.LogBufferingOptions;
return destination;
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs
deleted file mode 100644
index 435d7133..00000000
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LogBuffer.cs
+++ /dev/null
@@ -1,101 +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;
-using System.Collections.Concurrent;
-using System.Collections.Generic;
-using Microsoft.Extensions.Logging;
-
-namespace AWS.Lambda.Powertools.Logging.Internal;
-
-///
-/// A simplified buffer for storing log entries
-///
-internal class LogBuffer
-{
- // Simple storage for buffered messages
- private readonly ConcurrentQueue _buffer = new();
-
- // Keep track of approximate buffer size
- private int _currentSize = 0;
-
- ///
- /// Add a log entry to the buffer
- ///
- public void Add(string logEntry, int maxBytes)
- {
- // Estimate size (very roughly)
- var size = 100 + (logEntry?.Length ?? 0) * 2;
-
- // Check if buffer is full - drop oldest entries until we have space
- if (_currentSize + size > maxBytes)
- {
- while (_buffer.TryDequeue(out _) && _currentSize + size > maxBytes)
- {
- _currentSize -= 100; // Rough size per entry
- }
-
- // Safety check - don't allow negative sizes
- if (_currentSize < 0) _currentSize = 0;
- }
-
- // Add to buffer
- _buffer.Enqueue(logEntry);
- _currentSize += size;
- }
-
- ///
- /// Get all entries and clear the buffer
- ///
- public IReadOnlyCollection GetAndClear()
- {
- var entries = new List();
-
- try
- {
- while (_buffer.TryDequeue(out var entry))
- {
- entries.Add(entry);
- }
- }
- catch (Exception)
- {
- // If dequeuing fails, just clear and return what we have
- Clear();
- }
-
- _currentSize = 0;
- return entries;
- }
-
- ///
- /// Clear the buffer without returning entries
- ///
- public void Clear()
- {
- while (_buffer.TryDequeue(out _)) { }
- _currentSize = 0;
- }
-
- ///
- /// Check if the buffer has any entries
- ///
- public bool HasEntries => !_buffer.IsEmpty;
-
- ///
- /// Get the current estimated size of the buffer
- ///
- public int CurrentSize => _currentSize;
-}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index 4089a0ad..d70f4a1c 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -14,8 +14,6 @@
*/
using System;
-using System.Collections.Concurrent;
-using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -62,9 +60,10 @@ public class LoggingAspect
private bool _clearLambdaContext;
private ILogger _logger;
- private readonly bool _LogEventEnv;
+ private readonly bool _logEventEnv;
private readonly string _xRayTraceId;
private bool _isDebug;
+ private bool _bufferingEnabled;
///
@@ -73,7 +72,7 @@ public class LoggingAspect
/// The Powertools configurations.
public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
{
- _LogEventEnv = powertoolsConfigurations.LoggerLogEvent;
+ _logEventEnv = powertoolsConfigurations.LoggerLogEvent;
_xRayTraceId = powertoolsConfigurations.XRayTraceId;
}
@@ -86,7 +85,7 @@ private void InitializeLogger(LoggingAttribute trigger)
trigger.SamplingRate > 0);
// Configure logger if not configured or we have explicit settings
- if (!Logger.IsConfigured || hasExplicitSettings)
+ if (!Logger.IsConfigured )
{
// Create configuration with default values when not explicitly specified
var config = new PowertoolsLoggerConfiguration
@@ -107,6 +106,7 @@ private void InitializeLogger(LoggingAttribute trigger)
// Set debug flag based on the minimum level from Logger
_isDebug = Logger.GetConfiguration().MinimumLogLevel <= LogLevel.Debug;
+ _bufferingEnabled = Logger.GetConfiguration().LogBufferingOptions.Enabled;
}
///
@@ -163,13 +163,23 @@ public void OnEntry(
var eventObject = eventArgs.Args.FirstOrDefault();
CaptureXrayTraceId();
CaptureLambdaContext(eventArgs);
+
+ if(_bufferingEnabled)
+ {
+ LogBufferManager.SetInvocationId(LoggingLambdaContext.Instance.AwsRequestId);
+ }
+
CaptureCorrelationId(eventObject, trigger.CorrelationIdPath);
- if (logEvent || _LogEventEnv)
+ if (logEvent || _logEventEnv)
LogEvent(eventObject);
}
catch (Exception exception)
{
- _logger.FlushBuffer();
+ if (_bufferingEnabled && trigger.FlushBufferOnUncaughtError)
+ {
+ _logger.FlushBuffer();
+ }
+
// 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();
@@ -189,6 +199,12 @@ public void OnExit()
if (_clearState)
_logger.RemoveAllKeys();
_initializeContext = true;
+
+ if (_bufferingEnabled)
+ {
+ // clear the buffer after the handler has finished
+ _logger.ClearBuffer();
+ }
}
///
@@ -220,6 +236,7 @@ private void CaptureLambdaContext(AspectEventArgs eventArgs)
/// Captures the correlation identifier.
///
/// The event argument.
+ ///
private void CaptureCorrelationId(object eventArg, string correlationIdPath)
{
if (string.IsNullOrWhiteSpace(correlationIdPath))
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
index 23302cc4..21e8dd6a 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
@@ -731,15 +731,15 @@ public static void RemoveKey(this ILogger logger, string key)
public static void FlushBuffer(this ILogger logger)
{
// Direct call to the buffer manager to avoid any recursion
- LogBufferManager.FlushAllBuffers();
+ LogBufferManager.FlushCurrentBuffer();
}
///
/// Clear any buffered logs without writing them
///
- internal static void ClearBuffer(this ILogger logger)
+ public static void ClearBuffer(this ILogger logger)
{
// Direct call to the buffer manager to avoid any recursion
- LogBufferManager.ClearAllBuffers();
+ LogBufferManager.ClearCurrentBuffer();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs
index 4a5da930..ba7402bd 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs
@@ -171,4 +171,10 @@ public class LoggingAttribute : Attribute
///
/// The log level.
public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default;
+
+ ///
+ /// Flush buffer on uncaught error
+ /// When buffering is enabled, this property will flush the buffer on uncaught exceptions
+ ///
+ public bool FlushBufferOnUncaughtError { get; set; }
}
\ No newline at end of file
From 9d48307dfedf05ecc5690a40e92b8186e09eb73f Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Fri, 21 Mar 2025 18:45:55 +0000
Subject: [PATCH 14/49] buffer 3
---
...owertoolsLoggerConfigurationExtensions.cs} | 2 +-
.../Internal/LoggingAspect.cs | 61 +++++++++++++------
.../Internal/PowertoolsLoggerProvider.cs | 2 +-
.../PowertoolsLoggingSerializer.cs | 56 ++++++++++-------
4 files changed, 77 insertions(+), 44 deletions(-)
rename libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/{ConfigurationExtensions.cs => PowertoolsLoggerConfigurationExtensions.cs} (95%)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs
similarity index 95%
rename from libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs
index c8206123..2b109f15 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/ConfigurationExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs
@@ -6,7 +6,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
///
/// Extension methods for handling configuration copying between PowertoolsLogger configurations
///
-internal static class ConfigurationExtensions
+internal static class PowertoolsLoggerConfigurationExtensions
{
///
/// Copies configuration values from source to destination configuration
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index d70f4a1c..b7067074 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -74,39 +74,60 @@ public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
{
_logEventEnv = powertoolsConfigurations.LoggerLogEvent;
_xRayTraceId = powertoolsConfigurations.XRayTraceId;
+ // Get Logger Instance
+ // This is a singleton, so we can reuse the same instance
+ _logger = Logger.GetPowertoolsLogger();
}
private void InitializeLogger(LoggingAttribute trigger)
- {
- // Always check for explicit settings
- bool hasExplicitSettings = (trigger.LogLevel != LogLevel.None ||
- !string.IsNullOrEmpty(trigger.Service) ||
- trigger.LoggerOutputCase != default ||
- trigger.SamplingRate > 0);
+ {
+ // Check which settings are explicitly provided in the attribute
+ var hasLogLevel = trigger.LogLevel != LogLevel.None;
+ var hasService = !string.IsNullOrEmpty(trigger.Service);
+ var hasOutputCase = trigger.LoggerOutputCase != default;
+ var hasSamplingRate = trigger.SamplingRate > 0;
+
+ var hasExplicitSettings = hasLogLevel || hasService || hasOutputCase || hasSamplingRate;
- // Configure logger if not configured or we have explicit settings
- if (!Logger.IsConfigured )
+ if (!Logger.IsConfigured)
{
- // Create configuration with default values when not explicitly specified
+ // First time initialization - create a new configuration with defaults for any unspecified values
var config = new PowertoolsLoggerConfiguration
{
- // Use sensible defaults if not specified in the attribute
- MinimumLogLevel = trigger.LogLevel != LogLevel.None ? trigger.LogLevel : LogLevel.Information,
- Service = !string.IsNullOrEmpty(trigger.Service) ? trigger.Service : "service_undefined",
- LoggerOutputCase = trigger.LoggerOutputCase != default ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
- SamplingRate = trigger.SamplingRate > 0 ? trigger.SamplingRate : 1.0
+ MinimumLogLevel = hasLogLevel ? trigger.LogLevel : LogLevel.Information,
+ Service = hasService ? trigger.Service : "service_undefined",
+ LoggerOutputCase = hasOutputCase ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
+ SamplingRate = hasSamplingRate ? trigger.SamplingRate : 1.0
};
- // Configure the logger with our configuration
Logger.Configure(config);
}
+ else if (hasExplicitSettings)
+ {
+ // Preserve existing configuration and only override what's explicitly specified
+ Logger.UpdateConfiguration(config => {
+ if (hasLogLevel)
+ config.MinimumLogLevel = trigger.LogLevel;
+
+ if (hasService)
+ config.Service = trigger.Service;
+
+ if (hasOutputCase)
+ config.LoggerOutputCase = trigger.LoggerOutputCase;
+
+ if (hasSamplingRate)
+ config.SamplingRate = trigger.SamplingRate;
+ });
+ }
- // Get logger after configuration
- _logger = Logger.GetPowertoolsLogger();
- // Set debug flag based on the minimum level from Logger
- _isDebug = Logger.GetConfiguration().MinimumLogLevel <= LogLevel.Debug;
- _bufferingEnabled = Logger.GetConfiguration().LogBufferingOptions.Enabled;
+
+ // Fetch the current configuration
+ var currentConfig = Logger.GetConfiguration();
+
+ // Set operational flags based on current configuration
+ _isDebug = currentConfig.MinimumLogLevel <= LogLevel.Debug;
+ _bufferingEnabled = currentConfig.LogBufferingOptions?.Enabled ?? false;
}
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index 440b8985..e4e2c4af 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -129,7 +129,7 @@ private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
}
// Always configure the serializer with the output case
- PowertoolsLoggingSerializer.ConfigureNamingPolicy(config.LoggerOutputCase);
+ // PowertoolsLoggingSerializer.ConfigureNamingPolicy(config.LoggerOutputCase);
// Configure the log level key based on output case
config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index 9407dcfa..40e7d793 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -76,7 +76,7 @@ internal static void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase)
// Only rebuild options if they already exist
if (_jsonOptions != null)
{
- BuildJsonSerializerOptions();
+ SetOutputCase();
}
}
}
@@ -194,27 +194,8 @@ internal static void BuildJsonSerializerOptions(JsonSerializerOptions options =
{
// This should already be in a lock when called
_jsonOptions = options ?? new JsonSerializerOptions();
-
- switch (_currentOutputCase)
- {
- case LoggerOutputCase.CamelCase:
- _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
- _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
- break;
- case LoggerOutputCase.PascalCase:
- _jsonOptions.PropertyNamingPolicy = PascalCaseNamingPolicy.Instance;
- _jsonOptions.DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance;
- break;
- default: // Snake case
-#if NET8_0_OR_GREATER
- _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
- _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
-#else
- _jsonOptions.PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance;
- _jsonOptions.DictionaryKeyPolicy = SnakeCaseNamingPolicy.Instance;
-#endif
- break;
- }
+
+ SetOutputCase();
AddConverters();
@@ -239,6 +220,37 @@ internal static void BuildJsonSerializerOptions(JsonSerializerOptions options =
}
}
+ private static void SetOutputCase()
+ {
+ switch (_currentOutputCase)
+ {
+ case LoggerOutputCase.CamelCase:
+ _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
+ _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
+ break;
+ case LoggerOutputCase.PascalCase:
+ _jsonOptions.PropertyNamingPolicy = PascalCaseNamingPolicy.Instance;
+ _jsonOptions.DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance;
+ break;
+ default: // Snake case
+#if NET8_0_OR_GREATER
+ // If is default (Not Set) and JsonOptions provided with DictionaryKeyPolicy or PropertyNamingPolicy, use it
+ if (_jsonOptions.DictionaryKeyPolicy != null || _jsonOptions.PropertyNamingPolicy != null)
+ {
+ _jsonOptions.DictionaryKeyPolicy = _jsonOptions.DictionaryKeyPolicy;
+ _jsonOptions.PropertyNamingPolicy = _jsonOptions.PropertyNamingPolicy;
+ }else{
+ _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
+ _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
+ }
+#else
+ _jsonOptions.PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance;
+ _jsonOptions.DictionaryKeyPolicy = SnakeCaseNamingPolicy.Instance;
+#endif
+ break;
+ }
+ }
+
private static void AddConverters()
{
_jsonOptions.Converters.Add(new ByteArrayConverter());
From 29690e2465196cbeab09246f2b9a9ac9f6ee793b Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Fri, 21 Mar 2025 20:03:19 +0000
Subject: [PATCH 15/49] buffer4
---
libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index bdcdbe72..9df769ea 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -105,6 +105,17 @@ internal static PowertoolsLoggerConfiguration GetConfiguration()
public static ILogger GetPowertoolsLogger() => Factory.CreatePowertoolsLogger();
+ // Update configuration settings
+ internal static void UpdateConfiguration(Action configureAction)
+ {
+ if (configureAction == null) return;
+
+ // Apply updates to current configuration
+ configureAction(_currentConfig);
+
+ // Apply any output case changes
+ _currentConfig.ApplyOutputCase();
+ }
// For testing purposes
// internal static void Reset()
// {
From fc7b26b32bcb2dbf266a763f55d1f4b504e23037 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Sat, 22 Mar 2025 20:33:52 +0000
Subject: [PATCH 16/49] refactor: clean up whitespace and improve logger
configuration handling
---
.../BuilderExtensions.cs | 11 +-
.../Internal/LoggingAspect.cs | 38 ++---
.../PowertoolsLoggerConfiguration.cs | 131 +-----------------
.../PowertoolsLoggingSerializer.cs | 70 ++++++++--
4 files changed, 87 insertions(+), 163 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index 5efd2103..d0c35c75 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -1,8 +1,6 @@
using System;
-using System.Linq;
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
-using AWS.Lambda.Powertools.Logging.Serializers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
@@ -11,12 +9,17 @@
namespace AWS.Lambda.Powertools.Logging;
+///
+/// Extension methods for configuring the Powertools logger
+///
public static class BuilderExtensions
{
// Track if we're in the middle of configuration to prevent recursion
private static bool _configuring = false;
- // Single base method that all other overloads call
+ ///
+ /// Adds the Powertools logger to the logging builder.
+ ///
public static ILoggingBuilder AddPowertoolsLogger(
this ILoggingBuilder builder,
Action? configure = null)
@@ -48,7 +51,7 @@ public static ILoggingBuilder AddPowertoolsLogger(
RegisterServices(builder, options);
// Apply the output case configuration
- PowertoolsLoggingSerializer.ConfigureNamingPolicy(options.LoggerOutputCase);
+ options.ApplyOutputCase();
// Configure static Logger (if not already in a configuration cycle)
if (!_configuring)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index b7067074..66c2482d 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -58,7 +58,7 @@ public class LoggingAspect
/// Specify to clear Lambda Context on exit
///
private bool _clearLambdaContext;
-
+
private ILogger _logger;
private readonly bool _logEventEnv;
private readonly string _xRayTraceId;
@@ -75,10 +75,9 @@ public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
_logEventEnv = powertoolsConfigurations.LoggerLogEvent;
_xRayTraceId = powertoolsConfigurations.XRayTraceId;
// Get Logger Instance
- // This is a singleton, so we can reuse the same instance
_logger = Logger.GetPowertoolsLogger();
}
-
+
private void InitializeLogger(LoggingAttribute trigger)
{
// Check which settings are explicitly provided in the attribute
@@ -86,9 +85,9 @@ private void InitializeLogger(LoggingAttribute trigger)
var hasService = !string.IsNullOrEmpty(trigger.Service);
var hasOutputCase = trigger.LoggerOutputCase != default;
var hasSamplingRate = trigger.SamplingRate > 0;
-
+
var hasExplicitSettings = hasLogLevel || hasService || hasOutputCase || hasSamplingRate;
-
+
if (!Logger.IsConfigured)
{
// First time initialization - create a new configuration with defaults for any unspecified values
@@ -99,32 +98,32 @@ private void InitializeLogger(LoggingAttribute trigger)
LoggerOutputCase = hasOutputCase ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
SamplingRate = hasSamplingRate ? trigger.SamplingRate : 1.0
};
-
+
Logger.Configure(config);
}
else if (hasExplicitSettings)
{
// Preserve existing configuration and only override what's explicitly specified
- Logger.UpdateConfiguration(config => {
+ Logger.UpdateConfiguration(config =>
+ {
if (hasLogLevel)
config.MinimumLogLevel = trigger.LogLevel;
-
+
if (hasService)
config.Service = trigger.Service;
-
+
if (hasOutputCase)
config.LoggerOutputCase = trigger.LoggerOutputCase;
-
+
if (hasSamplingRate)
config.SamplingRate = trigger.SamplingRate;
});
}
-
-
-
+
+
// Fetch the current configuration
var currentConfig = Logger.GetConfiguration();
-
+
// Set operational flags based on current configuration
_isDebug = currentConfig.MinimumLogLevel <= LogLevel.Debug;
_bufferingEnabled = currentConfig.LogBufferingOptions?.Enabled ?? false;
@@ -184,12 +183,12 @@ public void OnEntry(
var eventObject = eventArgs.Args.FirstOrDefault();
CaptureXrayTraceId();
CaptureLambdaContext(eventArgs);
-
- if(_bufferingEnabled)
+
+ if (_bufferingEnabled)
{
LogBufferManager.SetInvocationId(LoggingLambdaContext.Instance.AwsRequestId);
}
-
+
CaptureCorrelationId(eventObject, trigger.CorrelationIdPath);
if (logEvent || _logEventEnv)
LogEvent(eventObject);
@@ -235,7 +234,8 @@ private void CaptureXrayTraceId()
{
if (string.IsNullOrWhiteSpace(_xRayTraceId))
return;
- _logger.AppendKey(LoggingConstants.KeyXRayTraceId, _xRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""));
+ _logger.AppendKey(LoggingConstants.KeyXRayTraceId,
+ _xRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""));
}
///
@@ -290,7 +290,7 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
{
// For casing parsing to be removed from Logging v2 when we get rid of outputcase
// without this CorrelationIdPaths.ApiGatewayRest would not work
-
+
// TODO: fix this
// var pathWithOutputCase =
// _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase);
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index b4d226ed..ed4903db 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -13,16 +13,10 @@
* permissions and limitations under the License.
*/
-using System;
-using System.Collections.Generic;
-using System.Collections.Concurrent;
using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Text.Json.Serialization.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using AWS.Lambda.Powertools.Common;
-using AWS.Lambda.Powertools.Common.Utils;
using AWS.Lambda.Powertools.Logging.Serializers;
namespace AWS.Lambda.Powertools.Logging;
@@ -91,10 +85,7 @@ public JsonSerializerOptions? JsonOptions
_jsonOptions = value;
if (_jsonOptions != null)
{
-#if NET8_0_OR_GREATER
- HandleJsonOptionsTypeResolver(_jsonOptions);
-#endif
- ApplyJsonOptions();
+ PowertoolsLoggingSerializer.SetOptions(_jsonOptions);
}
}
}
@@ -104,125 +95,6 @@ public JsonSerializerOptions? JsonOptions
///
public LogBufferingOptions LogBufferingOptions { get; set; } = new LogBufferingOptions();
-#if NET8_0_OR_GREATER
- ///
- /// Default JSON serializer context
- ///
- private JsonSerializerContext? _jsonContext = PowertoolsLoggingSerializationContext.Default;
- private readonly List _additionalContexts = new();
-
- ///
- /// Add additional JsonSerializerContext for client types
- ///
- internal void AddJsonContext(JsonSerializerContext context)
- {
- if (context == null)
- return;
-
- // Don't add duplicates
- if (!_additionalContexts.Contains(context))
- {
- _additionalContexts.Add(context);
- ApplyAdditionalJsonContext(context);
-
- // If we have existing JSON options, update their type resolver
- if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported)
- {
- // Reset the type resolver chain to rebuild it
- _jsonOptions.TypeInfoResolver = GetCompositeResolver();
- }
- }
- }
-
- ///
- /// Get all additional contexts
- ///
- internal IReadOnlyList GetAdditionalContexts()
- {
- return _additionalContexts.AsReadOnly();
- }
-
- private IJsonTypeInfoResolver? _customTypeInfoResolver = null;
- private List? _customTypeInfoResolvers;
-
- ///
- /// Process JSON options type resolver information
- ///
- internal void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
- {
- if (options == null) return;
-
- // Check for TypeInfoResolver and ensure it's not lost
- if (options.TypeInfoResolver != null &&
- options.TypeInfoResolver != GetCompositeResolver())
- {
- _customTypeInfoResolver = options.TypeInfoResolver;
-
- // If it's a JsonSerializerContext, also add it to our contexts
- if (_customTypeInfoResolver is JsonSerializerContext jsonContext)
- {
- AddJsonContext(jsonContext);
- }
- }
- }
-
- ///
- /// Get a composite resolver that includes all configured resolvers
- ///
- internal IJsonTypeInfoResolver GetCompositeResolver()
- {
- var resolvers = new List();
-
- // Add custom resolver if provided
- if (_customTypeInfoResolver != null)
- {
- resolvers.Add(_customTypeInfoResolver);
- }
-
- // Add additional custom resolvers if any
- if (_customTypeInfoResolvers != null)
- {
- foreach (var resolver in _customTypeInfoResolvers)
- {
- resolvers.Add(resolver);
- }
- }
-
- // Add default context
- if (_jsonContext != null)
- {
- resolvers.Add(_jsonContext);
- }
-
- // Add additional contexts
- foreach (var context in _additionalContexts)
- {
- resolvers.Add(context);
- }
-
- return new CompositeJsonTypeInfoResolver(resolvers.ToArray());
- }
-
- ///
- /// Apply additional JSON context to serializer
- ///
- private void ApplyAdditionalJsonContext(JsonSerializerContext context)
- {
- PowertoolsLoggingSerializer.AddSerializerContext(context);
- }
-#endif
-
- ///
- /// Apply JSON options to the serializer
- ///
- private void ApplyJsonOptions()
- {
- if (_jsonOptions != null)
- {
- PowertoolsLoggingSerializer.BuildJsonSerializerOptions(_jsonOptions);
- }
- }
-
///
/// Apply output case configuration
///
@@ -231,7 +103,6 @@ internal void ApplyOutputCase()
PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase);
}
-
// IOptions implementation
PowertoolsLoggerConfiguration IOptions.Value => this;
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index 40e7d793..9a09180e 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -25,7 +25,6 @@
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Common.Utils;
using AWS.Lambda.Powertools.Logging.Internal.Converters;
-using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging.Serializers;
@@ -34,9 +33,11 @@ namespace AWS.Lambda.Powertools.Logging.Serializers;
///
internal static class PowertoolsLoggingSerializer
{
+ private static JsonSerializerOptions _currentOptions;
private static LoggerOutputCase _currentOutputCase;
private static JsonSerializerOptions _jsonOptions;
private static readonly object _lock = new object();
+ private static IJsonTypeInfoResolver? _customTypeInfoResolver = null;
private static readonly ConcurrentBag AdditionalContexts =
new ConcurrentBag();
@@ -53,7 +54,7 @@ internal static JsonSerializerOptions GetSerializerOptions()
{
if (_jsonOptions == null)
{
- BuildJsonSerializerOptions();
+ BuildJsonSerializerOptions(_currentOptions);
}
}
}
@@ -146,9 +147,61 @@ internal static void AddSerializerContext(JsonSerializerContext context)
{
ArgumentNullException.ThrowIfNull(context);
+ // Don't add duplicates
if (!AdditionalContexts.Contains(context))
{
AdditionalContexts.Add(context);
+
+ // If we have existing JSON options, update their type resolver
+ if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported)
+ {
+ // Reset the type resolver chain to rebuild it
+ _jsonOptions.TypeInfoResolver = GetCompositeResolver();
+ }
+ }
+ }
+
+ ///
+ /// Get a composite resolver that includes all configured resolvers
+ ///
+ internal static IJsonTypeInfoResolver GetCompositeResolver()
+ {
+ var resolvers = new List();
+
+ // Add custom resolver if provided
+ if (_customTypeInfoResolver != null)
+ {
+ resolvers.Add(_customTypeInfoResolver);
+ }
+
+ // Add default context
+ resolvers.Add(PowertoolsLoggingSerializationContext.Default);
+
+ // Add additional contexts
+ foreach (var context in AdditionalContexts)
+ {
+ resolvers.Add(context);
+ }
+
+ return new CompositeJsonTypeInfoResolver(resolvers.ToArray());
+ }
+
+ ///
+ /// Handles the TypeInfoResolver from the JsonSerializerOptions.
+ ///
+ internal static void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
+ {
+ // Check for TypeInfoResolver and ensure it's not lost
+ if (options?.TypeInfoResolver != null &&
+ options.TypeInfoResolver != GetCompositeResolver())
+ {
+ _customTypeInfoResolver = options.TypeInfoResolver;
+
+ // If it's a JsonSerializerContext, also add it to our contexts
+ if (_customTypeInfoResolver is JsonSerializerContext jsonContext)
+ {
+ AddSerializerContext(jsonContext);
+ }
}
}
@@ -207,14 +260,7 @@ internal static void BuildJsonSerializerOptions(JsonSerializerOptions options =
// Only add TypeInfoResolver if AOT mode
if (!RuntimeFeatureWrapper.IsDynamicCodeSupported)
{
- // Always ensure our default context is in the chain first
- _jsonOptions.TypeInfoResolverChain.Add(PowertoolsLoggingSerializationContext.Default);
-
- // Add all registered contexts
- foreach (var context in AdditionalContexts)
- {
- _jsonOptions.TypeInfoResolverChain.Add(context);
- }
+ HandleJsonOptionsTypeResolver(_jsonOptions);
}
#endif
}
@@ -279,6 +325,10 @@ internal static void ClearContext()
}
#endif
+ internal static void SetOptions(JsonSerializerOptions options)
+ {
+ _currentOptions = options;
+ }
///
/// Clears options for tests
///
From b4741c0769b82439df7a2b5a2c90e99a79362638 Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Sun, 23 Mar 2025 18:31:31 +0000
Subject: [PATCH 17/49] fix net8 serializer
---
.../Serializers/PowertoolsLoggingSerializer.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index 9a09180e..f0b97ce6 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -37,8 +37,6 @@ internal static class PowertoolsLoggingSerializer
private static LoggerOutputCase _currentOutputCase;
private static JsonSerializerOptions _jsonOptions;
private static readonly object _lock = new object();
- private static IJsonTypeInfoResolver? _customTypeInfoResolver = null;
-
private static readonly ConcurrentBag AdditionalContexts =
new ConcurrentBag();
@@ -138,6 +136,9 @@ internal static string Serialize(object value, Type inputType)
}
#if NET8_0_OR_GREATER
+
+ private static IJsonTypeInfoResolver? _customTypeInfoResolver = null;
+
///
/// Adds a JsonSerializerContext to the serializer options.
///
From 249509868534930b7d5c30abc42f73af1364233d Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Mon, 24 Mar 2025 16:31:20 +0000
Subject: [PATCH 18/49] refactor: update log buffering options and improve
serializer handling
---
.../BuilderExtensions.cs | 7 +-
.../Buffer/PowertoolsBufferingLogger.cs | 6 +-
.../Internal/Helpers/LoggerFactoryHelper.cs | 19 +-
...PowertoolsLoggerConfigurationExtensions.cs | 64 +++----
.../Internal/LoggingAspect.cs | 102 +++++------
.../Internal/PowertoolsLogger.cs | 9 +-
.../Internal/PowertoolsLoggerProvider.cs | 3 -
.../Logger.Formatter.cs | 18 +-
.../AWS.Lambda.Powertools.Logging/Logger.cs | 170 +++++++++++++-----
.../PowertoolsLoggerBuilder.cs | 4 +-
.../PowertoolsLoggerConfiguration.cs | 38 +++-
...sions.cs => PowertoolsLoggerExtensions.cs} | 4 +-
.../PowertoolsLoggingSerializer.cs | 119 ++++++------
.../PowertoolsSourceGeneratorSerializer.cs | 2 +-
14 files changed, 321 insertions(+), 244 deletions(-)
rename libraries/src/AWS.Lambda.Powertools.Logging/{LoggerExtensions.cs => PowertoolsLoggerExtensions.cs} (99%)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
index d0c35c75..93675828 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
@@ -50,9 +50,6 @@ public static ILoggingBuilder AddPowertoolsLogger(
// Register services with the options
RegisterServices(builder, options);
- // Apply the output case configuration
- options.ApplyOutputCase();
-
// Configure static Logger (if not already in a configuration cycle)
if (!_configuring)
{
@@ -83,12 +80,12 @@ private static void RegisterServices(ILoggingBuilder builder, PowertoolsLoggerCo
new PowertoolsConfigurations(sp.GetRequiredService()));
// If buffering is enabled, register buffer providers
- if (options?.LogBufferingOptions?.Enabled == true)
+ if (options?.LogBuffering?.Enabled == true)
{
// Add a filter for the buffer provider
builder.AddFilter(
null,
- options.LogBufferingOptions.BufferAtLogLevel);
+ options.LogBuffering.BufferAtLogLevel);
// Register the inner provider factory
builder.Services.TryAddSingleton(sp =>
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
index b2c9da5a..190eb1dd 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
@@ -34,7 +34,7 @@ public bool IsEnabled(LogLevel logLevel)
var options = _options.CurrentValue;
// If buffering is disabled, defer to inner logger
- if (!options.LogBufferingOptions.Enabled)
+ if (!options.LogBuffering.Enabled)
{
return _innerLogger.IsEnabled(logLevel);
}
@@ -48,7 +48,7 @@ public bool IsEnabled(LogLevel logLevel)
// For logs below minimum level but at or above buffer threshold,
// we should handle them (buffer them)
- if (logLevel >= options.LogBufferingOptions.BufferAtLogLevel)
+ if (logLevel >= options.LogBuffering.BufferAtLogLevel)
{
return true;
}
@@ -69,7 +69,7 @@ public void Log(
return;
var options = _options.CurrentValue;
- var bufferOptions = options.LogBufferingOptions;
+ var bufferOptions = options.LogBuffering;
// Check if this log should be buffered
bool shouldBuffer = bufferOptions.Enabled &&
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
index aaa4f684..5b3b4b0b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
@@ -18,19 +18,22 @@ public static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfigura
{
builder.AddPowertoolsLogger(config =>
{
- config.CopyFrom(configuration);
+ config.Service = configuration.Service;
+ config.SamplingRate = configuration.SamplingRate;
+ config.MinimumLogLevel = configuration.MinimumLogLevel;
+ config.LoggerOutputCase = configuration.LoggerOutputCase;
+ config.LoggerOutput = configuration.LoggerOutput;
+ config.JsonOptions = configuration.JsonOptions;
+ config.TimestampFormat = configuration.TimestampFormat;
+ config.LogFormatter = configuration.LogFormatter;
+ config.LogLevelKey = configuration.LogLevelKey;
+ config.LogBuffering = configuration.LogBuffering;
});
});
// Configure the static logger with the factory
Logger.Configure(factory);
-
- // Apply formatter if one is specified
- if (configuration.LogFormatter != null)
- {
- Logger.UseFormatter(configuration.LogFormatter);
- }
-
+
return factory;
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs
index 2b109f15..80b4bd93 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerConfigurationExtensions.cs
@@ -1,32 +1,32 @@
-using System.Text.Json;
-using Microsoft.Extensions.Logging;
-
-namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
-
-///
-/// Extension methods for handling configuration copying between PowertoolsLogger configurations
-///
-internal static class PowertoolsLoggerConfigurationExtensions
-{
- ///
- /// Copies configuration values from source to destination configuration
- ///
- /// The destination configuration to copy values to
- /// The source configuration to copy values from
- /// The updated destination configuration
- public static PowertoolsLoggerConfiguration CopyFrom(this PowertoolsLoggerConfiguration destination, PowertoolsLoggerConfiguration source)
- {
- destination.Service = source.Service;
- destination.SamplingRate = source.SamplingRate;
- destination.MinimumLogLevel = source.MinimumLogLevel;
- destination.LoggerOutputCase = source.LoggerOutputCase;
- destination.LoggerOutput = source.LoggerOutput;
- destination.JsonOptions = source.JsonOptions;
- destination.TimestampFormat = source.TimestampFormat;
- destination.LogFormatter = source.LogFormatter;
- destination.LogLevelKey = source.LogLevelKey;
- destination.LogBufferingOptions = source.LogBufferingOptions;
-
- return destination;
- }
-}
\ No newline at end of file
+// using System.Text.Json;
+// using Microsoft.Extensions.Logging;
+//
+// namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
+//
+// ///
+// /// Extension methods for handling configuration copying between PowertoolsLogger configurations
+// ///
+// internal static class PowertoolsLoggerConfigurationExtensions
+// {
+// ///
+// /// Copies configuration values from source to destination configuration
+// ///
+// /// The destination configuration to copy values to
+// /// The source configuration to copy values from
+// /// The updated destination configuration
+// public static PowertoolsLoggerConfiguration CopyFrom(this PowertoolsLoggerConfiguration destination, PowertoolsLoggerConfiguration source)
+// {
+// destination.Service = source.Service;
+// destination.SamplingRate = source.SamplingRate;
+// destination.MinimumLogLevel = source.MinimumLogLevel;
+// destination.LoggerOutputCase = source.LoggerOutputCase;
+// destination.LoggerOutput = source.LoggerOutput;
+// destination.JsonOptions = source.JsonOptions;
+// destination.TimestampFormat = source.TimestampFormat;
+// destination.LogFormatter = source.LogFormatter;
+// destination.LogLevelKey = source.LogLevelKey;
+// destination.LogBuffering = source.LogBuffering;
+//
+// return destination;
+// }
+// }
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index 66c2482d..750012b7 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -75,7 +75,7 @@ public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
_logEventEnv = powertoolsConfigurations.LoggerLogEvent;
_xRayTraceId = powertoolsConfigurations.XRayTraceId;
// Get Logger Instance
- _logger = Logger.GetPowertoolsLogger();
+ // _logger = Logger.GetPowertoolsLogger();
}
private void InitializeLogger(LoggingAttribute trigger)
@@ -83,50 +83,34 @@ private void InitializeLogger(LoggingAttribute trigger)
// Check which settings are explicitly provided in the attribute
var hasLogLevel = trigger.LogLevel != LogLevel.None;
var hasService = !string.IsNullOrEmpty(trigger.Service);
- var hasOutputCase = trigger.LoggerOutputCase != default;
+ var hasOutputCase = trigger.LoggerOutputCase != LoggerOutputCase.Default;
var hasSamplingRate = trigger.SamplingRate > 0;
- var hasExplicitSettings = hasLogLevel || hasService || hasOutputCase || hasSamplingRate;
+ // Only update configuration if any settings were provided
+ var needsReconfiguration = hasLogLevel || hasService || hasOutputCase || hasSamplingRate;
- if (!Logger.IsConfigured)
+ if (needsReconfiguration)
{
- // First time initialization - create a new configuration with defaults for any unspecified values
- var config = new PowertoolsLoggerConfiguration
- {
- MinimumLogLevel = hasLogLevel ? trigger.LogLevel : LogLevel.Information,
- Service = hasService ? trigger.Service : "service_undefined",
- LoggerOutputCase = hasOutputCase ? trigger.LoggerOutputCase : LoggerOutputCase.SnakeCase,
- SamplingRate = hasSamplingRate ? trigger.SamplingRate : 1.0
- };
-
- Logger.Configure(config);
+ // Apply each setting directly using the existing Logger static methods
+ if (hasLogLevel) Logger.UseMinimumLogLevel(trigger.LogLevel);
+ if (hasService) Logger.UseServiceName(trigger.Service);
+ if (hasOutputCase) Logger.UseOutputCase(trigger.LoggerOutputCase);
+ if (hasSamplingRate) Logger.UseSamplingRate(trigger.SamplingRate);
+
+ // Update logger reference after configuration changes
+ _logger = Logger.GetPowertoolsLogger();
}
- else if (hasExplicitSettings)
+ else if (_logger == null)
{
- // Preserve existing configuration and only override what's explicitly specified
- Logger.UpdateConfiguration(config =>
- {
- if (hasLogLevel)
- config.MinimumLogLevel = trigger.LogLevel;
-
- if (hasService)
- config.Service = trigger.Service;
-
- if (hasOutputCase)
- config.LoggerOutputCase = trigger.LoggerOutputCase;
-
- if (hasSamplingRate)
- config.SamplingRate = trigger.SamplingRate;
- });
+ // Only get the logger if we don't already have it
+ _logger = Logger.GetPowertoolsLogger();
}
-
-
// Fetch the current configuration
var currentConfig = Logger.GetConfiguration();
// Set operational flags based on current configuration
_isDebug = currentConfig.MinimumLogLevel <= LogLevel.Debug;
- _bufferingEnabled = currentConfig.LogBufferingOptions?.Enabled ?? false;
+ _bufferingEnabled = currentConfig.LogBuffering?.Enabled ?? false;
}
///
@@ -281,26 +265,26 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
{
var correlationId = string.Empty;
- var jsonDoc =
- JsonDocument.Parse(PowertoolsLoggingSerializer.Serialize(eventArg, eventArg.GetType()));
-
- var element = jsonDoc.RootElement;
-
- for (var i = 0; i < correlationIdPaths.Length; i++)
- {
- // For casing parsing to be removed from Logging v2 when we get rid of outputcase
- // without this CorrelationIdPaths.ApiGatewayRest would not work
-
- // TODO: fix this
- // var pathWithOutputCase =
- // _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase);
- // if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
- // break;
- //
- // element = childElement;
- if (i == correlationIdPaths.Length - 1)
- correlationId = element.ToString();
- }
+ // var jsonDoc =
+ // JsonDocument.Parse(PowertoolsLoggingSerializer.Serialize(eventArg, eventArg.GetType()));
+ //
+ // var element = jsonDoc.RootElement;
+ //
+ // for (var i = 0; i < correlationIdPaths.Length; i++)
+ // {
+ // // For casing parsing to be removed from Logging v2 when we get rid of outputcase
+ // // without this CorrelationIdPaths.ApiGatewayRest would not work
+ //
+ // // TODO: fix this
+ // // var pathWithOutputCase =
+ // // _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase);
+ // // if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
+ // // break;
+ // //
+ // // element = childElement;
+ // if (i == correlationIdPaths.Length - 1)
+ // correlationId = element.ToString();
+ // }
if (!string.IsNullOrWhiteSpace(correlationId))
_logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId);
@@ -322,12 +306,12 @@ private void LogEvent(object eventArg)
switch (eventArg)
{
case null:
- {
- if (_isDebug)
- _logger.LogDebug(
- "Skipping Event Log because event parameter not found.");
- break;
- }
+ {
+ if (_isDebug)
+ _logger.LogDebug(
+ "Skipping Event Log because event parameter not found.");
+ break;
+ }
case Stream:
try
{
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
index 4aac91b5..88cc32d2 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -138,8 +138,9 @@ internal void LogLine(string message)
internal string LogEntryString(LogLevel logLevel, TState state, Exception exception, Func formatter)
{
var logEntry = LogEntry(logLevel, state, exception, formatter);
- return PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object));
+ return _currentConfig.Serializer.Serialize(logEntry, typeof(object));
}
+
internal object LogEntry(LogLevel logLevel, TState state, Exception exception, Func formatter)
{
var timestamp = DateTime.UtcNow;
@@ -156,7 +157,7 @@ internal object LogEntry(LogLevel logLevel, TState state, Exception exce
: formatter(state, exception);
// Get log entry
- var logFormatter = Logger.GetFormatter();
+ var logFormatter = _currentConfig.LogFormatter;
var logEntry = logFormatter is null
? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters)
: GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters);
@@ -176,7 +177,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
var logEntry = new Dictionary();
// Add Custom Keys
- foreach (var (key, value) in Logger.GetAllKeys())
+ foreach (var (key, value) in this.GetAllKeys())
{
logEntry.TryAdd(key, value);
}
@@ -257,7 +258,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
var extraKeys = new Dictionary();
// Add Custom Keys
- foreach (var (key, value) in Logger.GetAllKeys())
+ foreach (var (key, value) in this.GetAllKeys())
{
switch (key)
{
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index e4e2c4af..7e9ea250 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -128,9 +128,6 @@ private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
}
- // Always configure the serializer with the output case
- // PowertoolsLoggingSerializer.ConfigureNamingPolicy(config.LoggerOutputCase);
-
// Configure the log level key based on output case
config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
config.LoggerOutputCase == LoggerOutputCase.PascalCase
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
index 515ff2f0..fdeb3c97 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
@@ -13,17 +13,10 @@
* permissions and limitations under the License.
*/
-using System;
-using AWS.Lambda.Powertools.Logging.Internal;
-
namespace AWS.Lambda.Powertools.Logging;
public static partial class Logger
{
- private static ILogFormatter _logFormatter;
-
- #region Custom Log Formatter
-
///
/// Set the log formatter.
///
@@ -31,7 +24,7 @@ public static partial class Logger
/// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor
public static void UseFormatter(ILogFormatter logFormatter)
{
- _logFormatter = logFormatter ?? throw new ArgumentNullException(nameof(logFormatter));
+ _currentConfig.LogFormatter = logFormatter;
}
///
@@ -39,13 +32,6 @@ public static void UseFormatter(ILogFormatter logFormatter)
///
public static void UseDefaultFormatter()
{
- _logFormatter = null;
+ _currentConfig.LogFormatter = null;
}
-
- ///
- /// Returns the log formatter.
- ///
- internal static ILogFormatter GetFormatter() => _logFormatter;
-
- #endregion
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index 9df769ea..23f72614 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -14,11 +14,9 @@
*/
using System;
+using System.Text.Json;
using System.Threading;
using AWS.Lambda.Powertools.Common;
-using AWS.Lambda.Powertools.Logging.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging;
@@ -32,30 +30,41 @@ public static partial class Logger
private static Lazy _factoryLazy;
private static Lazy _defaultLoggerLazy;
- // Add a backing field
- private static bool _isConfigured = false;
+ // Static constructor to ensure initialization
+ static Logger()
+ {
+ // Initialize with default configuration (ensures we never have null fields)
+ InitializeWithDefaults();
+ }
// Properties to access the lazy-initialized instances
private static ILoggerFactory Factory => _factoryLazy.Value;
private static ILogger LoggerInstance => _defaultLoggerLazy.Value;
- ///
- /// Gets a value indicating whether the logger is configured.
- ///
- /// true if the logger is configured; otherwise, false.
- public static bool IsConfigured => _isConfigured;
-
// Add this field to the Logger class
private static PowertoolsLoggerConfiguration _currentConfig;
+ // Initialize with default settings
+ private static void InitializeWithDefaults()
+ {
+ _currentConfig = new PowertoolsLoggerConfiguration();
+
+ // Create default factory with minimal configuration
+ _factoryLazy = new Lazy(() =>
+ PowertoolsLoggerFactory.Create(_currentConfig));
+
+ _defaultLoggerLazy = new Lazy(() =>
+ Factory.CreatePowertoolsLogger());
+ }
+
// Allow manual configuration using options
- public static void Configure(Action configureOptions)
+ internal static void Configure(Action configureOptions)
{
var options = new PowertoolsLoggerConfiguration();
configureOptions(options);
Configure(options);
}
-
+
// Configure with existing factory
internal static void Configure(ILoggerFactory loggerFactory)
{
@@ -64,8 +73,6 @@ internal static void Configure(ILoggerFactory loggerFactory)
Interlocked.Exchange(ref _defaultLoggerLazy,
new Lazy(() => Factory.CreatePowertoolsLogger()));
-
- _isConfigured = true;
}
// Directly configure from a PowertoolsLoggerConfiguration
@@ -73,14 +80,15 @@ internal static void Configure(PowertoolsLoggerConfiguration options)
{
if (options == null) throw new ArgumentNullException(nameof(options));
+ // Store current config
+ _currentConfig = options;
+
// Update factory and logger
Interlocked.Exchange(ref _factoryLazy,
- new Lazy(() => PowertoolsLoggerFactory.Create(options)));
+ new Lazy(() => PowertoolsLoggerFactory.Create(_currentConfig)));
Interlocked.Exchange(ref _defaultLoggerLazy,
new Lazy(() => Factory.CreatePowertoolsLogger()));
-
- _isConfigured = true;
}
// Get the current configuration
@@ -88,41 +96,117 @@ internal static PowertoolsLoggerConfiguration GetConfiguration()
{
// Ensure logger is initialized
_ = LoggerInstance;
-
- // Create a new configuration with current settings
- if (_currentConfig == null)
- {
- _currentConfig = new PowertoolsLoggerConfiguration();
- }
-
+
return _currentConfig;
}
// Get a logger for a specific category
- public static ILogger GetLogger() => GetLogger(typeof(T).Name);
+ internal static ILogger GetLogger() => GetLogger(typeof(T).Name);
- public static ILogger GetLogger(string category) => Factory.CreateLogger(category);
+ internal static ILogger GetLogger(string category) => Factory.CreateLogger(category);
- public static ILogger GetPowertoolsLogger() => Factory.CreatePowertoolsLogger();
+ internal static ILogger GetPowertoolsLogger() => Factory.CreatePowertoolsLogger();
+
+ ///
+ /// Sets a custom output for the static logger.
+ /// Useful for testing to redirect logs to a test output.
+ ///
+ /// The custom output implementation
+ public static void UseOutput(ISystemWrapper loggerOutput)
+ {
+ if (loggerOutput == null)
+ throw new ArgumentNullException(nameof(loggerOutput));
+
+ _currentConfig.LoggerOutput = loggerOutput;
+ Configure(_currentConfig);
+ }
+
+ ///
+ /// Configure logger output case (snake_case, camelCase, PascalCase)
+ ///
+ /// The case to use for the output
+ public static void UseOutputCase(LoggerOutputCase outputCase)
+ {
+ _currentConfig.LoggerOutputCase = outputCase;
+ Configure(_currentConfig);
+ }
- // Update configuration settings
- internal static void UpdateConfiguration(Action configureAction)
+ ///
+ /// Configures the minimum log level
+ ///
+ /// The minimum log level to display
+ public static void UseMinimumLogLevel(LogLevel logLevel)
+ {
+ _currentConfig.MinimumLogLevel = logLevel;
+ Configure(_currentConfig);
+ }
+
+ ///
+ /// Configures the service name
+ ///
+ /// The service name to use in logs
+ public static void UseServiceName(string serviceName)
{
- if (configureAction == null) return;
+ if (string.IsNullOrEmpty(serviceName))
+ throw new ArgumentException("Service name cannot be null or empty", nameof(serviceName));
- // Apply updates to current configuration
- configureAction(_currentConfig);
+ _currentConfig.Service = serviceName;
+ Configure(_currentConfig);
+ }
+
+ ///
+ /// Sets the sampling rate for logs
+ ///
+ /// The rate (0.0 to 1.0) for sampling
+ public static void UseSamplingRate(double samplingRate)
+ {
+ if (samplingRate < 0 || samplingRate > 1)
+ throw new ArgumentOutOfRangeException(nameof(samplingRate), "Sampling rate must be between 0 and 1");
- // Apply any output case changes
- _currentConfig.ApplyOutputCase();
+ _currentConfig.SamplingRate = samplingRate;
+ Configure(_currentConfig);
+ }
+
+ ///
+ /// Log buffering options.
+ ///
+ /// Logger.UseLogBuffering(new LogBufferingOptions
+ /// {
+ /// Enabled = true,
+ /// BufferAtLogLevel = LogLevel.Debug
+ /// });
+ ///
+ ///
+ public static void UseLogBuffering(LogBufferingOptions logBuffering)
+ {
+ if (logBuffering == null)
+ throw new ArgumentNullException(nameof(logBuffering));
+
+ // Update the current configuration
+ _currentConfig.LogBuffering = logBuffering;
+
+ // Reconfigure to apply changes
+ Configure(_currentConfig);
+ }
+
+#if NET8_0_OR_GREATER
+ ///
+ /// Configure JSON serialization options
+ ///
+ /// The JSON options to use
+ public static void UseJsonOptions(JsonSerializerOptions jsonOptions)
+ {
+ if (jsonOptions == null)
+ throw new ArgumentNullException(nameof(jsonOptions));
+
+ // Update the current configuration
+ _currentConfig.JsonOptions = jsonOptions;
}
+#endif
+
// For testing purposes
- // internal static void Reset()
- // {
- // Interlocked.Exchange(ref _factoryLazy,
- // new Lazy(() => new PowertoolsLoggerFactory()));
-
- // Interlocked.Exchange(ref _defaultLoggerLazy,
- // new Lazy(() => Factory.CreateLogger()));
- // }
+ internal static void Reset()
+ {
+ InitializeWithDefaults();
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
index c583e42a..e18bc22b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
@@ -64,7 +64,7 @@ public PowertoolsLoggerBuilder WithFormatter(ILogFormatter formatter)
///
public PowertoolsLoggerBuilder WithLogBuffering(bool enabled = true)
{
- _configuration.LogBufferingOptions.Enabled = enabled;
+ _configuration.LogBuffering.Enabled = enabled;
return this;
}
@@ -73,7 +73,7 @@ public PowertoolsLoggerBuilder WithLogBuffering(bool enabled = true)
///
public PowertoolsLoggerBuilder WithLogBuffering(Action configure)
{
- configure?.Invoke(_configuration.LogBufferingOptions);
+ configure?.Invoke(_configuration.LogBuffering);
return this;
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index ed4903db..b77de34b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -83,24 +83,48 @@ public JsonSerializerOptions? JsonOptions
set
{
_jsonOptions = value;
- if (_jsonOptions != null)
+ if (_jsonOptions != null && _serializer != null)
{
- PowertoolsLoggingSerializer.SetOptions(_jsonOptions);
+ _serializer.SetOptions(_jsonOptions);
}
}
}
///
- /// Options for log buffering
+ /// Log buffering options.
+ ///
+ /// Logger.UseLogBuffering(new LogBufferingOptions
+ /// {
+ /// Enabled = true,
+ /// BufferAtLogLevel = LogLevel.Debug
+ /// });
+ ///
///
- public LogBufferingOptions LogBufferingOptions { get; set; } = new LogBufferingOptions();
+ public LogBufferingOptions LogBuffering { get; set; } = new LogBufferingOptions();
///
- /// Apply output case configuration
+ /// Serializer instance for this configuration
///
- internal void ApplyOutputCase()
+ private PowertoolsLoggingSerializer _serializer;
+
+ ///
+ /// Gets the serializer instance for this configuration
+ ///
+ internal PowertoolsLoggingSerializer Serializer => _serializer ??= InitializeSerializer();
+
+
+ ///
+ /// Initialize serializer with the current configuration
+ ///
+ private PowertoolsLoggingSerializer InitializeSerializer()
{
- PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase);
+ var serializer = new PowertoolsLoggingSerializer();
+ if (_jsonOptions != null)
+ {
+ serializer.SetOptions(_jsonOptions);
+ }
+ serializer.ConfigureNamingPolicy(LoggerOutputCase);
+ return serializer;
}
// IOptions implementation
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs
similarity index 99%
rename from libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
rename to libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs
index 21e8dd6a..e3ca6780 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs
@@ -23,7 +23,7 @@ namespace AWS.Lambda.Powertools.Logging;
///
/// Class LoggerExtensions.
///
-public static class LoggerExtensions
+public static class PowertoolsLoggerExtensions
{
#region JSON Logger Extentions
@@ -692,7 +692,7 @@ public static void AppendKey(this ILogger logger, string key, object value)
/// Returns all additional keys added to the log context.
///
/// IEnumerable<KeyValuePair<System.String, System.Object>>.
- public static IEnumerable> GetAllKeys(this ILogger logger)
+ public static IEnumerable> GetAllKeys(this ILogger logger)
{
return Logger.GetAllKeys();
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index f0b97ce6..758da539 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -31,19 +31,22 @@ namespace AWS.Lambda.Powertools.Logging.Serializers;
///
/// Provides serialization functionality for Powertools logging.
///
-internal static class PowertoolsLoggingSerializer
+internal class PowertoolsLoggingSerializer
{
- private static JsonSerializerOptions _currentOptions;
- private static LoggerOutputCase _currentOutputCase;
- private static JsonSerializerOptions _jsonOptions;
- private static readonly object _lock = new object();
- private static readonly ConcurrentBag AdditionalContexts =
+ private JsonSerializerOptions _currentOptions;
+ private LoggerOutputCase _currentOutputCase;
+ private JsonSerializerOptions _jsonOptions;
+ private readonly object _lock = new object();
+
+ private readonly ConcurrentBag _additionalContexts =
new ConcurrentBag();
+ private static JsonSerializerContext _staticAdditionalContexts;
+
///
/// Gets the JsonSerializerOptions instance.
///
- internal static JsonSerializerOptions GetSerializerOptions()
+ internal JsonSerializerOptions GetSerializerOptions()
{
// Double-checked locking pattern for thread safety while ensuring we only build once
if (_jsonOptions == null)
@@ -56,7 +59,7 @@ internal static JsonSerializerOptions GetSerializerOptions()
}
}
}
-
+
return _jsonOptions;
}
@@ -64,14 +67,14 @@ internal static JsonSerializerOptions GetSerializerOptions()
/// Configures the naming policy for the serializer.
///
/// The case to use for serialization.
- internal static void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase)
+ internal void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase)
{
if (_currentOutputCase != loggerOutputCase)
{
lock (_lock)
{
_currentOutputCase = loggerOutputCase;
-
+
// Only rebuild options if they already exist
if (_jsonOptions != null)
{
@@ -88,7 +91,7 @@ internal static void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase)
/// The type of the object to serialize.
/// A JSON string representation of the object.
/// Thrown when the input type is not known to the serializer.
- internal static string Serialize(object value, Type inputType)
+ internal string Serialize(object value, Type inputType)
{
#if NET6_0
var options = GetSerializerOptions();
@@ -100,11 +103,11 @@ internal static string Serialize(object value, Type inputType)
#pragma warning disable
return JsonSerializer.Serialize(value, jsonSerializerOptions);
}
-
+
var options = GetSerializerOptions();
-
+
// Try to serialize using the configured TypeInfoResolver
- try
+ try
{
var typeInfo = GetTypeInfo(inputType);
if (typeInfo != null)
@@ -116,7 +119,7 @@ internal static string Serialize(object value, Type inputType)
{
// Failed to get typeinfo, will fall back to trying the serializer directly
}
-
+
// Fall back to direct serialization which may work if the resolver chain can handle it
try
{
@@ -125,34 +128,36 @@ internal static string Serialize(object value, Type inputType)
catch (JsonException ex)
{
throw new JsonSerializerException(
- $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.", ex);
+ $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.",
+ ex);
}
catch (InvalidOperationException ex)
{
throw new JsonSerializerException(
- $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.", ex);
+ $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.",
+ ex);
}
#endif
}
#if NET8_0_OR_GREATER
-
- private static IJsonTypeInfoResolver? _customTypeInfoResolver = null;
-
+
+ private IJsonTypeInfoResolver? _customTypeInfoResolver = null;
+
///
/// Adds a JsonSerializerContext to the serializer options.
///
/// The JsonSerializerContext to add.
/// Thrown when the context is null.
- internal static void AddSerializerContext(JsonSerializerContext context)
+ internal void AddSerializerContext(JsonSerializerContext context)
{
ArgumentNullException.ThrowIfNull(context);
// Don't add duplicates
- if (!AdditionalContexts.Contains(context))
+ if (!_additionalContexts.Contains(context))
{
- AdditionalContexts.Add(context);
-
+ _additionalContexts.Add(context);
+
// If we have existing JSON options, update their type resolver
if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported)
{
@@ -162,10 +167,17 @@ internal static void AddSerializerContext(JsonSerializerContext context)
}
}
+ internal static void AddStaticSerializerContext(JsonSerializerContext context)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+
+ _staticAdditionalContexts = context;
+ }
+
///
/// Get a composite resolver that includes all configured resolvers
///
- internal static IJsonTypeInfoResolver GetCompositeResolver()
+ private IJsonTypeInfoResolver GetCompositeResolver()
{
var resolvers = new List();
@@ -174,30 +186,36 @@ internal static IJsonTypeInfoResolver GetCompositeResolver()
{
resolvers.Add(_customTypeInfoResolver);
}
+
+ // add any static resolvers
+ if (_staticAdditionalContexts != null)
+ {
+ resolvers.Add(_staticAdditionalContexts);
+ }
// Add default context
resolvers.Add(PowertoolsLoggingSerializationContext.Default);
// Add additional contexts
- foreach (var context in AdditionalContexts)
+ foreach (var context in _additionalContexts)
{
resolvers.Add(context);
}
return new CompositeJsonTypeInfoResolver(resolvers.ToArray());
}
-
+
///
/// Handles the TypeInfoResolver from the JsonSerializerOptions.
///
- internal static void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
+ private void HandleJsonOptionsTypeResolver(JsonSerializerOptions options)
{
// Check for TypeInfoResolver and ensure it's not lost
- if (options?.TypeInfoResolver != null &&
+ if (options?.TypeInfoResolver != null &&
options.TypeInfoResolver != GetCompositeResolver())
{
_customTypeInfoResolver = options.TypeInfoResolver;
-
+
// If it's a JsonSerializerContext, also add it to our contexts
if (_customTypeInfoResolver is JsonSerializerContext jsonContext)
{
@@ -211,7 +229,7 @@ internal static void HandleJsonOptionsTypeResolver(JsonSerializerOptions options
///
/// The type to get information for.
/// The JsonTypeInfo for the specified type, or null if not found.
- internal static JsonTypeInfo GetTypeInfo(Type type)
+ private JsonTypeInfo GetTypeInfo(Type type)
{
var options = GetSerializerOptions();
return options.TypeInfoResolver?.GetTypeInfo(type, options);
@@ -220,12 +238,12 @@ internal static JsonTypeInfo GetTypeInfo(Type type)
///
/// Checks if a type is supported by any of the configured type resolvers
///
- internal static bool IsTypeSupportedByAnyResolver(Type type)
+ private bool IsTypeSupportedByAnyResolver(Type type)
{
var options = GetSerializerOptions();
if (options.TypeInfoResolver == null)
return false;
-
+
try
{
var typeInfo = options.TypeInfoResolver.GetTypeInfo(type, options);
@@ -242,13 +260,13 @@ internal static bool IsTypeSupportedByAnyResolver(Type type)
/// Builds and configures the JsonSerializerOptions.
///
/// A configured JsonSerializerOptions instance.
- internal static void BuildJsonSerializerOptions(JsonSerializerOptions options = null)
+ private void BuildJsonSerializerOptions(JsonSerializerOptions options = null)
{
lock (_lock)
{
// This should already be in a lock when called
_jsonOptions = options ?? new JsonSerializerOptions();
-
+
SetOutputCase();
AddConverters();
@@ -267,7 +285,7 @@ internal static void BuildJsonSerializerOptions(JsonSerializerOptions options =
}
}
- private static void SetOutputCase()
+ internal void SetOutputCase()
{
switch (_currentOutputCase)
{
@@ -286,9 +304,11 @@ private static void SetOutputCase()
{
_jsonOptions.DictionaryKeyPolicy = _jsonOptions.DictionaryKeyPolicy;
_jsonOptions.PropertyNamingPolicy = _jsonOptions.PropertyNamingPolicy;
- }else{
+ }
+ else
+ {
_jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
- _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
+ _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower;
}
#else
_jsonOptions.PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance;
@@ -298,7 +318,7 @@ private static void SetOutputCase()
}
}
- private static void AddConverters()
+ private void AddConverters()
{
_jsonOptions.Converters.Add(new ByteArrayConverter());
_jsonOptions.Converters.Add(new ExceptionConverter());
@@ -314,27 +334,8 @@ private static void AddConverters()
#endif
}
-#if NET8_0_OR_GREATER
- internal static bool HasContext(JsonSerializerContext customContext)
- {
- return AdditionalContexts.Contains(customContext);
- }
-
- internal static void ClearContext()
- {
- AdditionalContexts.Clear();
- }
-#endif
-
- internal static void SetOptions(JsonSerializerOptions options)
+ internal void SetOptions(JsonSerializerOptions options)
{
_currentOptions = options;
}
- ///
- /// Clears options for tests
- ///
- internal static void ClearOptions()
- {
- _jsonOptions = null;
- }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs
index dadec8da..46c83c49 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs
@@ -74,7 +74,7 @@ public PowertoolsSourceGeneratorSerializer(
}
var jsonSerializerContext = constructor.Invoke(new object[] { options }) as TSgContext;
- PowertoolsLoggingSerializer.AddSerializerContext(jsonSerializerContext);
+ PowertoolsLoggingSerializer.AddStaticSerializerContext(jsonSerializerContext);
}
}
From e4975634a43d91cf049248a6ec78a910bc10075c Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Tue, 25 Mar 2025 12:47:07 +0000
Subject: [PATCH 19/49] refactor configuration and create StringCaseExtensions
---
.../Core/TestLoggerOutput.cs | 9 +
.../Internal/LoggingAspect.cs | 67 ++---
.../Internal/LoggingAspectFactory.cs | 8 +-
.../PowertoolsConfigurationsExtension.cs | 229 +++++++++---------
.../Internal/PowertoolsLoggerProvider.cs | 5 +-
.../Internal/StringCaseExtensions.cs | 132 ++++++++++
.../PowertoolsLoggerConfiguration.cs | 3 +
.../PowertoolsLoggerFactory.cs | 3 -
8 files changed, 293 insertions(+), 163 deletions(-)
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
index 5a70b529..9a64c881 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
@@ -48,4 +48,13 @@ public void SetOut(TextWriter writeTo)
{
Console.SetOut(writeTo);
}
+
+ ///
+ /// Overrides the ToString method to return the buffer as a string.
+ ///
+ ///
+ public override string ToString()
+ {
+ return Buffer.ToString();
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index 750012b7..10be5b0f 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -31,7 +31,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
/// Scope.Global is singleton
///
///
-[Aspect(Scope.Global, Factory = typeof(LoggingAspectFactory))]
+[Aspect(Scope.Global)]
public class LoggingAspect
{
///
@@ -60,24 +60,11 @@ public class LoggingAspect
private bool _clearLambdaContext;
private ILogger _logger;
- private readonly bool _logEventEnv;
- private readonly string _xRayTraceId;
private bool _isDebug;
private bool _bufferingEnabled;
+ private PowertoolsLoggerConfiguration _currentConfig;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The Powertools configurations.
- public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations)
- {
- _logEventEnv = powertoolsConfigurations.LoggerLogEvent;
- _xRayTraceId = powertoolsConfigurations.XRayTraceId;
- // Get Logger Instance
- // _logger = Logger.GetPowertoolsLogger();
- }
-
private void InitializeLogger(LoggingAttribute trigger)
{
// Check which settings are explicitly provided in the attribute
@@ -106,11 +93,11 @@ private void InitializeLogger(LoggingAttribute trigger)
_logger = Logger.GetPowertoolsLogger();
}
// Fetch the current configuration
- var currentConfig = Logger.GetConfiguration();
+ _currentConfig = Logger.GetConfiguration();
// Set operational flags based on current configuration
- _isDebug = currentConfig.MinimumLogLevel <= LogLevel.Debug;
- _bufferingEnabled = currentConfig.LogBuffering?.Enabled ?? false;
+ _isDebug = _currentConfig.MinimumLogLevel <= LogLevel.Debug;
+ _bufferingEnabled = _currentConfig.LogBuffering?.Enabled ?? false;
}
///
@@ -174,7 +161,7 @@ public void OnEntry(
}
CaptureCorrelationId(eventObject, trigger.CorrelationIdPath);
- if (logEvent || _logEventEnv)
+ if (logEvent || _currentConfig.LogEvent)
LogEvent(eventObject);
}
catch (Exception exception)
@@ -216,10 +203,10 @@ public void OnExit()
///
private void CaptureXrayTraceId()
{
- if (string.IsNullOrWhiteSpace(_xRayTraceId))
+ if (string.IsNullOrWhiteSpace(_currentConfig.XRayTraceId))
return;
_logger.AppendKey(LoggingConstants.KeyXRayTraceId,
- _xRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""));
+ _currentConfig.XRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""));
}
///
@@ -265,26 +252,24 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath)
{
var correlationId = string.Empty;
- // var jsonDoc =
- // JsonDocument.Parse(PowertoolsLoggingSerializer.Serialize(eventArg, eventArg.GetType()));
- //
- // var element = jsonDoc.RootElement;
- //
- // for (var i = 0; i < correlationIdPaths.Length; i++)
- // {
- // // For casing parsing to be removed from Logging v2 when we get rid of outputcase
- // // without this CorrelationIdPaths.ApiGatewayRest would not work
- //
- // // TODO: fix this
- // // var pathWithOutputCase =
- // // _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase);
- // // if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
- // // break;
- // //
- // // element = childElement;
- // if (i == correlationIdPaths.Length - 1)
- // correlationId = element.ToString();
- // }
+ var jsonDoc =
+ JsonDocument.Parse(_currentConfig.Serializer.Serialize(eventArg, eventArg.GetType()));
+
+ var element = jsonDoc.RootElement;
+
+ for (var i = 0; i < correlationIdPaths.Length; i++)
+ {
+ // TODO: For casing parsing to be removed from Logging v2 when we get rid of outputcase without this CorrelationIdPaths.ApiGatewayRest would not work
+ // TODO: This will be removed and replaced by JMesPath
+
+ var pathWithOutputCase = correlationIdPaths[i].ToCase(_currentConfig.LoggerOutputCase);
+ if (!element.TryGetProperty(pathWithOutputCase, out var childElement))
+ break;
+
+ element = childElement;
+ if (i == correlationIdPaths.Length - 1)
+ correlationId = element.ToString();
+ }
if (!string.IsNullOrWhiteSpace(correlationId))
_logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId);
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
index 72ea5645..903a8850 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
@@ -28,8 +28,8 @@ internal static class LoggingAspectFactory
///
/// The type of the class to be logged.
/// An instance of the LoggingAspect class.
- public static object GetInstance(Type type)
- {
- return new LoggingAspect(PowertoolsConfigurations.Instance);
- }
+ // public static object GetInstance(Type type)
+ // {
+ // return new LoggingAspect(PowertoolsConfigurations.Instance);
+ // }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs
index d513c185..dd36ce87 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Serializers;
using Microsoft.Extensions.Logging;
@@ -193,120 +194,120 @@ internal static bool LambdaLogLevelEnabled(this IPowertoolsConfigurations powert
///
/// The input string converted to the configured case (camel, pascal, or snake case).
///
- internal static string ConvertToOutputCase(this IPowertoolsConfigurations powertoolsConfigurations,
- string correlationIdPath, LoggerOutputCase loggerOutputCase)
- {
- return powertoolsConfigurations.GetLoggerOutputCase(loggerOutputCase) switch
- {
- LoggerOutputCase.CamelCase => ToCamelCase(correlationIdPath),
- LoggerOutputCase.PascalCase => ToPascalCase(correlationIdPath),
- _ => ToSnakeCase(correlationIdPath), // default snake_case
- };
- }
-
- ///
- /// Converts a string to snake_case.
- ///
- ///
- /// The input string converted to snake_case.
- private static string ToSnakeCase(string input)
- {
- if (string.IsNullOrEmpty(input))
- return input;
-
- var result = new StringBuilder(input.Length + 10);
- bool lastCharWasUnderscore = false;
- bool lastCharWasUpper = false;
-
- for (int i = 0; i < input.Length; i++)
- {
- char currentChar = input[i];
-
- if (currentChar == '_')
- {
- result.Append('_');
- lastCharWasUnderscore = true;
- lastCharWasUpper = false;
- }
- else if (char.IsUpper(currentChar))
- {
- if (i > 0 && !lastCharWasUnderscore &&
- (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1]))))
- {
- result.Append('_');
- }
-
- result.Append(char.ToLowerInvariant(currentChar));
- lastCharWasUnderscore = false;
- lastCharWasUpper = true;
- }
- else
- {
- result.Append(char.ToLowerInvariant(currentChar));
- lastCharWasUnderscore = false;
- lastCharWasUpper = false;
- }
- }
-
- return result.ToString();
- }
-
-
- ///
- /// Converts a string to PascalCase.
- ///
- ///
- /// The input string converted to PascalCase.
- private static string ToPascalCase(string input)
- {
- if (string.IsNullOrEmpty(input))
- return input;
-
- var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
- var result = new StringBuilder();
-
- foreach (var word in words)
- {
- if (word.Length > 0)
- {
- // Capitalize the first character of each word
- result.Append(char.ToUpperInvariant(word[0]));
-
- // Handle the rest of the characters
- if (word.Length > 1)
- {
- // If the word is all uppercase, convert the rest to lowercase
- if (word.All(char.IsUpper))
- {
- result.Append(word.Substring(1).ToLowerInvariant());
- }
- else
- {
- // Otherwise, keep the original casing
- result.Append(word.Substring(1));
- }
- }
- }
- }
-
- return result.ToString();
- }
-
- ///
- /// Converts a string to camelCase.
- ///
- /// The string to convert.
- /// The input string converted to camelCase.
- private static string ToCamelCase(string input)
- {
- if (string.IsNullOrEmpty(input))
- return input;
-
- // First, convert to PascalCase
- string pascalCase = ToPascalCase(input);
+ // internal static string ConvertToOutputCase(this IPowertoolsConfigurations powertoolsConfigurations,
+ // string correlationIdPath, LoggerOutputCase loggerOutputCase)
+ // {
+ // return powertoolsConfigurations.GetLoggerOutputCase(loggerOutputCase) switch
+ // {
+ // LoggerOutputCase.CamelCase => ToCamelCase(correlationIdPath),
+ // LoggerOutputCase.PascalCase => ToPascalCase(correlationIdPath),
+ // _ => ToSnakeCase(correlationIdPath), // default snake_case
+ // };
+ // }
- // Then convert the first character to lowercase
- return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1);
- }
+ // ///
+ // /// Converts a string to snake_case.
+ // ///
+ // ///
+ // /// The input string converted to snake_case.
+ // private static string ToSnakeCase(string input)
+ // {
+ // if (string.IsNullOrEmpty(input))
+ // return input;
+ //
+ // var result = new StringBuilder(input.Length + 10);
+ // bool lastCharWasUnderscore = false;
+ // bool lastCharWasUpper = false;
+ //
+ // for (int i = 0; i < input.Length; i++)
+ // {
+ // char currentChar = input[i];
+ //
+ // if (currentChar == '_')
+ // {
+ // result.Append('_');
+ // lastCharWasUnderscore = true;
+ // lastCharWasUpper = false;
+ // }
+ // else if (char.IsUpper(currentChar))
+ // {
+ // if (i > 0 && !lastCharWasUnderscore &&
+ // (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1]))))
+ // {
+ // result.Append('_');
+ // }
+ //
+ // result.Append(char.ToLowerInvariant(currentChar));
+ // lastCharWasUnderscore = false;
+ // lastCharWasUpper = true;
+ // }
+ // else
+ // {
+ // result.Append(char.ToLowerInvariant(currentChar));
+ // lastCharWasUnderscore = false;
+ // lastCharWasUpper = false;
+ // }
+ // }
+ //
+ // return result.ToString();
+ // }
+ //
+ //
+ // ///
+ // /// Converts a string to PascalCase.
+ // ///
+ // ///
+ // /// The input string converted to PascalCase.
+ // private static string ToPascalCase(string input)
+ // {
+ // if (string.IsNullOrEmpty(input))
+ // return input;
+ //
+ // var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
+ // var result = new StringBuilder();
+ //
+ // foreach (var word in words)
+ // {
+ // if (word.Length > 0)
+ // {
+ // // Capitalize the first character of each word
+ // result.Append(char.ToUpperInvariant(word[0]));
+ //
+ // // Handle the rest of the characters
+ // if (word.Length > 1)
+ // {
+ // // If the word is all uppercase, convert the rest to lowercase
+ // if (word.All(char.IsUpper))
+ // {
+ // result.Append(word.Substring(1).ToLowerInvariant());
+ // }
+ // else
+ // {
+ // // Otherwise, keep the original casing
+ // result.Append(word.Substring(1));
+ // }
+ // }
+ // }
+ // }
+ //
+ // return result.ToString();
+ // }
+ //
+ // ///
+ // /// Converts a string to camelCase.
+ // ///
+ // /// The string to convert.
+ // /// The input string converted to camelCase.
+ // private static string ToCamelCase(string input)
+ // {
+ // if (string.IsNullOrEmpty(input))
+ // return input;
+ //
+ // // First, convert to PascalCase
+ // string pascalCase = ToPascalCase(input);
+ //
+ // // Then convert the first character to lowercase
+ // return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1);
+ // }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index 7e9ea250..663e3f05 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -83,7 +83,7 @@ public ILogger CreateLogger(string categoryName)
_systemWrapper));
}
- private PowertoolsLoggerConfiguration GetCurrentConfig()
+ internal PowertoolsLoggerConfiguration GetCurrentConfig()
{
var config = _currentConfig;
@@ -128,6 +128,9 @@ private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
}
+ config.XRayTraceId = _powertoolsConfigurations.XRayTraceId;
+ config.LogEvent = _powertoolsConfigurations.LoggerLogEvent;
+
// Configure the log level key based on output case
config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
config.LoggerOutputCase == LoggerOutputCase.PascalCase
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs
new file mode 100644
index 00000000..7e7b390a
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Linq;
+using System.Text;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+/// Extension methods for string case conversion.
+///
+internal static class StringCaseExtensions
+{
+ ///
+ /// Converts a string to camelCase.
+ ///
+ /// The string to convert.
+ /// A camelCase formatted string.
+ public static string ToCamel(this string value)
+ {
+ if (string.IsNullOrEmpty(value))
+ return value;
+
+ // Convert to PascalCase first to handle potential snake_case or kebab-case
+ string pascalCase = ToPascal(value);
+
+ // Convert first char to lowercase
+ return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1);
+ }
+
+ ///
+ /// Converts a string to PascalCase.
+ ///
+ /// The string to convert.
+ /// A PascalCase formatted string.
+ public static string ToPascal(this string input)
+ {
+ if (string.IsNullOrEmpty(input))
+ return input;
+
+ var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
+ var result = new StringBuilder();
+
+ foreach (var word in words)
+ {
+ if (word.Length > 0)
+ {
+ // Capitalize the first character of each word
+ result.Append(char.ToUpperInvariant(word[0]));
+
+ // Handle the rest of the characters
+ if (word.Length > 1)
+ {
+ // If the word is all uppercase, convert the rest to lowercase
+ if (word.All(char.IsUpper))
+ {
+ result.Append(word.Substring(1).ToLowerInvariant());
+ }
+ else
+ {
+ // Otherwise, keep the original casing
+ result.Append(word.Substring(1));
+ }
+ }
+ }
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Converts a string to snake_case.
+ ///
+ /// The string to convert.
+ /// A snake_case formatted string.
+ public static string ToSnake(this string input)
+ {
+ if (string.IsNullOrEmpty(input))
+ return input;
+
+ var result = new StringBuilder(input.Length + 10);
+ bool lastCharWasUnderscore = false;
+ bool lastCharWasUpper = false;
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ char currentChar = input[i];
+
+ if (currentChar == '_')
+ {
+ result.Append('_');
+ lastCharWasUnderscore = true;
+ lastCharWasUpper = false;
+ }
+ else if (char.IsUpper(currentChar))
+ {
+ if (i > 0 && !lastCharWasUnderscore &&
+ (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1]))))
+ {
+ result.Append('_');
+ }
+
+ result.Append(char.ToLowerInvariant(currentChar));
+ lastCharWasUnderscore = false;
+ lastCharWasUpper = true;
+ }
+ else
+ {
+ result.Append(char.ToLowerInvariant(currentChar));
+ lastCharWasUnderscore = false;
+ lastCharWasUpper = false;
+ }
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Converts a string to the specified case format.
+ ///
+ /// The string to convert.
+ /// The target case format.
+ /// A formatted string in the specified case.
+ public static string ToCase(this string value, LoggerOutputCase outputCase)
+ {
+ return outputCase switch
+ {
+ LoggerOutputCase.CamelCase => value.ToCamel(),
+ LoggerOutputCase.PascalCase => value.ToPascal(),
+ LoggerOutputCase.SnakeCase => value.ToSnake(),
+ _ => value.ToSnake() // Default/unchanged
+ };
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index b77de34b..e713eb0b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -129,4 +129,7 @@ private PowertoolsLoggingSerializer InitializeSerializer()
// IOptions implementation
PowertoolsLoggerConfiguration IOptions.Value => this;
+
+ internal string XRayTraceId { get; set; }
+ internal bool LogEvent { get; set; }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
index 18640b7b..6c5a9005 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs
@@ -25,10 +25,7 @@ public static PowertoolsLoggerFactory Create(Action
Date: Fri, 28 Mar 2025 15:49:13 +0000
Subject: [PATCH 20/49] checkpoint, some issues with buffer and loglevel
---
.../BuilderExtensions.cs | 111 ---------
.../Buffer/BufferingLoggerProvider.cs | 18 +-
.../Buffer/PowertoolsBufferingLogger.cs | 10 +-
.../Internal/Helpers/LoggerFactoryHelper.cs | 47 +++-
.../Internal/LoggerFactoryHolder.cs | 190 +++++++++++++++
.../Internal/LoggingAspect.cs | 14 +-
.../Internal/LoggingAspectFactory.cs | 8 +-
.../Internal/PowertoolsLogger.cs | 41 ++--
.../Internal/PowertoolsLoggerProvider.cs | 213 ++++++++--------
.../Logger.Formatter.cs | 8 +-
.../AWS.Lambda.Powertools.Logging/Logger.cs | 178 +++++++-------
.../PowertoolsLoggerBuilder.cs | 6 -
.../PowertoolsLoggerConfiguration.cs | 5 -
.../PowertoolsLoggingBuilderExtensions.cs | 227 ++++++++++++++++++
14 files changed, 713 insertions(+), 363 deletions(-)
delete mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs
create mode 100644 libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
deleted file mode 100644
index 93675828..00000000
--- a/libraries/src/AWS.Lambda.Powertools.Logging/BuilderExtensions.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-using System;
-using AWS.Lambda.Powertools.Common;
-using AWS.Lambda.Powertools.Logging.Internal;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Configuration;
-using Microsoft.Extensions.Options;
-
-namespace AWS.Lambda.Powertools.Logging;
-
-///
-/// Extension methods for configuring the Powertools logger
-///
-public static class BuilderExtensions
-{
- // Track if we're in the middle of configuration to prevent recursion
- private static bool _configuring = false;
-
- ///
- /// Adds the Powertools logger to the logging builder.
- ///
- public static ILoggingBuilder AddPowertoolsLogger(
- this ILoggingBuilder builder,
- Action? configure = null)
- {
- // Add configuration
- builder.AddConfiguration();
-
- // If no configuration was provided, register services with defaults
- if (configure == null)
- {
- RegisterServices(builder);
- return builder;
- }
-
- // Create initial configuration
- var options = new PowertoolsLoggerConfiguration();
- configure(options);
-
- // IMPORTANT: Set the minimum level directly on the builder
- if (options.MinimumLogLevel != LogLevel.None)
- {
- builder.SetMinimumLevel(options.MinimumLogLevel);
- }
-
- // Configure options for DI
- builder.Services.Configure(configure);
-
- // Register services with the options
- RegisterServices(builder, options);
-
- // Configure static Logger (if not already in a configuration cycle)
- if (!_configuring)
- {
- try
- {
- _configuring = true;
- Logger.Configure(options);
- }
- finally
- {
- _configuring = false;
- }
- }
-
- return builder;
- }
-
- private static void RegisterServices(ILoggingBuilder builder, PowertoolsLoggerConfiguration options = null)
- {
- // Register ISystemWrapper if not already registered
- builder.Services.TryAddSingleton();
-
- // Register IPowertoolsEnvironment if it exists
- builder.Services.TryAddSingleton();
-
- // Register IPowertoolsConfigurations with all its dependencies
- builder.Services.TryAddSingleton(sp =>
- new PowertoolsConfigurations(sp.GetRequiredService()));
-
- // If buffering is enabled, register buffer providers
- if (options?.LogBuffering?.Enabled == true)
- {
- // Add a filter for the buffer provider
- builder.AddFilter(
- null,
- options.LogBuffering.BufferAtLogLevel);
-
- // Register the inner provider factory
- builder.Services.TryAddSingleton(sp =>
- new BufferingLoggerProvider(
- // Create a new PowertoolsLoggerProvider specifically for buffering
- new PowertoolsLoggerProvider(
- sp.GetRequiredService>(),
- sp.GetRequiredService(),
- sp.GetRequiredService()
- ),
- sp.GetRequiredService>()
- )
- );
- }
-
- // Register the regular provider
- builder.Services.TryAddEnumerable(
- ServiceDescriptor.Singleton());
-
- LoggerProviderOptions.RegisterProviderOptions
- (builder.Services);
- }
-}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
index 3802f042..c3b099dc 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
@@ -24,19 +24,25 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
/// Logger provider that supports buffering logs
///
[ProviderAlias("PowertoolsBuffering")]
-internal partial class BufferingLoggerProvider : ILoggerProvider
+internal class BufferingLoggerProvider : ILoggerProvider
{
private readonly ILoggerProvider _innerProvider;
private readonly ConcurrentDictionary _loggers = new();
- private readonly IOptionsMonitor _options;
+ private readonly IDisposable? _onChangeToken;
+ private PowertoolsLoggerConfiguration _currentConfig;
public BufferingLoggerProvider(
ILoggerProvider innerProvider,
- IOptionsMonitor options)
+ IOptionsMonitor config)
{
_innerProvider = innerProvider ?? throw new ArgumentNullException(nameof(innerProvider));
- _options = options ?? throw new ArgumentNullException(nameof(options));
+ _currentConfig = config.CurrentValue;
+ _onChangeToken = config.OnChange(updatedConfig =>
+ {
+ _currentConfig = updatedConfig;
+ // No need to do anything else - the loggers get the config through GetCurrentConfig
+ });
// Register with the buffer manager
LogBufferManager.RegisterProvider(this);
}
@@ -47,10 +53,12 @@ public ILogger CreateLogger(string categoryName)
categoryName,
name => new PowertoolsBufferingLogger(
_innerProvider.CreateLogger(name),
- _options,
+ GetCurrentConfig,
name));
}
+ internal PowertoolsLoggerConfiguration GetCurrentConfig() => _currentConfig;
+
public void Dispose()
{
// Flush all buffers before disposing
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
index 190eb1dd..ef2c4f95 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs
@@ -10,17 +10,17 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
internal class PowertoolsBufferingLogger : ILogger
{
private readonly ILogger _innerLogger;
- private readonly IOptionsMonitor _options;
+ private readonly Func _getCurrentConfig;
private readonly string _categoryName;
private readonly LogBuffer _buffer = new();
public PowertoolsBufferingLogger(
ILogger innerLogger,
- IOptionsMonitor options,
+ Func getCurrentConfig,
string categoryName)
{
_innerLogger = innerLogger;
- _options = options;
+ _getCurrentConfig = getCurrentConfig;
_categoryName = categoryName;
}
@@ -31,7 +31,7 @@ public IDisposable BeginScope(TState state)
public bool IsEnabled(LogLevel logLevel)
{
- var options = _options.CurrentValue;
+ var options = _getCurrentConfig();
// If buffering is disabled, defer to inner logger
if (!options.LogBuffering.Enabled)
@@ -68,7 +68,7 @@ public void Log(
if (!IsEnabled(logLevel))
return;
- var options = _options.CurrentValue;
+ var options = _getCurrentConfig();
var bufferOptions = options.LogBuffering;
// Check if this log should be buffered
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
index 5b3b4b0b..a4c64a65 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
@@ -1,3 +1,6 @@
+using System;
+using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Common.Tests;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
@@ -22,7 +25,6 @@ public static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfigura
config.SamplingRate = configuration.SamplingRate;
config.MinimumLogLevel = configuration.MinimumLogLevel;
config.LoggerOutputCase = configuration.LoggerOutputCase;
- config.LoggerOutput = configuration.LoggerOutput;
config.JsonOptions = configuration.JsonOptions;
config.TimestampFormat = configuration.TimestampFormat;
config.LogFormatter = configuration.LogFormatter;
@@ -32,8 +34,49 @@ public static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfigura
});
// Configure the static logger with the factory
- Logger.Configure(factory);
+ // Logger.Configure(factory);
return factory;
}
+}
+
+// Add to a new TestHelpers.cs file
+public static class PowertoolsLoggerTestHelpers
+{
+ private static readonly object _lock = new();
+ private static ISystemWrapper _systemWrapper;
+
+ static PowertoolsLoggerTestHelpers()
+ {
+ _systemWrapper = null;
+ }
+
+ // Call this at the beginning of your test
+ public static TestLoggerOutput EnableTestMode()
+ {
+ var system = new TestLoggerOutput();
+ _systemWrapper = system;
+ PowertoolsLoggingBuilderExtensions.UpdateSystemInAllProviders(system);
+ return system;
+ }
+
+ public static void UseCustomSystem(ISystemWrapper system)
+ {
+ if (system == null) throw new ArgumentNullException(nameof(system));
+ lock (_lock)
+ {
+ // Store the mock system for later use when providers are created
+ _systemWrapper = system;
+ // Update all providers to use the mock system
+ PowertoolsLoggingBuilderExtensions.UpdateSystemInAllProviders(system);
+ }
+ }
+
+ internal static ISystemWrapper GetSystemWrapper()
+ {
+ lock (_lock)
+ {
+ return _systemWrapper;
+ }
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs
new file mode 100644
index 00000000..bde8860f
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs
@@ -0,0 +1,190 @@
+/*
+ * 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;
+using System.Threading;
+using AWS.Lambda.Powertools.Common;
+using Microsoft.Extensions.Logging;
+
+namespace AWS.Lambda.Powertools.Logging.Internal;
+
+///
+/// Holds and manages the shared logger factory instance
+///
+internal static class LoggerFactoryHolder
+{
+ private static ILoggerFactory? _factory;
+ private static readonly object _lock = new object();
+ private static bool _isConfigured = false;
+
+ ///
+ /// Gets or creates the shared logger factory
+ ///
+ public static ILoggerFactory GetOrCreateFactory()
+ {
+ lock (_lock)
+ {
+ if (_factory == null)
+ {
+ _factory = LoggerFactory.Create(builder => builder.AddPowertoolsLogger());
+ }
+ return _factory;
+ }
+ }
+
+ public static void SetFactory(ILoggerFactory factory)
+ {
+ if (factory == null) throw new ArgumentNullException(nameof(factory));
+ lock (_lock)
+ {
+ _factory = factory;
+ _isConfigured = true;
+ }
+ }
+
+ ///
+ /// Automatically called when GetOrCreateFactory is used
+ ///
+ public static void ConfigureFromEnvironment(IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
+ {
+ // Only configure once
+ if (_isConfigured) return;
+
+ // Create initial configuration
+ var config = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
+ // Apply environment configuration if available
+ if (configurations != null)
+ {
+ ApplyPowertoolsConfig(config, configurations, systemWrapper);
+ PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config);
+ }
+
+ _isConfigured = true;
+ }
+
+ ///
+ /// Apply Powertools configuration from environment variables to the logger configuration
+ ///
+ private static void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config,
+ IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
+ {
+ var logLevel = configurations.GetLogLevel(LogLevel.None);
+ var lambdaLogLevel = configurations.GetLambdaLogLevel();
+ var lambdaLogLevelEnabled = configurations.LambdaLogLevelEnabled();
+
+ // Check for explicit config
+ bool hasExplicitLevel = config.MinimumLogLevel != LogLevel.None;
+
+ // Warn if Lambda log level doesn't match
+ if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLogLevel < lambdaLogLevel)
+ {
+ systemWrapper.LogLine(
+ $"Current log level ({config.MinimumLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
+ }
+
+ // Set service from environment if not explicitly set
+ if (string.IsNullOrEmpty(config.Service))
+ {
+ config.Service = configurations.Service;
+ }
+
+ // Set output case from environment if not explicitly set
+ if (config.LoggerOutputCase == LoggerOutputCase.Default)
+ {
+ var loggerOutputCase = configurations.GetLoggerOutputCase(config.LoggerOutputCase);
+ config.LoggerOutputCase = loggerOutputCase;
+ }
+
+ // Set log level from environment ONLY if not explicitly set
+ if (!hasExplicitLevel)
+ {
+ var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
+ config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
+ }
+
+ config.XRayTraceId = configurations.XRayTraceId;
+ config.LogEvent = configurations.LoggerLogEvent;
+
+ // Configure the log level key based on output case
+ config.LogLevelKey = configurations.LambdaLogLevelEnabled() &&
+ config.LoggerOutputCase == LoggerOutputCase.PascalCase
+ ? "LogLevel"
+ : LoggingConstants.KeyLogLevel;
+
+ ProcessSamplingRate(config, configurations, systemWrapper);
+ }
+
+ ///
+ /// Process sampling rate configuration
+ ///
+ private static void ProcessSamplingRate(PowertoolsLoggerConfiguration config, IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
+ {
+ var samplingRate = config.SamplingRate > 0
+ ? config.SamplingRate
+ : configurations.LoggerSampleRate;
+
+ samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLogLevel, systemWrapper);
+ config.SamplingRate = samplingRate;
+
+ // Only notify if sampling is configured
+ if (samplingRate > 0)
+ {
+ double sample = systemWrapper.GetRandom();
+
+ // Instead of changing log level, just indicate sampling status
+ if (sample <= samplingRate)
+ {
+ systemWrapper.LogLine(
+ $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
+ config.MinimumLogLevel = LogLevel.Debug;
+ }
+ }
+ }
+
+ ///
+ /// Validate sampling rate
+ ///
+ private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
+ {
+ if (samplingRate < 0 || samplingRate > 1)
+ {
+ if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
+ {
+ systemWrapper.LogLine(
+ $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
+ }
+
+ return 0;
+ }
+
+ return samplingRate;
+ }
+
+
+
+ ///
+ /// Resets the factory holder for testing
+ ///
+ internal static void Reset()
+ {
+ lock (_lock)
+ {
+ var oldFactory = Interlocked.Exchange(ref _factory, null);
+ oldFactory?.Dispose();
+ _isConfigured = false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index 10be5b0f..0f301f9e 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -31,9 +31,11 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
/// Scope.Global is singleton
///
///
-[Aspect(Scope.Global)]
+[Aspect(Scope.Global, Factory = typeof(LoggingAspectFactory))]
public class LoggingAspect
{
+ private readonly ILoggerFactory _loggerFactory;
+
///
/// The is cold start
///
@@ -64,6 +66,10 @@ public class LoggingAspect
private bool _bufferingEnabled;
private PowertoolsLoggerConfiguration _currentConfig;
+ public LoggingAspect(ILogger logger)
+ {
+ _logger = logger ?? LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger();
+ }
private void InitializeLogger(LoggingAttribute trigger)
{
@@ -85,15 +91,15 @@ private void InitializeLogger(LoggingAttribute trigger)
if (hasSamplingRate) Logger.UseSamplingRate(trigger.SamplingRate);
// Update logger reference after configuration changes
- _logger = Logger.GetPowertoolsLogger();
+ // _logger = Logger.GetPowertoolsLogger();
}
else if (_logger == null)
{
// Only get the logger if we don't already have it
- _logger = Logger.GetPowertoolsLogger();
+ // _logger = Logger.GetPowertoolsLogger();
}
// Fetch the current configuration
- _currentConfig = Logger.GetConfiguration();
+ _currentConfig = Logger.GetCurrentConfiguration();
// Set operational flags based on current configuration
_isDebug = _currentConfig.MinimumLogLevel <= LogLevel.Debug;
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
index 903a8850..ada06cb1 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
@@ -28,8 +28,8 @@ internal static class LoggingAspectFactory
///
/// The type of the class to be logged.
/// An instance of the LoggingAspect class.
- // public static object GetInstance(Type type)
- // {
- // return new LoggingAspect(PowertoolsConfigurations.Instance);
- // }
+ public static object GetInstance(Type type)
+ {
+ return new LoggingAspect(LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger());
+ }
}
\ 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 88cc32d2..f625bae6 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -39,12 +39,12 @@ internal sealed class PowertoolsLogger : ILogger
///
/// The current configuration
///
- private readonly PowertoolsLoggerConfiguration _currentConfig;
+ private readonly Func _currentConfig;
///
/// The system wrapper
///
- private readonly ISystemWrapper _systemWrapper;
+ private readonly Func _getSystemWrapper;
///
/// The current scope
@@ -56,15 +56,15 @@ internal sealed class PowertoolsLogger : ILogger
///
/// The name.
///
- /// The system wrapper.
+ /// The system wrapper.
public PowertoolsLogger(
string categoryName,
Func getCurrentConfig,
- ISystemWrapper systemWrapper)
+ Func getSystemWrapper)
{
_categoryName = categoryName;
- _currentConfig = getCurrentConfig();
- _systemWrapper = systemWrapper;
+ _currentConfig = getCurrentConfig;
+ _getSystemWrapper = getSystemWrapper;
}
///
@@ -95,9 +95,10 @@ internal void EndScope()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsEnabled(LogLevel logLevel)
{
+ var config = _currentConfig();
// If we have no explicit minimum level, use the default
- var effectiveMinLevel = _currentConfig.MinimumLogLevel != LogLevel.None
- ? _currentConfig.MinimumLogLevel
+ var effectiveMinLevel = config.MinimumLogLevel != LogLevel.None
+ ? config.MinimumLogLevel
: LoggingConstants.DefaultLogLevel;
// Log diagnostic info for Debug/Trace levels
@@ -127,18 +128,18 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
return;
}
- _systemWrapper.LogLine(LogEntryString(logLevel, state, exception, formatter));
+ _getSystemWrapper().LogLine(LogEntryString(logLevel, state, exception, formatter));
}
internal void LogLine(string message)
{
- _systemWrapper.LogLine(message);
+ _getSystemWrapper().LogLine(message);
}
internal string LogEntryString(LogLevel logLevel, TState state, Exception exception, Func formatter)
{
var logEntry = LogEntry(logLevel, state, exception, formatter);
- return _currentConfig.Serializer.Serialize(logEntry, typeof(object));
+ return _currentConfig().Serializer.Serialize(logEntry, typeof(object));
}
internal object LogEntry(LogLevel logLevel, TState state, Exception exception, Func formatter)
@@ -157,7 +158,7 @@ internal object LogEntry(LogLevel logLevel, TState state, Exception exce
: formatter(state, exception);
// Get log entry
- var logFormatter = _currentConfig.LogFormatter;
+ var logFormatter = _currentConfig().LogFormatter;
var logEntry = logFormatter is null
? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters)
: GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters);
@@ -212,13 +213,14 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
}
}
- logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString( _currentConfig.TimestampFormat ?? "o"));
- logEntry.TryAdd(_currentConfig.LogLevelKey, logLevel.ToString());
- logEntry.TryAdd(LoggingConstants.KeyService, _currentConfig.Service);
+ var config = _currentConfig();
+ logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString( config.TimestampFormat ?? "o"));
+ logEntry.TryAdd(config.LogLevelKey, logLevel.ToString());
+ logEntry.TryAdd(LoggingConstants.KeyService, config.Service);
logEntry.TryAdd(LoggingConstants.KeyLoggerName, _categoryName);
logEntry.TryAdd(LoggingConstants.KeyMessage, message);
- if (_currentConfig.SamplingRate > 0)
- logEntry.TryAdd(LoggingConstants.KeySamplingRate, _currentConfig.SamplingRate);
+ if (config.SamplingRate > 0)
+ logEntry.TryAdd(LoggingConstants.KeySamplingRate, config.SamplingRate);
// Use the AddExceptionDetails method instead of adding exception directly
if (exception != null)
@@ -244,15 +246,16 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
if (logFormatter is null)
return null;
+ var config = _currentConfig();
var logEntry = new LogEntry
{
Timestamp = timestamp,
Level = logLevel,
- Service = _currentConfig.Service,
+ Service = config.Service,
Name = _categoryName,
Message = message,
Exception = exception, // Keep this to maintain compatibility
- SamplingRate = _currentConfig.SamplingRate,
+ SamplingRate = config.SamplingRate,
};
var extraKeys = new Dictionary();
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index 663e3f05..986d6851 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -38,7 +38,7 @@ internal sealed class PowertoolsLoggerProvider : ILoggerProvider
///
/// The system wrapper
///
- private readonly ISystemWrapper _systemWrapper;
+ private ISystemWrapper _systemWrapper;
///
/// The loggers
@@ -56,17 +56,17 @@ internal sealed class PowertoolsLoggerProvider : ILoggerProvider
///
public PowertoolsLoggerProvider(IOptionsMonitor config,
IPowertoolsConfigurations powertoolsConfigurations,
- ISystemWrapper? systemWrapper = null)
+ ISystemWrapper systemWrapper = null)
{
- // Use custom system wrapper if provided through config
- var currentConfig = config.CurrentValue;
- _systemWrapper = currentConfig.LoggerOutput ?? systemWrapper ?? new SystemWrapper();
-
+ _currentConfig = config.CurrentValue;
+ _systemWrapper = systemWrapper;
_powertoolsConfigurations = powertoolsConfigurations;
- _currentConfig = currentConfig;
-
- _onChangeToken = config.OnChange(updatedConfig => _currentConfig = updatedConfig);
- ApplyPowertoolsConfig(_currentConfig);
+
+ _onChangeToken = config.OnChange(updatedConfig =>
+ {
+ _currentConfig = updatedConfig;
+ // No need to do anything else - the loggers get the config through GetCurrentConfig
+ });
}
///
@@ -79,107 +79,112 @@ public ILogger CreateLogger(string categoryName)
_powertoolsConfigurations.SetExecutionEnvironment(typeof(PowertoolsLogger));
return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger(name,
- () => _currentConfig,
- _systemWrapper));
- }
-
- internal PowertoolsLoggerConfiguration GetCurrentConfig()
- {
- var config = _currentConfig;
-
- ApplyPowertoolsConfig(config);
-
- return config;
+ GetCurrentConfig,
+ () => _systemWrapper));
}
- private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
+ internal PowertoolsLoggerConfiguration GetCurrentConfig() => _currentConfig;
+
+ public void UpdateConfiguration(PowertoolsLoggerConfiguration config)
{
- var logLevel = _powertoolsConfigurations.GetLogLevel(LogLevel.None);
- var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
- var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
-
- // Check for explicit config
- bool hasExplicitLevel = config.MinimumLogLevel != LogLevel.None;
-
- // Warn if Lambda log level doesn't match
- if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLogLevel < lambdaLogLevel)
- {
- _systemWrapper.LogLine(
- $"Current log level ({config.MinimumLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
- }
-
- // Set service from environment if not explicitly set
- if (string.IsNullOrEmpty(config.Service))
- {
- config.Service = _powertoolsConfigurations.Service;
- }
-
- // Set output case from environment if not explicitly set
- if (config.LoggerOutputCase == LoggerOutputCase.Default)
- {
- var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(config.LoggerOutputCase);
- config.LoggerOutputCase = loggerOutputCase;
- }
-
- // Set log level from environment ONLY if not explicitly set
- if (!hasExplicitLevel)
- {
- var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
- config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
- }
-
- config.XRayTraceId = _powertoolsConfigurations.XRayTraceId;
- config.LogEvent = _powertoolsConfigurations.LoggerLogEvent;
-
- // Configure the log level key based on output case
- config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
- config.LoggerOutputCase == LoggerOutputCase.PascalCase
- ? "LogLevel"
- : LoggingConstants.KeyLogLevel;
-
- // Handle sampling rate - BUT DON'T MODIFY MINIMUM LEVEL
- ProcessSamplingRate(config);
+ _currentConfig = config;
}
-
- private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
+
+ public void UpdateSystem(ISystemWrapper system)
{
- var samplingRate = config.SamplingRate > 0
- ? config.SamplingRate
- : _powertoolsConfigurations.LoggerSampleRate;
-
- samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLogLevel, _systemWrapper);
- config.SamplingRate = samplingRate;
-
- // Only notify if sampling is configured
- if (samplingRate > 0)
- {
- double sample = _systemWrapper.GetRandom();
-
- // Instead of changing log level, just indicate sampling status
- if (sample <= samplingRate)
- {
- _systemWrapper.LogLine(
- $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
- config.MinimumLogLevel = LogLevel.Debug;
- }
- }
+ if (system == null) return;
+
+ _systemWrapper = system;
}
- private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
- {
- if (samplingRate < 0 || samplingRate > 1)
- {
- if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
- {
- systemWrapper.LogLine(
- $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
- }
-
- return 0;
- }
-
- return samplingRate;
- }
+ // private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
+ // {
+ // var logLevel = _powertoolsConfigurations.GetLogLevel(LogLevel.None);
+ // var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
+ // var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
+ //
+ // // Check for explicit config
+ // bool hasExplicitLevel = config.MinimumLogLevel != LogLevel.None;
+ //
+ // // Warn if Lambda log level doesn't match
+ // if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLogLevel < lambdaLogLevel)
+ // {
+ // _systemWrapper.LogLine(
+ // $"Current log level ({config.MinimumLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
+ // }
+ //
+ // // Set service from environment if not explicitly set
+ // if (string.IsNullOrEmpty(config.Service))
+ // {
+ // config.Service = _powertoolsConfigurations.Service;
+ // }
+ //
+ // // Set output case from environment if not explicitly set
+ // if (config.LoggerOutputCase == LoggerOutputCase.Default)
+ // {
+ // var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(config.LoggerOutputCase);
+ // config.LoggerOutputCase = loggerOutputCase;
+ // }
+ //
+ // // Set log level from environment ONLY if not explicitly set
+ // if (!hasExplicitLevel)
+ // {
+ // var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
+ // config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
+ // }
+ //
+ // config.XRayTraceId = _powertoolsConfigurations.XRayTraceId;
+ // config.LogEvent = _powertoolsConfigurations.LoggerLogEvent;
+ //
+ // // Configure the log level key based on output case
+ // config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
+ // config.LoggerOutputCase == LoggerOutputCase.PascalCase
+ // ? "LogLevel"
+ // : LoggingConstants.KeyLogLevel;
+ //
+ // // Handle sampling rate - BUT DON'T MODIFY MINIMUM LEVEL
+ // ProcessSamplingRate(config);
+ // }
+ //
+ // private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
+ // {
+ // var samplingRate = config.SamplingRate > 0
+ // ? config.SamplingRate
+ // : _powertoolsConfigurations.LoggerSampleRate;
+ //
+ // samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLogLevel, _systemWrapper);
+ // config.SamplingRate = samplingRate;
+ //
+ // // Only notify if sampling is configured
+ // if (samplingRate > 0)
+ // {
+ // double sample = _systemWrapper.GetRandom();
+ //
+ // // Instead of changing log level, just indicate sampling status
+ // if (sample <= samplingRate)
+ // {
+ // _systemWrapper.LogLine(
+ // $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
+ // config.MinimumLogLevel = LogLevel.Debug;
+ // }
+ // }
+ // }
+ //
+ // private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
+ // {
+ // if (samplingRate < 0 || samplingRate > 1)
+ // {
+ // if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
+ // {
+ // systemWrapper.LogLine(
+ // $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
+ // }
+ //
+ // return 0;
+ // }
+ //
+ // return samplingRate;
+ // }
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
index fdeb3c97..d5f5b5ca 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs
@@ -24,7 +24,9 @@ public static partial class Logger
/// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor
public static void UseFormatter(ILogFormatter logFormatter)
{
- _currentConfig.LogFormatter = logFormatter;
+ Configure(config => {
+ config.LogFormatter = logFormatter;
+ });
}
///
@@ -32,6 +34,8 @@ public static void UseFormatter(ILogFormatter logFormatter)
///
public static void UseDefaultFormatter()
{
- _currentConfig.LogFormatter = null;
+ Configure(config => {
+ config.LogFormatter = null;
+ });
}
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index 23f72614..d6398a08 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -15,8 +15,9 @@
using System;
using System.Text.Json;
-using System.Threading;
using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Internal;
+using AWS.Lambda.Powertools.Logging.Internal.Helpers;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging;
@@ -26,99 +27,72 @@ namespace AWS.Lambda.Powertools.Logging;
///
public static partial class Logger
{
- // Use Lazy for thread-safe initialization
- private static Lazy _factoryLazy;
- private static Lazy _defaultLoggerLazy;
+ private static readonly object _lock = new object();
- // Static constructor to ensure initialization
+ // Static constructor to ensure initialization happens before first use
static Logger()
{
- // Initialize with default configuration (ensures we never have null fields)
- InitializeWithDefaults();
+ LoggerInstance = GetPowertoolsLogger();
+ // Factory and logger will be lazily initialized when first accessed
}
- // Properties to access the lazy-initialized instances
- private static ILoggerFactory Factory => _factoryLazy.Value;
- private static ILogger LoggerInstance => _defaultLoggerLazy.Value;
+ // Get the current logger instance
+ private static ILogger LoggerInstance;
- // Add this field to the Logger class
- private static PowertoolsLoggerConfiguration _currentConfig;
-
- // Initialize with default settings
- private static void InitializeWithDefaults()
- {
- _currentConfig = new PowertoolsLoggerConfiguration();
-
- // Create default factory with minimal configuration
- _factoryLazy = new Lazy(() =>
- PowertoolsLoggerFactory.Create(_currentConfig));
-
- _defaultLoggerLazy = new Lazy(() =>
- Factory.CreatePowertoolsLogger());
- }
-
- // Allow manual configuration using options
- internal static void Configure(Action configureOptions)
- {
- var options = new PowertoolsLoggerConfiguration();
- configureOptions(options);
- Configure(options);
- }
-
- // Configure with existing factory
+ ///
+ /// Configure with an existing logger factory
+ ///
+ /// The factory to use
internal static void Configure(ILoggerFactory loggerFactory)
{
- Interlocked.Exchange(ref _factoryLazy,
- new Lazy(() => loggerFactory));
-
- Interlocked.Exchange(ref _defaultLoggerLazy,
- new Lazy(() => Factory.CreatePowertoolsLogger()));
+ if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory));
+ LoggerFactoryHolder.SetFactory(loggerFactory);
}
- // Directly configure from a PowertoolsLoggerConfiguration
- internal static void Configure(PowertoolsLoggerConfiguration options)
+ ///
+ /// Configure using a configuration action
+ ///
+ ///
+ internal static void Configure(Action configure)
{
- if (options == null) throw new ArgumentNullException(nameof(options));
-
- // Store current config
- _currentConfig = options;
-
- // Update factory and logger
- Interlocked.Exchange(ref _factoryLazy,
- new Lazy(() => PowertoolsLoggerFactory.Create(_currentConfig)));
-
- Interlocked.Exchange(ref _defaultLoggerLazy,
- new Lazy(() => Factory.CreatePowertoolsLogger()));
+ lock (_lock)
+ {
+ var config = GetCurrentConfiguration();
+ configure(config);
+ PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config);
+ }
}
-
- // Get the current configuration
- internal static PowertoolsLoggerConfiguration GetConfiguration()
+
+ public static PowertoolsLoggerConfiguration GetCurrentConfiguration()
{
- // Ensure logger is initialized
- _ = LoggerInstance;
-
- return _currentConfig;
+ return PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
}
- // Get a logger for a specific category
- internal static ILogger GetLogger() => GetLogger(typeof(T).Name);
-
- internal static ILogger GetLogger(string category) => Factory.CreateLogger(category);
-
- internal static ILogger GetPowertoolsLogger() => Factory.CreatePowertoolsLogger();
+ // ///
+ // /// Get a logger for a specific type
+ // ///
+ // /// The type to create logger for
+ // /// A configured logger
+ // internal static ILogger GetLogger() => GetLogger(typeof(T).Name);
+ //
+ // ///
+ // /// Get a logger for a specific category
+ // ///
+ // /// The category name
+ // /// A configured logger
+ // internal static ILogger GetLogger(string category)
+ // {
+ // return LoggerFactoryHolder.GetOrCreateFactory().CreateLogger(category);
+ // }
+ //
///
- /// Sets a custom output for the static logger.
- /// Useful for testing to redirect logs to a test output.
+ /// Get the Powertools logger instance
///
- /// The custom output implementation
- public static void UseOutput(ISystemWrapper loggerOutput)
+ /// The configured Powertools logger
+ internal static ILogger GetPowertoolsLogger()
{
- if (loggerOutput == null)
- throw new ArgumentNullException(nameof(loggerOutput));
-
- _currentConfig.LoggerOutput = loggerOutput;
- Configure(_currentConfig);
+ return LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger();
}
///
@@ -127,18 +101,20 @@ public static void UseOutput(ISystemWrapper loggerOutput)
/// The case to use for the output
public static void UseOutputCase(LoggerOutputCase outputCase)
{
- _currentConfig.LoggerOutputCase = outputCase;
- Configure(_currentConfig);
+ Configure(config => {
+ config.LoggerOutputCase = outputCase;
+ });
}
-
+
///
/// Configures the minimum log level
///
/// The minimum log level to display
public static void UseMinimumLogLevel(LogLevel logLevel)
{
- _currentConfig.MinimumLogLevel = logLevel;
- Configure(_currentConfig);
+ Configure(config => {
+ config.MinimumLogLevel = logLevel;
+ });
}
///
@@ -149,9 +125,10 @@ public static void UseServiceName(string serviceName)
{
if (string.IsNullOrEmpty(serviceName))
throw new ArgumentException("Service name cannot be null or empty", nameof(serviceName));
-
- _currentConfig.Service = serviceName;
- Configure(_currentConfig);
+
+ Configure(config => {
+ config.Service = serviceName;
+ });
}
///
@@ -162,9 +139,10 @@ public static void UseSamplingRate(double samplingRate)
{
if (samplingRate < 0 || samplingRate > 1)
throw new ArgumentOutOfRangeException(nameof(samplingRate), "Sampling rate must be between 0 and 1");
-
- _currentConfig.SamplingRate = samplingRate;
- Configure(_currentConfig);
+
+ Configure(config => {
+ config.SamplingRate = samplingRate;
+ });
}
///
@@ -182,11 +160,9 @@ public static void UseLogBuffering(LogBufferingOptions logBuffering)
if (logBuffering == null)
throw new ArgumentNullException(nameof(logBuffering));
- // Update the current configuration
- _currentConfig.LogBuffering = logBuffering;
-
- // Reconfigure to apply changes
- Configure(_currentConfig);
+ Configure(config => {
+ config.LogBuffering = logBuffering;
+ });
}
#if NET8_0_OR_GREATER
@@ -198,15 +174,25 @@ public static void UseJsonOptions(JsonSerializerOptions jsonOptions)
{
if (jsonOptions == null)
throw new ArgumentNullException(nameof(jsonOptions));
-
- // Update the current configuration
- _currentConfig.JsonOptions = jsonOptions;
+
+ Configure(config => {
+ config.JsonOptions = jsonOptions;
+ });
}
#endif
- // For testing purposes
+ // Add to Logger.cs
+ public static ISystemWrapper UseStringWriter()
+ {
+ return PowertoolsLoggerTestHelpers.EnableTestMode();
+
+ }
+
+ ///
+ /// Reset the logger for testing
+ ///
internal static void Reset()
{
- InitializeWithDefaults();
+ LoggerFactoryHolder.Reset();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
index e18bc22b..06f6657b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs
@@ -46,12 +46,6 @@ public PowertoolsLoggerBuilder WithOutputCase(LoggerOutputCase outputCase)
_configuration.LoggerOutputCase = outputCase;
return this;
}
-
- public PowertoolsLoggerBuilder WithOutput(ISystemWrapper output)
- {
- _configuration.LoggerOutput = output;
- return this;
- }
public PowertoolsLoggerBuilder WithFormatter(ILogFormatter formatter)
{
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index e713eb0b..f6a2ea5b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -63,11 +63,6 @@ public class PowertoolsLoggerConfiguration : IOptions
internal string LogLevelKey { get; set; } = "level";
- ///
- /// Custom output logger to use instead of Console
- ///
- public ISystemWrapper? LoggerOutput { get; set; }
-
///
/// Custom log formatter to use for formatting log entries
///
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
new file mode 100644
index 00000000..70e2f135
--- /dev/null
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Internal;
+using AWS.Lambda.Powertools.Logging.Internal.Helpers;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace AWS.Lambda.Powertools.Logging;
+
+///
+/// Extension methods for configuring the Powertools logger
+///
+public static class PowertoolsLoggingBuilderExtensions
+{
+ private static readonly ConcurrentBag AllProviders = new();
+ private static readonly object _lock = new();
+ private static PowertoolsLoggerConfiguration _currentConfig = new();
+
+ public static void UpdateConfiguration(PowertoolsLoggerConfiguration config)
+ {
+ lock (_lock)
+ {
+ // Update the shared configuration
+ _currentConfig = config;
+
+ // Notify all providers about the change
+ foreach (var provider in AllProviders)
+ {
+ provider.UpdateConfiguration(config);
+ }
+ }
+ }
+
+ public static PowertoolsLoggerConfiguration GetCurrentConfiguration()
+ {
+ lock (_lock)
+ {
+ // Return a copy to prevent external modification
+ return new PowertoolsLoggerConfiguration
+ {
+ Service = _currentConfig.Service,
+ SamplingRate = _currentConfig.SamplingRate,
+ MinimumLogLevel = _currentConfig.MinimumLogLevel,
+ LoggerOutputCase = _currentConfig.LoggerOutputCase,
+ JsonOptions = _currentConfig.JsonOptions,
+ TimestampFormat = _currentConfig.TimestampFormat,
+ LogFormatter = _currentConfig.LogFormatter,
+ LogLevelKey = _currentConfig.LogLevelKey,
+ LogBuffering = _currentConfig.LogBuffering
+
+ };
+ }
+ }
+
+ public static ILoggingBuilder AddPowertoolsLogger(
+ this ILoggingBuilder builder)
+ {
+ builder.AddConfiguration();
+
+ // Register ISystemWrapper if not already registered
+ builder.Services.TryAddSingleton(provider =>
+ {
+ // Check if there's a pending mock system first
+ var mockSystem = PowertoolsLoggerTestHelpers.GetSystemWrapper();
+ return mockSystem ?? new SystemWrapper();
+ });
+
+ builder.Services.TryAddSingleton();
+ // Register IPowertoolsConfigurations with all its dependencies
+ builder.Services.TryAddSingleton(sp =>
+ new PowertoolsConfigurations(sp.GetRequiredService()));
+
+ // Register the regular provider
+ builder.Services.TryAddEnumerable(
+ ServiceDescriptor.Singleton(provider =>
+ {
+ var powertoolsConfigurations = provider.GetRequiredService();
+ var systemWrapper = provider.GetRequiredService();
+
+ var loggerProvider = new PowertoolsLoggerProvider(
+ new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration), powertoolsConfigurations,
+ systemWrapper);
+ lock (_lock)
+ {
+ AllProviders.Add(loggerProvider);
+ }
+
+ return loggerProvider;
+ }));
+
+ builder.Services.ConfigureOptions();
+
+ LoggerProviderOptions.RegisterProviderOptions
+ (builder.Services);
+
+ return builder;
+ }
+
+ ///
+ /// Adds the Powertools logger to the logging builder.
+ ///
+ public static ILoggingBuilder AddPowertoolsLogger(
+ this ILoggingBuilder builder,
+ Action configure)
+ {
+ // Add configuration
+ builder.AddPowertoolsLogger();
+
+ // Create initial configuration
+ var options = new PowertoolsLoggerConfiguration();
+ configure(options);
+
+ // IMPORTANT: Set the minimum level directly on the builder
+ if (options.MinimumLogLevel != LogLevel.None)
+ {
+ builder.SetMinimumLevel(options.MinimumLogLevel);
+ }
+
+ builder.Services.Configure(configure);
+
+ UpdateConfiguration(options);
+
+ // If buffering is enabled, register buffer providers
+ if (options?.LogBuffering?.Enabled == true)
+ {
+ // Add a filter for the buffer provider
+ builder.AddFilter(
+ null,
+ options.LogBuffering.BufferAtLogLevel);
+
+ // Register the inner provider factory
+ builder.Services.AddSingleton(sp =>
+ new BufferingLoggerProvider(
+ // We need to create a PowertoolsLoggerProvider here
+ new PowertoolsLoggerProvider(
+ new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration),
+ sp.GetRequiredService(),
+ sp.GetRequiredService()
+ ),
+ new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration)
+ )
+ );
+ }
+
+ return builder;
+ }
+
+ internal static void UpdateSystemInAllProviders(ISystemWrapper system)
+ {
+ if (system == null) return;
+
+ lock (_lock)
+ {
+ foreach (var provider in AllProviders)
+ {
+ provider.UpdateSystem(system);
+ }
+ }
+ }
+
+ private class ConfigureLoggingOptions : IConfigureOptions
+ {
+ private readonly IPowertoolsConfigurations _configurations;
+ private readonly ISystemWrapper _systemWrapper;
+
+ public ConfigureLoggingOptions(IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
+ {
+ _configurations = configurations;
+ _systemWrapper = systemWrapper;
+ }
+
+ public void Configure(LoggerFilterOptions options)
+ {
+ // This runs when IOptions is resolved
+ LoggerFactoryHolder.ConfigureFromEnvironment(_configurations,_systemWrapper);
+ }
+ }
+
+ private class TrackedOptionsMonitor : IOptionsMonitor
+ {
+ private PowertoolsLoggerConfiguration _config;
+ private readonly Action _updateCallback;
+ private readonly List> _listeners = new();
+
+ public TrackedOptionsMonitor(
+ PowertoolsLoggerConfiguration config,
+ Action updateCallback)
+ {
+ _config = config;
+ _updateCallback = updateCallback;
+ }
+
+ public PowertoolsLoggerConfiguration CurrentValue => _config;
+
+ public IDisposable OnChange(Action listener)
+ {
+ _listeners.Add(listener);
+ return new ListenerDisposable(_listeners, listener);
+ }
+
+ public PowertoolsLoggerConfiguration Get(string? name) => _config;
+
+ private class ListenerDisposable : IDisposable
+ {
+ private readonly List> _listeners;
+ private readonly Action _listener;
+
+ public ListenerDisposable(
+ List> listeners,
+ Action listener)
+ {
+ _listeners = listeners;
+ _listener = listener;
+ }
+
+ public void Dispose()
+ {
+ _listeners.Remove(_listener);
+ }
+ }
+ }
+}
\ No newline at end of file
From e7dc7049a2b1922c9c185e42071f8a7ee53cb30a Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Sat, 29 Mar 2025 10:09:01 +0000
Subject: [PATCH 21/49] fix buffering
---
.../Buffer/BufferingLoggerProvider.cs | 50 ++++++--------
.../Internal/Helpers/LoggerFactoryHelper.cs | 44 -------------
.../Internal/PowertoolsLoggerProvider.cs | 6 +-
.../AWS.Lambda.Powertools.Logging/Logger.cs | 7 --
.../PowertoolsLoggingBuilderExtensions.cs | 65 +++++++++++++------
5 files changed, 69 insertions(+), 103 deletions(-)
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
index c3b099dc..3f5e89e2 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
@@ -15,6 +15,7 @@
using System;
using System.Collections.Concurrent;
+using AWS.Lambda.Powertools.Common;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -24,53 +25,30 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
/// Logger provider that supports buffering logs
///
[ProviderAlias("PowertoolsBuffering")]
-internal class BufferingLoggerProvider : ILoggerProvider
+internal class BufferingLoggerProvider : PowertoolsLoggerProvider
{
- private readonly ILoggerProvider _innerProvider;
private readonly ConcurrentDictionary _loggers = new();
- private readonly IDisposable? _onChangeToken;
- private PowertoolsLoggerConfiguration _currentConfig;
public BufferingLoggerProvider(
- ILoggerProvider innerProvider,
- IOptionsMonitor config)
+ IOptionsMonitor config,
+ IPowertoolsConfigurations powertoolsConfigurations,
+ ISystemWrapper systemWrapper)
+ : base(config, powertoolsConfigurations, systemWrapper)
{
- _innerProvider = innerProvider ?? throw new ArgumentNullException(nameof(innerProvider));
- _currentConfig = config.CurrentValue;
-
- _onChangeToken = config.OnChange(updatedConfig =>
- {
- _currentConfig = updatedConfig;
- // No need to do anything else - the loggers get the config through GetCurrentConfig
- });
// Register with the buffer manager
LogBufferManager.RegisterProvider(this);
}
- public ILogger CreateLogger(string categoryName)
+ public override ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(
categoryName,
name => new PowertoolsBufferingLogger(
- _innerProvider.CreateLogger(name),
+ base.CreateLogger(name), // Use the parent's logger creation
GetCurrentConfig,
name));
}
- internal PowertoolsLoggerConfiguration GetCurrentConfig() => _currentConfig;
-
- public void Dispose()
- {
- // Flush all buffers before disposing
- foreach (var logger in _loggers.Values)
- {
- logger.FlushBuffer();
- }
-
- _innerProvider.Dispose();
- _loggers.Clear();
- }
-
///
/// Flush all buffered logs
///
@@ -103,4 +81,16 @@ public void ClearCurrentBuffer()
logger.ClearCurrentInvocation();
}
}
+
+ public override void Dispose()
+ {
+ // Flush all buffers before disposing
+ foreach (var logger in _loggers.Values)
+ {
+ logger.FlushBuffer();
+ }
+
+ _loggers.Clear();
+ base.Dispose();
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
index a4c64a65..e08cfdd1 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
@@ -1,6 +1,3 @@
-using System;
-using AWS.Lambda.Powertools.Common;
-using AWS.Lambda.Powertools.Common.Tests;
using Microsoft.Extensions.Logging;
namespace AWS.Lambda.Powertools.Logging.Internal.Helpers;
@@ -38,45 +35,4 @@ public static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfigura
return factory;
}
-}
-
-// Add to a new TestHelpers.cs file
-public static class PowertoolsLoggerTestHelpers
-{
- private static readonly object _lock = new();
- private static ISystemWrapper _systemWrapper;
-
- static PowertoolsLoggerTestHelpers()
- {
- _systemWrapper = null;
- }
-
- // Call this at the beginning of your test
- public static TestLoggerOutput EnableTestMode()
- {
- var system = new TestLoggerOutput();
- _systemWrapper = system;
- PowertoolsLoggingBuilderExtensions.UpdateSystemInAllProviders(system);
- return system;
- }
-
- public static void UseCustomSystem(ISystemWrapper system)
- {
- if (system == null) throw new ArgumentNullException(nameof(system));
- lock (_lock)
- {
- // Store the mock system for later use when providers are created
- _systemWrapper = system;
- // Update all providers to use the mock system
- PowertoolsLoggingBuilderExtensions.UpdateSystemInAllProviders(system);
- }
- }
-
- internal static ISystemWrapper GetSystemWrapper()
- {
- lock (_lock)
- {
- return _systemWrapper;
- }
- }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index 986d6851..e0caecb2 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -28,7 +28,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
///
///
[ProviderAlias("PowertoolsLogger")]
-internal sealed class PowertoolsLoggerProvider : ILoggerProvider
+internal class PowertoolsLoggerProvider : ILoggerProvider
{
///
/// The powertools configurations
@@ -74,7 +74,7 @@ public PowertoolsLoggerProvider(IOptionsMonitor c
///
/// The category name for messages produced by the logger.
/// The instance of that was created.
- public ILogger CreateLogger(string categoryName)
+ public virtual ILogger CreateLogger(string categoryName)
{
_powertoolsConfigurations.SetExecutionEnvironment(typeof(PowertoolsLogger));
@@ -189,7 +189,7 @@ public void UpdateSystem(ISystemWrapper system)
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
- public void Dispose()
+ public virtual void Dispose()
{
_loggers.Clear();
_onChangeToken?.Dispose();
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index d6398a08..ff9ae0fa 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -180,13 +180,6 @@ public static void UseJsonOptions(JsonSerializerOptions jsonOptions)
});
}
#endif
-
- // Add to Logger.cs
- public static ISystemWrapper UseStringWriter()
- {
- return PowertoolsLoggerTestHelpers.EnableTestMode();
-
- }
///
/// Reset the logger for testing
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
index 70e2f135..15d480dc 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
@@ -63,12 +63,14 @@ public static ILoggingBuilder AddPowertoolsLogger(
builder.AddConfiguration();
// Register ISystemWrapper if not already registered
- builder.Services.TryAddSingleton(provider =>
- {
- // Check if there's a pending mock system first
- var mockSystem = PowertoolsLoggerTestHelpers.GetSystemWrapper();
- return mockSystem ?? new SystemWrapper();
- });
+ // builder.Services.TryAddSingleton(provider =>
+ // {
+ // // Check if there's a pending mock system first
+ // var mockSystem = PowertoolsLoggerTestFixture.GetSystemWrapper();
+ // return mockSystem ?? new SystemWrapper();
+ // });
+
+ builder.Services.TryAddSingleton();
builder.Services.TryAddSingleton();
// Register IPowertoolsConfigurations with all its dependencies
@@ -120,7 +122,7 @@ public static ILoggingBuilder AddPowertoolsLogger(
{
builder.SetMinimumLevel(options.MinimumLogLevel);
}
-
+
builder.Services.Configure(configure);
UpdateConfiguration(options);
@@ -133,18 +135,27 @@ public static ILoggingBuilder AddPowertoolsLogger(
null,
options.LogBuffering.BufferAtLogLevel);
- // Register the inner provider factory
- builder.Services.AddSingleton(sp =>
- new BufferingLoggerProvider(
- // We need to create a PowertoolsLoggerProvider here
- new PowertoolsLoggerProvider(
- new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration),
- sp.GetRequiredService(),
- sp.GetRequiredService()
- ),
- new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration)
- )
- );
+ // Register the buffer provider as an enumerable service
+ // Using singleton to ensure it's properly tracked
+ builder.Services.TryAddEnumerable(
+ ServiceDescriptor.Singleton(provider =>
+ {
+ var powertoolsConfigurations = provider.GetRequiredService();
+ var systemWrapper = provider.GetRequiredService();
+
+ var bufferingProvider = new BufferingLoggerProvider(
+ new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration),
+ powertoolsConfigurations,
+ systemWrapper
+ );
+
+ lock (_lock)
+ {
+ AllProviders.Add(bufferingProvider);
+ }
+
+ return bufferingProvider;
+ }));
}
return builder;
@@ -163,6 +174,22 @@ internal static void UpdateSystemInAllProviders(ISystemWrapper system)
}
}
+ ///
+ /// Resets all providers and clears the configuration.
+ /// This is useful for testing purposes to ensure a clean state.
+ ///
+ internal static void ResetAllProviders()
+ {
+ lock (_lock)
+ {
+ // Clear the provider collection
+ AllProviders.Clear();
+
+ // Reset the current configuration to default
+ _currentConfig = new PowertoolsLoggerConfiguration();
+ }
+ }
+
private class ConfigureLoggingOptions : IConfigureOptions
{
private readonly IPowertoolsConfigurations _configurations;
From c43c165e03dc7f3c32ea3e0fb54dd51277ca259d Mon Sep 17 00:00:00 2001
From: Henrique <999396+hjgraca@users.noreply.github.com>
Date: Mon, 31 Mar 2025 23:37:12 +0100
Subject: [PATCH 22/49] refactor: enhance logger configuration and output
handling. Fix tests
---
.../Core/TestLoggerOutput.cs | 22 +-
.../Buffer/BufferingLoggerProvider.cs | 30 +-
.../Internal/Buffer/Logger.Buffer.cs | 1 +
.../Internal/Converters/ByteArrayConverter.cs | 27 +-
.../Internal/Helpers/LoggerFactoryHelper.cs | 3 +
.../Internal/LoggerFactoryHolder.cs | 178 ++----
.../Internal/LoggingAspect.cs | 19 +-
.../Internal/LoggingAspectFactory.cs | 1 +
.../Internal/PowertoolsLogger.cs | 39 +-
.../Internal/PowertoolsLoggerProvider.cs | 240 ++++----
.../AWS.Lambda.Powertools.Logging/Logger.cs | 62 +-
.../LoggingAttribute.cs | 14 +-
.../PowertoolsLoggerConfiguration.cs | 46 +-
.../PowertoolsLoggingBuilderExtensions.cs | 120 +---
.../PowertoolsLoggingSerializer.cs | 37 +-
.../Attributes/LoggerAspectTests.cs | 278 +++++----
.../Attributes/LoggingAttributeTest.cs | 296 +++++-----
.../Attributes/ServiceTests.cs | 50 ++
.../Formatter/LogFormatterTest.cs | 83 +--
.../Handlers/ExceptionFunctionHandlerTests.cs | 2 +-
.../Handlers/TestHandlers.cs | 11 -
.../PowertoolsLoggerTest.cs | 558 +++++++++---------
.../PowertoolsLambdaSerializerTests.cs | 34 +-
.../PowertoolsLoggingSerializerTests.cs | 268 +++++++--
.../PowertoolsConfigurationExtensionsTests.cs | 16 +-
.../Utilities/PowertoolsLoggerHelpersTests.cs | 21 +-
.../Utilities/SystemWrapperMock.cs | 68 ---
27 files changed, 1289 insertions(+), 1235 deletions(-)
create mode 100644 libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs
delete mode 100644 libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs
diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
index 9a64c881..96974d69 100644
--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/TestLoggerOutput.cs
@@ -12,7 +12,7 @@ public class TestLoggerOutput : ISystemWrapper
///
/// Buffer for all the log messages written to the logger.
///
- public StringBuilder Buffer { get; } = new StringBuilder();
+ private readonly StringBuilder _outputBuffer = new StringBuilder();
///
/// Logs the specified value.
@@ -20,8 +20,7 @@ public class TestLoggerOutput : ISystemWrapper
///
public void Log(string value)
{
- Buffer.Append(value);
- Console.Write(value);
+ _outputBuffer.Append(value);
}
///
@@ -29,8 +28,7 @@ public void Log(string value)
///
public void LogLine(string value)
{
- Buffer.AppendLine(value);
- Console.WriteLine(value);
+ _outputBuffer.AppendLine(value);
}
///
@@ -46,15 +44,15 @@ public double GetRandom()
///
public void SetOut(TextWriter writeTo)
{
- Console.SetOut(writeTo);
}
-
- ///
- /// Overrides the ToString method to return the buffer as a string.
- ///
- ///
+
+ public void Clear()
+ {
+ _outputBuffer.Clear();
+ }
+
public override string ToString()
{
- return Buffer.ToString();
+ return _outputBuffer.ToString();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
index 3f5e89e2..2e0ce5fa 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.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
@@ -28,27 +28,27 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
internal class BufferingLoggerProvider : PowertoolsLoggerProvider
{
private readonly ConcurrentDictionary _loggers = new();
-
+ private readonly IPowertoolsConfigurations _powertoolsConfigurations;
+
public BufferingLoggerProvider(
- IOptionsMonitor config,
- IPowertoolsConfigurations powertoolsConfigurations,
- ISystemWrapper systemWrapper)
- : base(config, powertoolsConfigurations, systemWrapper)
+ PowertoolsLoggerConfiguration config,
+ IPowertoolsConfigurations powertoolsConfigurations)
+ : base(config, powertoolsConfigurations)
{
// Register with the buffer manager
LogBufferManager.RegisterProvider(this);
}
-
+
public override ILogger CreateLogger(string categoryName)
{
return _loggers.GetOrAdd(
- categoryName,
+ categoryName,
name => new PowertoolsBufferingLogger(
base.CreateLogger(name), // Use the parent's logger creation
GetCurrentConfig,
name));
}
-
+
///
/// Flush all buffered logs
///
@@ -59,7 +59,7 @@ public void FlushBuffers()
logger.FlushBuffer();
}
}
-
+
///
/// Clear all buffered logs
///
@@ -70,7 +70,7 @@ public void ClearBuffers()
logger.ClearBuffer();
}
}
-
+
///
/// Clear buffered logs for the current invocation only
///
@@ -81,7 +81,7 @@ public void ClearCurrentBuffer()
logger.ClearCurrentInvocation();
}
}
-
+
public override void Dispose()
{
// Flush all buffers before disposing
@@ -89,7 +89,7 @@ public override void Dispose()
{
logger.FlushBuffer();
}
-
+
_loggers.Clear();
base.Dispose();
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs
index 9e715c55..f3739651 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs
@@ -14,6 +14,7 @@
* permissions and limitations under the License.
*/
+using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
namespace AWS.Lambda.Powertools.Logging;
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs
index b6d7120d..ab709bf9 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs
@@ -34,7 +34,13 @@ internal class ByteArrayConverter : JsonConverter
///
public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
- throw new NotSupportedException("Deserializing ByteArray is not allowed");
+ if (reader.TokenType == JsonTokenType.Null)
+ return null;
+
+ if (reader.TokenType == JsonTokenType.String)
+ return Convert.FromBase64String(reader.GetString()!);
+
+ throw new JsonException("Expected string value for byte array");
}
///
@@ -43,22 +49,15 @@ public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS
/// The unicode JsonWriter.
/// The byte array.
/// The JsonSerializer options.
- public override void Write(Utf8JsonWriter writer, byte[] values, JsonSerializerOptions options)
+ public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options)
{
- if (values == null)
+ if (value == null)
{
writer.WriteNullValue();
+ return;
}
- else
- {
- writer.WriteStartArray();
-
- foreach (var value in values)
- {
- writer.WriteNumberValue(value);
- }
-
- writer.WriteEndArray();
- }
+
+ string base64 = Convert.ToBase64String(value);
+ writer.WriteStringValue(base64);
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
index e08cfdd1..e8dacf4e 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs
@@ -27,6 +27,9 @@ public static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfigura
config.LogFormatter = configuration.LogFormatter;
config.LogLevelKey = configuration.LogLevelKey;
config.LogBuffering = configuration.LogBuffering;
+ config.LogEvent = configuration.LogEvent;
+ config.LogOutput = configuration.LogOutput;
+ config.XRayTraceId = configuration.XRayTraceId;
});
});
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs
index bde8860f..9299b44b 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs
@@ -29,152 +29,68 @@ internal static class LoggerFactoryHolder
private static readonly object _lock = new object();
private static bool _isConfigured = false;
+ private static LogLevel _currentFilterLevel = LogLevel.Information;
+
///
- /// Gets or creates the shared logger factory
+ /// Updates the filter log level at runtime
///
- public static ILoggerFactory GetOrCreateFactory()
+ /// The new minimum log level
+ public static void UpdateFilterLogLevel(LogLevel logLevel)
{
lock (_lock)
{
- if (_factory == null)
+ // Only reset if level actually changes
+ if (_currentFilterLevel != logLevel)
{
- _factory = LoggerFactory.Create(builder => builder.AddPowertoolsLogger());
+ _currentFilterLevel = logLevel;
+
+ if (_factory != null)
+ {
+ try { _factory.Dispose(); } catch { /* Ignore */ }
+ _factory = null;
+ }
}
- return _factory;
}
}
- public static void SetFactory(ILoggerFactory factory)
- {
- if (factory == null) throw new ArgumentNullException(nameof(factory));
- lock (_lock)
- {
- _factory = factory;
- _isConfigured = true;
- }
- }
-
///
- /// Automatically called when GetOrCreateFactory is used
- ///
- public static void ConfigureFromEnvironment(IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
- {
- // Only configure once
- if (_isConfigured) return;
-
- // Create initial configuration
- var config = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
-
- // Apply environment configuration if available
- if (configurations != null)
- {
- ApplyPowertoolsConfig(config, configurations, systemWrapper);
- PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config);
- }
-
- _isConfigured = true;
- }
-
- ///
- /// Apply Powertools configuration from environment variables to the logger configuration
- ///
- private static void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config,
- IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
- {
- var logLevel = configurations.GetLogLevel(LogLevel.None);
- var lambdaLogLevel = configurations.GetLambdaLogLevel();
- var lambdaLogLevelEnabled = configurations.LambdaLogLevelEnabled();
-
- // Check for explicit config
- bool hasExplicitLevel = config.MinimumLogLevel != LogLevel.None;
-
- // Warn if Lambda log level doesn't match
- if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLogLevel < lambdaLogLevel)
- {
- systemWrapper.LogLine(
- $"Current log level ({config.MinimumLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
- }
-
- // Set service from environment if not explicitly set
- if (string.IsNullOrEmpty(config.Service))
- {
- config.Service = configurations.Service;
- }
-
- // Set output case from environment if not explicitly set
- if (config.LoggerOutputCase == LoggerOutputCase.Default)
- {
- var loggerOutputCase = configurations.GetLoggerOutputCase(config.LoggerOutputCase);
- config.LoggerOutputCase = loggerOutputCase;
- }
-
- // Set log level from environment ONLY if not explicitly set
- if (!hasExplicitLevel)
- {
- var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
- config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
- }
-
- config.XRayTraceId = configurations.XRayTraceId;
- config.LogEvent = configurations.LoggerLogEvent;
-
- // Configure the log level key based on output case
- config.LogLevelKey = configurations.LambdaLogLevelEnabled() &&
- config.LoggerOutputCase == LoggerOutputCase.PascalCase
- ? "LogLevel"
- : LoggingConstants.KeyLogLevel;
-
- ProcessSamplingRate(config, configurations, systemWrapper);
- }
-
- ///
- /// Process sampling rate configuration
+ /// Gets or creates the shared logger factory
///
- private static void ProcessSamplingRate(PowertoolsLoggerConfiguration config, IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
+ public static ILoggerFactory GetOrCreateFactory()
{
- var samplingRate = config.SamplingRate > 0
- ? config.SamplingRate
- : configurations.LoggerSampleRate;
-
- samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLogLevel, systemWrapper);
- config.SamplingRate = samplingRate;
-
- // Only notify if sampling is configured
- if (samplingRate > 0)
+ lock (_lock)
{
- double sample = systemWrapper.GetRandom();
-
- // Instead of changing log level, just indicate sampling status
- if (sample <= samplingRate)
+ if (_factory == null)
{
- systemWrapper.LogLine(
- $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
- config.MinimumLogLevel = LogLevel.Debug;
+ var config = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
+ // Use current filter level or level from config
+ _currentFilterLevel = config.MinimumLogLevel != LogLevel.None
+ ? config.MinimumLogLevel
+ : _currentFilterLevel;
+
+ _factory = LoggerFactory.Create(builder =>
+ {
+ builder.AddPowertoolsLogger();
+
+ // Correctly configure the filter
+ builder.AddFilter(null, _currentFilterLevel);
+ });
}
+ return _factory;
}
}
- ///
- /// Validate sampling rate
- ///
- private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
+ public static void SetFactory(ILoggerFactory factory)
{
- if (samplingRate < 0 || samplingRate > 1)
+ if (factory == null) throw new ArgumentNullException(nameof(factory));
+ lock (_lock)
{
- if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
- {
- systemWrapper.LogLine(
- $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
- }
-
- return 0;
+ _factory = factory;
+ _isConfigured = true;
}
-
- return samplingRate;
}
-
-
///
/// Resets the factory holder for testing
///
@@ -182,8 +98,22 @@ internal static void Reset()
{
lock (_lock)
{
- var oldFactory = Interlocked.Exchange(ref _factory, null);
- oldFactory?.Dispose();
+ // Dispose the old factory if it exists
+ if (_factory != null)
+ {
+ try
+ {
+ _factory.Dispose();
+ }
+ catch
+ {
+ // Ignore disposal errors
+ }
+
+ _factory = null;
+ }
+
+ _currentFilterLevel = LogLevel.None;
_isConfigured = false;
}
}
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
index 0f301f9e..415547fd 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs
@@ -90,14 +90,11 @@ private void InitializeLogger(LoggingAttribute trigger)
if (hasOutputCase) Logger.UseOutputCase(trigger.LoggerOutputCase);
if (hasSamplingRate) Logger.UseSamplingRate(trigger.SamplingRate);
- // Update logger reference after configuration changes
- // _logger = Logger.GetPowertoolsLogger();
- }
- else if (_logger == null)
- {
- // Only get the logger if we don't already have it
+ // Need to refresh the logger after configuration changes
// _logger = Logger.GetPowertoolsLogger();
+ _logger = LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger();
}
+
// Fetch the current configuration
_currentConfig = Logger.GetCurrentConfiguration();
@@ -167,8 +164,15 @@ public void OnEntry(
}
CaptureCorrelationId(eventObject, trigger.CorrelationIdPath);
- if (logEvent || _currentConfig.LogEvent)
+
+ if(trigger.IsLogEventSet && trigger.LogEvent)
+ {
LogEvent(eventObject);
+ }
+ else if (!trigger.IsLogEventSet && _currentConfig.LogEvent)
+ {
+ LogEvent(eventObject);
+ }
}
catch (Exception exception)
{
@@ -334,6 +338,5 @@ private void LogEvent(object eventArg)
internal static void ResetForTest()
{
LoggingLambdaContext.Clear();
- // _logger.RemoveAllKeys();
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
index ada06cb1..a3fce9d3 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs
@@ -30,6 +30,7 @@ internal static class LoggingAspectFactory
/// An instance of the LoggingAspect class.
public static object GetInstance(Type type)
{
+ // Use Logger.GetPowertoolsLogger() to ensure it's consistent with current config
return new LoggingAspect(LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger());
}
}
\ 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 f625bae6..9f278a12 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs
@@ -41,11 +41,6 @@ internal sealed class PowertoolsLogger : ILogger
///
private readonly Func _currentConfig;
- ///
- /// The system wrapper
- ///
- private readonly Func _getSystemWrapper;
-
///
/// The current scope
///
@@ -56,15 +51,12 @@ internal sealed class PowertoolsLogger : ILogger
///
/// The name.
///
- /// The system wrapper.
public PowertoolsLogger(
string categoryName,
- Func getCurrentConfig,
- Func getSystemWrapper)
+ Func getCurrentConfig)
{
_categoryName = categoryName;
_currentConfig = getCurrentConfig;
- _getSystemWrapper = getSystemWrapper;
}
///
@@ -128,12 +120,12 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
return;
}
- _getSystemWrapper().LogLine(LogEntryString(logLevel, state, exception, formatter));
+ _currentConfig().LogOutput.LogLine(LogEntryString(logLevel, state, exception, formatter));
}
internal void LogLine(string message)
{
- _getSystemWrapper().LogLine(message);
+ _currentConfig().LogOutput.LogLine(message);
}
internal string LogEntryString(LogLevel logLevel, TState state, Exception exception, Func formatter)
@@ -225,7 +217,7 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times
// Use the AddExceptionDetails method instead of adding exception directly
if (exception != null)
{
- AddExceptionDetails(logEntry, exception);
+ logEntry.TryAdd(LoggingConstants.KeyException, exception);
}
return logEntry;
@@ -308,7 +300,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec
if (exception != null)
{
var exceptionDetails = new Dictionary();
- AddExceptionDetails(exceptionDetails, exception);
+ exceptionDetails.TryAdd(LoggingConstants.KeyException, exception);
// Add exception details to extra keys
foreach (var (key, value) in exceptionDetails)
@@ -564,25 +556,4 @@ private string ExtractParameterName(string key)
? nameWithPossibleFormat.Substring(0, colonIndex)
: nameWithPossibleFormat;
}
-
- private void AddExceptionDetails(Dictionary logEntry, Exception exception)
- {
- if (exception == null)
- return;
-
- logEntry.TryAdd("errorType", exception.GetType().FullName);
- logEntry.TryAdd("errorMessage", exception.Message);
-
- // Add stack trace as array of strings for better readability
- var stackFrames = exception.StackTrace?.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
- if (stackFrames?.Length > 0)
- {
- var cleanedStackTrace = new List
- {
- $"{exception.GetType().FullName}: {exception.Message}"
- };
- cleanedStackTrace.AddRange(stackFrames);
- logEntry.TryAdd("stackTrace", cleanedStackTrace);
- }
- }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
index e0caecb2..bea51da3 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs
@@ -30,57 +30,120 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
[ProviderAlias("PowertoolsLogger")]
internal class PowertoolsLoggerProvider : ILoggerProvider
{
- ///
- /// The powertools configurations
- ///
+ private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase);
+ private readonly IDisposable? _onChangeToken;
+ private PowertoolsLoggerConfiguration _currentConfig;
private readonly IPowertoolsConfigurations _powertoolsConfigurations;
- ///
- /// The system wrapper
- ///
- private ISystemWrapper _systemWrapper;
+ public PowertoolsLoggerProvider(
+ PowertoolsLoggerConfiguration config,
+ IPowertoolsConfigurations powertoolsConfigurations)
+ {
+ _powertoolsConfigurations = powertoolsConfigurations;
+ _currentConfig = config;
+
+ // Set execution environment
+ _powertoolsConfigurations.SetExecutionEnvironment(this);
+
+ // Apply environment configurations if available
+ ConfigureFromEnvironment();
+ }
- ///
- /// The loggers
- ///
- private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase);
+ public void ConfigureFromEnvironment()
+ {
+ var logLevel = _powertoolsConfigurations.GetLogLevel(_currentConfig.MinimumLogLevel);
+ var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
+ var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
- private readonly IDisposable? _onChangeToken;
- private PowertoolsLoggerConfiguration _currentConfig;
+ // Warn if Lambda log level doesn't match
+ if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel)
+ {
+ _currentConfig.LogOutput.LogLine(
+ $"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.");
+ }
+
+ // Set service from environment if not explicitly set
+ if (string.IsNullOrEmpty(_currentConfig.Service))
+ {
+ _currentConfig.Service = _powertoolsConfigurations.Service;
+ }
+
+ // Set output case from environment if not explicitly set
+ if (_currentConfig.LoggerOutputCase == LoggerOutputCase.Default)
+ {
+ var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(_currentConfig.LoggerOutputCase);
+ _currentConfig.LoggerOutputCase = loggerOutputCase;
+ }
+ // Set log level from environment ONLY if not explicitly set
+ var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
+ _currentConfig.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
+
+ // LoggerFactoryHolder.UpdateFilterLogLevel(minLogLevel);
+
+ _currentConfig.XRayTraceId = _powertoolsConfigurations.XRayTraceId;
+ _currentConfig.LogEvent = _powertoolsConfigurations.LoggerLogEvent;
+
+ // Configure the log level key based on output case
+ _currentConfig.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
+ _currentConfig.LoggerOutputCase == LoggerOutputCase.PascalCase
+ ? "LogLevel"
+ : LoggingConstants.KeyLogLevel;
+
+ ProcessSamplingRate(_currentConfig, _powertoolsConfigurations);
+ }
+
///
- /// Initializes a new instance of the class.
+ /// Process sampling rate configuration
///
- /// The configuration.
- ///
- ///
- public PowertoolsLoggerProvider(IOptionsMonitor config,
- IPowertoolsConfigurations powertoolsConfigurations,
- ISystemWrapper systemWrapper = null)
+ private void ProcessSamplingRate(PowertoolsLoggerConfiguration config, IPowertoolsConfigurations configurations)
{
- _currentConfig = config.CurrentValue;
- _systemWrapper = systemWrapper;
- _powertoolsConfigurations = powertoolsConfigurations;
-
- _onChangeToken = config.OnChange(updatedConfig =>
+ var samplingRate = config.SamplingRate > 0
+ ? config.SamplingRate
+ : configurations.LoggerSampleRate;
+
+ samplingRate = ValidateSamplingRate(samplingRate, config);
+ config.SamplingRate = samplingRate;
+
+ // Only notify if sampling is configured
+ if (samplingRate > 0)
{
- _currentConfig = updatedConfig;
- // No need to do anything else - the loggers get the config through GetCurrentConfig
- });
+ double sample = config.GetRandom();
+
+ // Instead of changing log level, just indicate sampling status
+ if (sample <= samplingRate)
+ {
+ config.LogOutput.LogLine(
+ $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
+ config.MinimumLogLevel = LogLevel.Debug;
+ }
+ }
}
///
- /// Creates a new instance.
+ /// Validate sampling rate
///
- /// The category name for messages produced by the logger.
- /// The instance of that was created.
+ private double ValidateSamplingRate(double samplingRate, PowertoolsLoggerConfiguration config)
+ {
+ if (samplingRate < 0 || samplingRate > 1)
+ {
+ if (config.MinimumLogLevel is LogLevel.Debug or LogLevel.Trace)
+ {
+ config.LogOutput.LogLine(
+ $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
+ }
+
+ return 0;
+ }
+
+ return samplingRate;
+ }
+
public virtual ILogger CreateLogger(string categoryName)
{
- _powertoolsConfigurations.SetExecutionEnvironment(typeof(PowertoolsLogger));
-
- return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger(name,
- GetCurrentConfig,
- () => _systemWrapper));
+ return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger(
+ name,
+ GetCurrentConfig));
}
internal PowertoolsLoggerConfiguration GetCurrentConfig() => _currentConfig;
@@ -88,107 +151,14 @@ public virtual ILogger CreateLogger(string categoryName)
public void UpdateConfiguration(PowertoolsLoggerConfiguration config)
{
_currentConfig = config;
- }
-
- public void UpdateSystem(ISystemWrapper system)
- {
- if (system == null) return;
-
- _systemWrapper = system;
+
+ // Apply environment configurations if available
+ if (_powertoolsConfigurations != null)
+ {
+ ConfigureFromEnvironment();
+ }
}
- // private void ApplyPowertoolsConfig(PowertoolsLoggerConfiguration config)
- // {
- // var logLevel = _powertoolsConfigurations.GetLogLevel(LogLevel.None);
- // var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel();
- // var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled();
- //
- // // Check for explicit config
- // bool hasExplicitLevel = config.MinimumLogLevel != LogLevel.None;
- //
- // // Warn if Lambda log level doesn't match
- // if (lambdaLogLevelEnabled && hasExplicitLevel && config.MinimumLogLevel < lambdaLogLevel)
- // {
- // _systemWrapper.LogLine(
- // $"Current log level ({config.MinimumLogLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.");
- // }
- //
- // // Set service from environment if not explicitly set
- // if (string.IsNullOrEmpty(config.Service))
- // {
- // config.Service = _powertoolsConfigurations.Service;
- // }
- //
- // // Set output case from environment if not explicitly set
- // if (config.LoggerOutputCase == LoggerOutputCase.Default)
- // {
- // var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(config.LoggerOutputCase);
- // config.LoggerOutputCase = loggerOutputCase;
- // }
- //
- // // Set log level from environment ONLY if not explicitly set
- // if (!hasExplicitLevel)
- // {
- // var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel;
- // config.MinimumLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel;
- // }
- //
- // config.XRayTraceId = _powertoolsConfigurations.XRayTraceId;
- // config.LogEvent = _powertoolsConfigurations.LoggerLogEvent;
- //
- // // Configure the log level key based on output case
- // config.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() &&
- // config.LoggerOutputCase == LoggerOutputCase.PascalCase
- // ? "LogLevel"
- // : LoggingConstants.KeyLogLevel;
- //
- // // Handle sampling rate - BUT DON'T MODIFY MINIMUM LEVEL
- // ProcessSamplingRate(config);
- // }
- //
- // private void ProcessSamplingRate(PowertoolsLoggerConfiguration config)
- // {
- // var samplingRate = config.SamplingRate > 0
- // ? config.SamplingRate
- // : _powertoolsConfigurations.LoggerSampleRate;
- //
- // samplingRate = ValidateSamplingRate(samplingRate, config.MinimumLogLevel, _systemWrapper);
- // config.SamplingRate = samplingRate;
- //
- // // Only notify if sampling is configured
- // if (samplingRate > 0)
- // {
- // double sample = _systemWrapper.GetRandom();
- //
- // // Instead of changing log level, just indicate sampling status
- // if (sample <= samplingRate)
- // {
- // _systemWrapper.LogLine(
- // $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}.");
- // config.MinimumLogLevel = LogLevel.Debug;
- // }
- // }
- // }
- //
- // private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper)
- // {
- // if (samplingRate < 0 || samplingRate > 1)
- // {
- // if (minLogLevel is LogLevel.Debug or LogLevel.Trace)
- // {
- // systemWrapper.LogLine(
- // $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}");
- // }
- //
- // return 0;
- // }
- //
- // return samplingRate;
- // }
-
- ///
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
public virtual void Dispose()
{
_loggers.Clear();
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
index ff9ae0fa..63619b2f 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs
@@ -27,18 +27,29 @@ namespace AWS.Lambda.Powertools.Logging;
///
public static partial class Logger
{
+ private static ILogger _loggerInstance;
private static readonly object _lock = new object();
- // Static constructor to ensure initialization happens before first use
- static Logger()
+ // Change this to a property with getter that recreates if needed
+ private static ILogger LoggerInstance
{
- LoggerInstance = GetPowertoolsLogger();
- // Factory and logger will be lazily initialized when first accessed
+ get
+ {
+ // If we have no instance or configuration has changed, get a new logger
+ if (_loggerInstance == null)
+ {
+ lock (_lock)
+ {
+ if (_loggerInstance == null)
+ {
+ _loggerInstance = GetPowertoolsLogger();
+ }
+ }
+ }
+ return _loggerInstance;
+ }
}
- // Get the current logger instance
- private static ILogger LoggerInstance;
-
///
/// Configure with an existing logger factory
///
@@ -68,24 +79,6 @@ public static PowertoolsLoggerConfiguration GetCurrentConfiguration()
return PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
}
-
- // ///
- // /// Get a logger for a specific type
- // ///
- // /// The type to create logger for
- // /// A configured logger
- // internal static ILogger GetLogger() => GetLogger(typeof(T).Name);
- //
- // ///
- // /// Get a logger for a specific category
- // ///
- // /// The category name
- // /// A configured logger
- // internal static ILogger GetLogger(string category)
- // {
- // return LoggerFactoryHolder.GetOrCreateFactory().CreateLogger(category);
- // }
- //
///
/// Get the Powertools logger instance
///
@@ -115,6 +108,10 @@ public static void UseMinimumLogLevel(LogLevel logLevel)
Configure(config => {
config.MinimumLogLevel = logLevel;
});
+
+ // Also directly update the log filter level to ensure it takes effect immediately
+ LoggerFactoryHolder.UpdateFilterLogLevel(logLevel);
+ _loggerInstance = null;
}
///
@@ -137,9 +134,6 @@ public static void UseServiceName(string serviceName)
/// The rate (0.0 to 1.0) for sampling
public static void UseSamplingRate(double samplingRate)
{
- if (samplingRate < 0 || samplingRate > 1)
- throw new ArgumentOutOfRangeException(nameof(samplingRate), "Sampling rate must be between 0 and 1");
-
Configure(config => {
config.SamplingRate = samplingRate;
});
@@ -187,5 +181,17 @@ public static void UseJsonOptions(JsonSerializerOptions jsonOptions)
internal static void Reset()
{
LoggerFactoryHolder.Reset();
+ _loggerInstance = null;
+ RemoveAllKeys();
+ }
+
+ public static void SetOutput(ISystemWrapper consoleOut)
+ {
+ if (consoleOut == null)
+ throw new ArgumentNullException(nameof(consoleOut));
+
+ Configure(config => {
+ config.LogOutput = consoleOut;
+ });
}
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs
index ba7402bd..b52b12f1 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs
@@ -146,7 +146,19 @@ public class LoggingAttribute : Attribute
/// such as a string or any custom data object.
///
/// true if [log event]; otherwise, false.
- public bool LogEvent { get; set; }
+ public bool LogEvent
+ {
+ get => _logEvent;
+ set
+ {
+ _logEvent = value;
+ _logEventSet = true;
+ }
+ }
+
+ private bool _logEventSet;
+ private bool _logEvent;
+ internal bool IsLogEventSet => _logEventSet;
///
/// Pointer path to extract correlation id from input parameter.
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
index f6a2ea5b..67f7f3a2 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs
@@ -13,6 +13,7 @@
* permissions and limitations under the License.
*/
+using System;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -107,7 +108,12 @@ public JsonSerializerOptions? JsonOptions
///
internal PowertoolsLoggingSerializer Serializer => _serializer ??= InitializeSerializer();
-
+ ///
+ /// The system wrapper used for output operations. Defaults to SystemWrapper instance.
+ /// Primarily useful for testing to capture and verify output.
+ ///
+ public ISystemWrapper LogOutput { get; set; } = new SystemWrapper();
+
///
/// Initialize serializer with the current configuration
///
@@ -122,9 +128,47 @@ private PowertoolsLoggingSerializer InitializeSerializer()
return serializer;
}
+ ///
+ /// Creates a deep clone of the configuration
+ ///
+ public PowertoolsLoggerConfiguration Clone()
+ {
+ return new PowertoolsLoggerConfiguration
+ {
+ Service = Service,
+ TimestampFormat = TimestampFormat,
+ MinimumLogLevel = MinimumLogLevel,
+ SamplingRate = SamplingRate,
+ LoggerOutputCase = LoggerOutputCase,
+ LogLevelKey = LogLevelKey,
+ LogFormatter = LogFormatter,
+ JsonOptions = JsonOptions,
+ LogBuffering = new LogBufferingOptions
+ {
+ Enabled = LogBuffering.Enabled,
+ BufferAtLogLevel = LogBuffering.BufferAtLogLevel,
+ FlushOnErrorLog = LogBuffering.FlushOnErrorLog,
+ },
+ LogOutput = LogOutput, // Reference the same output for now
+ XRayTraceId = XRayTraceId,
+ LogEvent = LogEvent
+ };
+ }
+
// IOptions implementation
PowertoolsLoggerConfiguration IOptions.Value => this;
internal string XRayTraceId { get; set; }
internal bool LogEvent { get; set; }
+
+ internal double Random { get; set; } = new Random().NextDouble();
+
+ ///
+ /// Gets random number
+ ///
+ /// System.Double.
+ internal virtual double GetRandom()
+ {
+ return Random;
+ }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
index 15d480dc..322e2389 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs
@@ -27,6 +27,10 @@ public static void UpdateConfiguration(PowertoolsLoggerConfiguration config)
{
// Update the shared configuration
_currentConfig = config;
+
+ // Uncomment this line to update the filter level
+ if(config.MinimumLogLevel != LogLevel.None)
+ LoggerFactoryHolder.UpdateFilterLogLevel(config.MinimumLogLevel);
// Notify all providers about the change
foreach (var provider in AllProviders)
@@ -41,19 +45,7 @@ public static PowertoolsLoggerConfiguration GetCurrentConfiguration()
lock (_lock)
{
// Return a copy to prevent external modification
- return new PowertoolsLoggerConfiguration
- {
- Service = _currentConfig.Service,
- SamplingRate = _currentConfig.SamplingRate,
- MinimumLogLevel = _currentConfig.MinimumLogLevel,
- LoggerOutputCase = _currentConfig.LoggerOutputCase,
- JsonOptions = _currentConfig.JsonOptions,
- TimestampFormat = _currentConfig.TimestampFormat,
- LogFormatter = _currentConfig.LogFormatter,
- LogLevelKey = _currentConfig.LogLevelKey,
- LogBuffering = _currentConfig.LogBuffering
-
- };
+ return _currentConfig.Clone();
}
}
@@ -62,31 +54,19 @@ public static ILoggingBuilder AddPowertoolsLogger(
{
builder.AddConfiguration();
- // Register ISystemWrapper if not already registered
- // builder.Services.TryAddSingleton(provider =>
- // {
- // // Check if there's a pending mock system first
- // var mockSystem = PowertoolsLoggerTestFixture.GetSystemWrapper();
- // return mockSystem ?? new SystemWrapper();
- // });
-
- builder.Services.TryAddSingleton();
-
builder.Services.TryAddSingleton();
- // Register IPowertoolsConfigurations with all its dependencies
builder.Services.TryAddSingleton(sp =>
new PowertoolsConfigurations(sp.GetRequiredService()));
- // Register the regular provider
builder.Services.TryAddEnumerable(
ServiceDescriptor.Singleton(provider =>
{
var powertoolsConfigurations = provider.GetRequiredService();
- var systemWrapper = provider.GetRequiredService();
var loggerProvider = new PowertoolsLoggerProvider(
- new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration), powertoolsConfigurations,
- systemWrapper);
+ _currentConfig,
+ powertoolsConfigurations);
+
lock (_lock)
{
AllProviders.Add(loggerProvider);
@@ -94,9 +74,7 @@ public static ILoggingBuilder AddPowertoolsLogger(
return loggerProvider;
}));
-
- builder.Services.ConfigureOptions();
-
+
LoggerProviderOptions.RegisterProviderOptions
(builder.Services);
@@ -141,12 +119,9 @@ public static ILoggingBuilder AddPowertoolsLogger(
ServiceDescriptor.Singleton(provider =>
{
var powertoolsConfigurations = provider.GetRequiredService();
- var systemWrapper = provider.GetRequiredService();
var bufferingProvider = new BufferingLoggerProvider(
- new TrackedOptionsMonitor(_currentConfig, UpdateConfiguration),
- powertoolsConfigurations,
- systemWrapper
+ _currentConfig, powertoolsConfigurations
);
lock (_lock)
@@ -161,19 +136,6 @@ public static ILoggingBuilder AddPowertoolsLogger(
return builder;
}
- internal static void UpdateSystemInAllProviders(ISystemWrapper system)
- {
- if (system == null) return;
-
- lock (_lock)
- {
- foreach (var provider in AllProviders)
- {
- provider.UpdateSystem(system);
- }
- }
- }
-
///
/// Resets all providers and clears the configuration.
/// This is useful for testing purposes to ensure a clean state.
@@ -189,66 +151,4 @@ internal static void ResetAllProviders()
_currentConfig = new PowertoolsLoggerConfiguration();
}
}
-
- private class ConfigureLoggingOptions : IConfigureOptions
- {
- private readonly IPowertoolsConfigurations _configurations;
- private readonly ISystemWrapper _systemWrapper;
-
- public ConfigureLoggingOptions(IPowertoolsConfigurations configurations, ISystemWrapper systemWrapper)
- {
- _configurations = configurations;
- _systemWrapper = systemWrapper;
- }
-
- public void Configure(LoggerFilterOptions options)
- {
- // This runs when IOptions is resolved
- LoggerFactoryHolder.ConfigureFromEnvironment(_configurations,_systemWrapper);
- }
- }
-
- private class TrackedOptionsMonitor : IOptionsMonitor
- {
- private PowertoolsLoggerConfiguration _config;
- private readonly Action _updateCallback;
- private readonly List> _listeners = new();
-
- public TrackedOptionsMonitor(
- PowertoolsLoggerConfiguration config,
- Action updateCallback)
- {
- _config = config;
- _updateCallback = updateCallback;
- }
-
- public PowertoolsLoggerConfiguration CurrentValue => _config;
-
- public IDisposable OnChange(Action listener)
- {
- _listeners.Add(listener);
- return new ListenerDisposable(_listeners, listener);
- }
-
- public PowertoolsLoggerConfiguration Get(string? name) => _config;
-
- private class ListenerDisposable : IDisposable
- {
- private readonly List> _listeners;
- private readonly Action _listener;
-
- public ListenerDisposable(
- List> listeners,
- Action listener)
- {
- _listeners = listeners;
- _listener = listener;
- }
-
- public void Dispose()
- {
- _listeners.Remove(_listener);
- }
- }
- }
}
\ No newline at end of file
diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
index 758da539..20b3a4c0 100644
--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
+++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs
@@ -104,39 +104,15 @@ internal string Serialize(object value, Type inputType)
return JsonSerializer.Serialize(value, jsonSerializerOptions);
}
- var options = GetSerializerOptions();
-
// Try to serialize using the configured TypeInfoResolver
- try
- {
- var typeInfo = GetTypeInfo(inputType);
- if (typeInfo != null)
- {
- return JsonSerializer.Serialize(value, typeInfo);
- }
- }
- catch (InvalidOperationException)
- {
- // Failed to get typeinfo, will fall back to trying the serializer directly
- }
-
- // Fall back to direct serialization which may work if the resolver chain can handle it
- try
- {
- return JsonSerializer.Serialize(value, inputType, options);
- }
- catch (JsonException ex)
+ var typeInfo = GetTypeInfo(inputType);
+ if (typeInfo == null)
{
throw new JsonSerializerException(
- $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.",
- ex);
- }
- catch (InvalidOperationException ex)
- {
- throw new JsonSerializerException(
- $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.",
- ex);
+ $"Type {inputType} is not known to the serializer. Ensure it's included in the JsonSerializerContext.");
}
+ return JsonSerializer.Serialize(value, typeInfo);
+
#endif
}
@@ -280,6 +256,9 @@ private void BuildJsonSerializerOptions(JsonSerializerOptions options = null)
if (!RuntimeFeatureWrapper.IsDynamicCodeSupported)
{
HandleJsonOptionsTypeResolver(_jsonOptions);
+
+ // Ensure the TypeInfoResolver is set
+ _jsonOptions.TypeInfoResolver = GetCompositeResolver();
}
#endif
}
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs
index ba08453f..840fa13a 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs
@@ -16,7 +16,6 @@
using System;
using AWS.Lambda.Powertools.Common;
using AWS.Lambda.Powertools.Logging.Internal;
-using AWS.Lambda.Powertools.Logging.Serializers;
using AWS.Lambda.Powertools.Logging.Tests.Handlers;
using AWS.Lambda.Powertools.Logging.Tests.Serializers;
using Microsoft.Extensions.Logging;
@@ -28,23 +27,20 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes;
[Collection("Sequential")]
public class LoggerAspectTests : IDisposable
{
- private ISystemWrapper _mockSystemWrapper;
- private readonly IPowertoolsConfigurations _mockPowertoolsConfigurations;
-
- public LoggerAspectTests()
- {
- _mockSystemWrapper = Substitute.For();
- _mockPowertoolsConfigurations = Substitute.For();
- }
-
[Fact]
public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments()
{
// Arrange
-#if NET8_0_OR_GREATER
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
+ var consoleOut = Substitute.For();
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = "TestService",
+ MinimumLogLevel = LogLevel.Information,
+ LogOutput = consoleOut
+ };
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
var instance = new object();
var name = "TestMethod";
@@ -66,14 +62,12 @@ public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments()
}
};
- _mockSystemWrapper.GetRandom().Returns(0.7);
-
// Act
- var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper);
+ var loggingAspect = new LoggingAspect(logger);
loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers);
// Assert
- _mockSystemWrapper.Received().LogLine(Arg.Is(s =>
+ consoleOut.Received().LogLine(Arg.Is(s =>
s.Contains(
"\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null},\"SamplingRate\":0.5}")
&& s.Contains("\"CorrelationId\":\"20\"")
@@ -84,12 +78,19 @@ public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments()
public void OnEntry_ShouldLog_Event_When_EnvironmentVariable_Set()
{
// Arrange
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+ Environment.SetEnvironmentVariable(Constants.LoggerLogEventNameEnv, "true");
+ var consoleOut = Substitute.For();
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = "TestService",
+ MinimumLogLevel = LogLevel.Information,
+ LogEvent = true,
+ LogOutput = consoleOut
+ };
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
+
var instance = new object();
var name = "TestMethod";
var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } };
@@ -103,43 +104,97 @@ public void OnEntry_ShouldLog_Event_When_EnvironmentVariable_Set()
Service = "TestService",
LoggerOutputCase = LoggerOutputCase.PascalCase,
LogLevel = LogLevel.Information,
- LogEvent = false,
CorrelationIdPath = "/Age",
ClearState = true
}
};
-
- // Env returns true
- _mockPowertoolsConfigurations.LoggerLogEvent.Returns(true);
-
+
// Act
- var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper);
+ var loggingAspect = new LoggingAspect(logger);
loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers);
-
+
+ var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
// Assert
- var config = _mockPowertoolsConfigurations.CurrentConfig();
- Assert.NotNull(Logger.LoggerProvider);
- Assert.Equal("TestService", config.Service);
- Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase);
- Assert.Equal(0, config.SamplingRate);
-
- _mockSystemWrapper.Received().LogLine(Arg.Is(s =>
+ Assert.Equal("TestService", updatedConfig.Service);
+ Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase);
+ Assert.Equal(0, updatedConfig.SamplingRate);
+ Assert.True(updatedConfig.LogEvent);
+
+ consoleOut.Received().LogLine(Arg.Is(s =>
s.Contains(
"\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}}")
&& s.Contains("\"CorrelationId\":\"20\"")
));
}
+
+ [Fact]
+ public void OnEntry_Should_NOT_Log_Event_When_EnvironmentVariable_Set_But_Attribute_False()
+ {
+ // Arrange
+ Environment.SetEnvironmentVariable(Constants.LoggerLogEventNameEnv, "true");
+ var consoleOut = Substitute.For();
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = "TestService",
+ MinimumLogLevel = LogLevel.Information,
+ LogEvent = true,
+ LogOutput = consoleOut
+ };
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
+
+ var instance = new object();
+ var name = "TestMethod";
+ var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } };
+ var hostType = typeof(string);
+ var method = typeof(TestHandlers).GetMethod("TestMethod");
+ var returnType = typeof(string);
+ var triggers = new Attribute[]
+ {
+ new LoggingAttribute
+ {
+ Service = "TestService",
+ LoggerOutputCase = LoggerOutputCase.PascalCase,
+ LogLevel = LogLevel.Information,
+ LogEvent = false,
+ CorrelationIdPath = "/Age",
+ ClearState = true
+ }
+ };
+
+ // Act
+ var loggingAspect = new LoggingAspect(logger);
+ loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers);
+
+ var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
+ // Assert
+ Assert.Equal("TestService", updatedConfig.Service);
+ Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase);
+ Assert.Equal(0, updatedConfig.SamplingRate);
+ Assert.True(updatedConfig.LogEvent);
+ consoleOut.DidNotReceive().LogLine(Arg.Any());
+ }
+
[Fact]
public void OnEntry_ShouldLog_SamplingRate_When_EnvironmentVariable_Set()
{
// Arrange
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+ var consoleOut = Substitute.For();
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = "TestService",
+ MinimumLogLevel = LogLevel.Information,
+ SamplingRate = 0.5,
+ LogOutput = consoleOut
+ };
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
+
var instance = new object();
var name = "TestMethod";
var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } };
@@ -159,31 +214,39 @@ public void OnEntry_ShouldLog_SamplingRate_When_EnvironmentVariable_Set()
}
};
- // Env returns true
- _mockPowertoolsConfigurations.LoggerSampleRate.Returns(0.5);
-
// Act
- var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper);
+ var loggingAspect = new LoggingAspect(logger);
loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers);
-
+
// Assert
- var config = _mockPowertoolsConfigurations.CurrentConfig();
- Assert.NotNull(Logger.LoggerProvider);
- Assert.Equal("TestService", config.Service);
- Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase);
- Assert.Equal(0.5, config.SamplingRate);
-
- _mockSystemWrapper.Received().LogLine(Arg.Is(s =>
+ var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
+ Assert.Equal("TestService", updatedConfig.Service);
+ Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase);
+ Assert.Equal(0.5, updatedConfig.SamplingRate);
+
+ consoleOut.Received().LogLine(Arg.Is(s =>
s.Contains(
"\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null},\"SamplingRate\":0.5}")
&& s.Contains("\"CorrelationId\":\"20\"")
));
}
-
+
[Fact]
public void OnEntry_ShouldLogEvent_WhenLogEventIsTrue()
{
// Arrange
+ var consoleOut = Substitute.For();
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = "TestService",
+ MinimumLogLevel = LogLevel.Information,
+ LogOutput = consoleOut,
+ };
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
+
var eventObject = new { testData = "test-data" };
var triggers = new Attribute[]
{
@@ -192,29 +255,34 @@ public void OnEntry_ShouldLogEvent_WhenLogEventIsTrue()
LogEvent = true
}
};
-
+
// Act
-
- var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper);
+
+ var loggingAspect = new LoggingAspect(logger);
loggingAspect.OnEntry(null, null, new object[] { eventObject }, null, null, null, triggers);
-
+
// Assert
- _mockSystemWrapper.Received().LogLine(Arg.Is(s =>
+ consoleOut.Received().LogLine(Arg.Is(s =>
s.Contains(
"\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":{\"test_data\":\"test-data\"}}")
));
}
-
+
[Fact]
public void OnEntry_ShouldNot_Log_Info_When_LogLevel_Higher_EnvironmentVariable()
{
// Arrange
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+ var consoleOut = Substitute.For();
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = "TestService",
+ MinimumLogLevel = LogLevel.Error,
+ LogOutput = consoleOut
+ };
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
+
var instance = new object();
var name = "TestMethod";
var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } };
@@ -227,38 +295,37 @@ public void OnEntry_ShouldNot_Log_Info_When_LogLevel_Higher_EnvironmentVariable(
{
Service = "TestService",
LoggerOutputCase = LoggerOutputCase.PascalCase,
-
+
LogEvent = true,
CorrelationIdPath = "/age"
}
};
-
- // Env returns true
- _mockPowertoolsConfigurations.LogLevel.Returns(LogLevel.Error.ToString());
-
+
// Act
- var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper);
+ var loggingAspect = new LoggingAspect(logger);
loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers);
-
+
+ var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
// Assert
- var config = _mockPowertoolsConfigurations.CurrentConfig();
- Assert.NotNull(Logger.LoggerProvider);
- Assert.Equal("TestService", config.Service);
- Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase);
-
- _mockSystemWrapper.DidNotReceive().LogLine(Arg.Any());
+ Assert.Equal("TestService", updatedConfig.Service);
+ Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase);
+
+ consoleOut.DidNotReceive().LogLine(Arg.Any());
}
-
+
[Fact]
public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable()
{
// Arrange
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+ Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Debug");
+
+ var consoleOut = Substitute.For();
+ var config = new PowertoolsLoggerConfiguration
+ {
+ LogOutput = consoleOut
+ };
+
var instance = new object();
var name = "TestMethod";
var args = new object[]
@@ -278,25 +345,26 @@ public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable()
CorrelationIdPath = "/Headers/MyRequestIdHeader"
}
};
+
+ var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger();
- // Env returns true
- _mockPowertoolsConfigurations.LogLevel.Returns(LogLevel.Debug.ToString());
-
+
// Act
- var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper);
+ var loggingAspect = new LoggingAspect(logger);
loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers);
-
+
// Assert
- var config = _mockPowertoolsConfigurations.CurrentConfig();
- Assert.NotNull(Logger.LoggerProvider);
- Assert.Equal("TestService", config.Service);
- Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase);
- Assert.Equal(LogLevel.Debug, config.MinimumLevel);
-
- _mockSystemWrapper.Received(1).LogLine(Arg.Is(s =>
- s == "Skipping Lambda Context injection because ILambdaContext context parameter not found."));
-
- _mockSystemWrapper.Received(1).LogLine(Arg.Is(s =>
+ var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration();
+
+ Assert.Equal("TestService", updatedConfig.Service);
+ Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase);
+ Assert.Equal(LogLevel.Debug, updatedConfig.MinimumLogLevel);
+
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains(
+ "\"Level\":\"Debug\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":\"Skipping Lambda Context injection because ILambdaContext context parameter not found.\"}")));
+
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
s.Contains("\"CorrelationId\":\"test\"") &&
s.Contains(
"\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":{\"MyRequestIdHeader\":\"test\"}")
@@ -306,6 +374,6 @@ public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable()
public void Dispose()
{
LoggingAspect.ResetForTest();
- PowertoolsLoggingSerializer.ClearOptions();
+ Logger.Reset();
}
}
\ 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..27141662 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs
@@ -15,17 +15,17 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.ApplicationLoadBalancerEvents;
using Amazon.Lambda.CloudWatchEvents.S3Events;
using Amazon.Lambda.TestUtilities;
using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Common.Tests;
using AWS.Lambda.Powertools.Logging.Internal;
-using AWS.Lambda.Powertools.Logging.Serializers;
using AWS.Lambda.Powertools.Logging.Tests.Handlers;
using AWS.Lambda.Powertools.Logging.Tests.Serializers;
+using Microsoft.Extensions.Logging;
using NSubstitute;
using Xunit;
@@ -35,27 +35,26 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes
public class LoggingAttributeTests : IDisposable
{
private TestHandlers _testHandlers;
-
+
public LoggingAttributeTests()
{
_testHandlers = new TestHandlers();
}
-
+
[Fact]
public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
// Act
_testHandlers.TestMethod();
-
+
// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
-
- Assert.NotNull(Logger.LoggerProvider);
+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyColdStart));
//Assert.True((bool)allKeys[LoggingConstants.KeyColdStart]);
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionName));
@@ -63,25 +62,24 @@ public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext()
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionMemorySize));
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionArn));
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionRequestId));
-
- consoleOut.DidNotReceive().WriteLine(Arg.Any());
+
+ consoleOut.DidNotReceive().LogLine(Arg.Any());
}
-
+
[Fact]
public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContextAndLogDebug()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
// Act
_testHandlers.TestMethodDebug();
-
+
// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
-
- Assert.NotNull(Logger.LoggerProvider);
+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyColdStart));
//Assert.True((bool)allKeys[LoggingConstants.KeyColdStart]);
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionName));
@@ -89,24 +87,24 @@ public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContextAndLogDebu
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionMemorySize));
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionArn));
Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionRequestId));
-
- consoleOut.Received(1).WriteLine(
+
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
- i == $"Skipping Lambda Context injection because ILambdaContext context parameter not found.")
+ i.Contains("\"level\":\"Debug\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Skipping Lambda Context injection because ILambdaContext context parameter not found.\"}"))
);
}
-
+
[Fact]
public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArg()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
// Act
_testHandlers.LogEventNoArgs();
-
- consoleOut.DidNotReceive().WriteLine(
+
+ consoleOut.DidNotReceive().LogLine(
Arg.Any()
);
}
@@ -115,20 +113,15 @@ public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArg()
public void OnEntry_WhenEventArgExist_LogEvent()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
var correlationId = Guid.NewGuid().ToString();
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
var context = new TestLambdaContext()
{
FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1"
};
-
+
var testObj = new TestObject
{
Headers = new Header
@@ -139,8 +132,8 @@ public void OnEntry_WhenEventArgExist_LogEvent()
// Act
_testHandlers.LogEvent(testObj, context);
-
- consoleOut.Received(1).WriteLine(
+
+ consoleOut.Received(1).LogLine(
Arg.Is(i => i.Contains("FunctionName\":\"PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1"))
);
}
@@ -149,14 +142,9 @@ public void OnEntry_WhenEventArgExist_LogEvent()
public void OnEntry_WhenEventArgExist_LogEvent_False_Should_Not_Log()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
var context = new TestLambdaContext()
{
FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1"
@@ -164,41 +152,44 @@ public void OnEntry_WhenEventArgExist_LogEvent_False_Should_Not_Log()
// Act
_testHandlers.LogEventFalse(context);
-
- consoleOut.DidNotReceive().WriteLine(
+
+ consoleOut.DidNotReceive().LogLine(
Arg.Any()
);
}
-
+
[Fact]
public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArgAndLogDebug()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
// Act
_testHandlers.LogEventDebug();
-
- consoleOut.Received(1).WriteLine(
- Arg.Is(i => i == "Skipping Event Log because event parameter not found.")
+
+ consoleOut.Received(1).LogLine(
+ Arg.Is(i => i.Contains("\"level\":\"Debug\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Skipping Event Log because event parameter not found.\"}"))
+ );
+
+ consoleOut.Received(1).LogLine(
+ Arg.Is(i => i.Contains("\"level\":\"Debug\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Skipping Lambda Context injection because ILambdaContext context parameter not found.\"}"))
);
}
-
+
[Fact]
public void OnExit_WhenHandler_ClearState_Enabled_ClearKeys()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
// Act
_testHandlers.ClearState();
-
- Assert.NotNull(Logger.LoggerProvider);
+
Assert.False(Logger.GetAllKeys().Any());
}
-
+
[Theory]
[InlineData(CorrelationIdPaths.ApiGatewayRest)]
[InlineData(CorrelationIdPaths.ApplicationLoadBalancer)]
@@ -208,13 +199,8 @@ public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationI
{
// Arrange
var correlationId = Guid.NewGuid().ToString();
-
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+
+
// Act
switch (correlationIdPath)
{
@@ -252,15 +238,15 @@ public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationI
});
break;
}
-
+
// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
-
+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
}
-
+
[Theory]
[InlineData(LoggerOutputCase.SnakeCase)]
[InlineData(LoggerOutputCase.PascalCase)]
@@ -269,13 +255,8 @@ public void When_Capturing_CorrelationId_Converts_To_Case(LoggerOutputCase outpu
{
// Arrange
var correlationId = Guid.NewGuid().ToString();
-
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+
+
// Act
switch (outputCase)
{
@@ -307,11 +288,11 @@ public void When_Capturing_CorrelationId_Converts_To_Case(LoggerOutputCase outpu
});
break;
}
-
+
// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
-
+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
}
@@ -324,13 +305,8 @@ public void When_Capturing_CorrelationId_Converts_To_Case_From_Environment_Var(L
{
// Arrange
var correlationId = Guid.NewGuid().ToString();
-
-#if NET8_0_OR_GREATER
-
- // Add seriolization context for AOT
- PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default);
-#endif
-
+
+
// Act
switch (outputCase)
{
@@ -364,11 +340,11 @@ public void When_Capturing_CorrelationId_Converts_To_Case_From_Environment_Var(L
});
break;
}
-
+
// Assert
var allKeys = Logger.GetAllKeys()
.ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value);
-
+
Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId));
Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId);
}
@@ -377,15 +353,15 @@ public void When_Capturing_CorrelationId_Converts_To_Case_From_Environment_Var(L
public void When_Setting_SamplingRate_Should_Add_Key()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
// Act
_testHandlers.HandlerSamplingRate();
// Assert
- consoleOut.Received().WriteLine(
+ consoleOut.Received().LogLine(
Arg.Is(i => i.Contains("\"message\":\"test\",\"samplingRate\":0.5"))
);
}
@@ -394,8 +370,8 @@ public void When_Setting_SamplingRate_Should_Add_Key()
public void When_Setting_Service_Should_Update_Key()
{
// Arrange
- var consoleOut = new StringWriter();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = new TestLoggerOutput();
+ Logger.SetOutput(consoleOut);
// Act
_testHandlers.HandlerService();
@@ -410,9 +386,9 @@ public void When_Setting_Service_Should_Update_Key()
public void When_Setting_LogLevel_Should_Update_LogLevel()
{
// Arrange
- var consoleOut = new StringWriter();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = new TestLoggerOutput();
+ Logger.SetOutput(consoleOut);
+
// Act
_testHandlers.TestLogLevelCritical();
@@ -426,8 +402,8 @@ public void When_Setting_LogLevel_Should_Update_LogLevel()
public void When_Setting_LogLevel_HigherThanInformation_Should_Not_LogEvent()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
var context = new TestLambdaContext()
{
FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1"
@@ -437,108 +413,126 @@ public void When_Setting_LogLevel_HigherThanInformation_Should_Not_LogEvent()
_testHandlers.TestLogLevelCriticalLogEvent(context);
// Assert
- consoleOut.DidNotReceive().WriteLine(Arg.Any());
+ consoleOut.DidNotReceive().LogLine(Arg.Any());
}
[Fact]
public void When_LogLevel_Debug_Should_Log_Message_When_No_Context_And_LogEvent_True()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
// Act
_testHandlers.TestLogEventWithoutContext();
// Assert
- consoleOut.Received(1).WriteLine(Arg.Is(s => s == "Skipping Event Log because event parameter not found."));
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains("\"level\":\"Debug\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Skipping Event Log because event parameter not found.\"}")));
+
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains("\"level\":\"Debug\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Skipping Lambda Context injection because ILambdaContext context parameter not found.\"}")));
}
[Fact]
public void Should_Log_When_Not_Using_Decorator()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
var test = new TestHandlers();
// Act
test.TestLogNoDecorator();
-
+
// Assert
- consoleOut.Received().WriteLine(
+ consoleOut.Received().LogLine(
Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"test\"}"))
);
}
- public void Dispose()
+ [Fact]
+ public void LoggingAspect_ShouldRespectDynamicLogLevelChanges()
{
- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", "");
- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "");
- LoggingAspect.ResetForTest();
- PowertoolsLoggingSerializer.ClearOptions();
- }
- }
+ // Arrange
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+ Logger.UseMinimumLogLevel(LogLevel.Warning); // Start with Warning level
- [Collection("A Sequential")]
- public class ServiceTests : IDisposable
- {
- private readonly TestServiceHandler _testHandler;
+ // Act
+ _testHandlers.TestMethodDebug(); // Uses LogLevel.Debug attribute
+
+ // Assert
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains("\"level\":\"Debug\"") &&
+ s.Contains("Skipping Lambda Context injection")));
+ }
- public ServiceTests()
+ [Fact]
+ public void LoggingAspect_ShouldCorrectlyResetLogLevelAfterExecution()
{
- _testHandler = new TestServiceHandler();
+ // Arrange
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+ Logger.UseMinimumLogLevel(LogLevel.Warning);
+
+ // Act - First call with Debug level attribute
+ _testHandlers.TestMethodDebug();
+ consoleOut.ClearReceivedCalls();
+
+ // Act - Then log directly at Debug level (should still work)
+ Logger.LogDebug("This should be logged");
+
+ // Assert
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains("\"level\":\"Debug\"") &&
+ s.Contains("\"message\":\"This should be logged\"")));
}
[Fact]
- public void When_Setting_Service_Should_Override_Env()
+ public void LoggingAspect_ShouldRespectAttributePrecedenceOverEnvironment()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error");
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
// Act
- _testHandler.LogWithEnv();
- _testHandler.Handler();
-
+ _testHandlers.TestMethodDebug(); // Uses LogLevel.Debug attribute
+
// Assert
-
- consoleOut.Received(1).WriteLine(
- Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Environment Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Environment Service\""))
- );
- consoleOut.Received(1).WriteLine(
- Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\""))
- );
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains("\"level\":\"Debug\"")));
}
[Fact]
- public void When_Setting_Service_Should_Override_Env_And_Empty()
+ public void LoggingAspect_ShouldImmediatelyApplyFilterLevelChanges()
{
// Arrange
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+ Logger.UseMinimumLogLevel(LogLevel.Error);
+
// Act
- _testHandler.LogWithAndWithoutEnv();
- _testHandler.Handler();
-
+ Logger.LogInformation("This should NOT be logged");
+ _testHandlers.TestMethodDebug(); // Should change level to Debug
+ Logger.LogInformation("This should be logged");
+
// Assert
-
- consoleOut.Received(2).WriteLine(
- Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: service_undefined\""))
- );
- consoleOut.Received(1).WriteLine(
- Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\""))
- );
+ consoleOut.Received(1).LogLine(Arg.Is(s =>
+ s.Contains("\"message\":\"This should be logged\"")));
+ consoleOut.DidNotReceive().LogLine(Arg.Is(s =>
+ s.Contains("\"message\":\"This should NOT be logged\"")));
}
-
+
public void Dispose()
{
Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", "");
Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "");
LoggingAspect.ResetForTest();
- PowertoolsLoggingSerializer.ClearOptions();
+ Logger.Reset();
+ PowertoolsLoggingBuilderExtensions.ResetAllProviders();
}
}
}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs
new file mode 100644
index 00000000..3e3d4ac6
--- /dev/null
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs
@@ -0,0 +1,50 @@
+using System;
+using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Logging.Internal;
+using AWS.Lambda.Powertools.Logging.Tests.Handlers;
+using NSubstitute;
+using Xunit;
+
+namespace AWS.Lambda.Powertools.Logging.Tests.Attributes;
+
+[Collection("A Sequential")]
+public class ServiceTests : IDisposable
+{
+ private readonly TestServiceHandler _testHandler;
+
+ public ServiceTests()
+ {
+ _testHandler = new TestServiceHandler();
+ }
+
+ [Fact]
+ public void When_Setting_Service_Should_Override_Env()
+ {
+ Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service");
+
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
+ // Act
+ _testHandler.LogWithEnv();
+ _testHandler.Handler();
+
+ // Assert
+
+ consoleOut.Received(1).LogLine(
+ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Environment Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Environment Service\""))
+ );
+ consoleOut.Received(1).LogLine(
+ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\""))
+ );
+ }
+
+ public void Dispose()
+ {
+ Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", "");
+ Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "");
+ LoggingAspect.ResetForTest();
+ Logger.Reset();
+ PowertoolsLoggingBuilderExtensions.ResetAllProviders();
+ }
+}
\ No newline at end of file
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..89ca07d5 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs
@@ -15,7 +15,6 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
@@ -26,6 +25,7 @@
using AWS.Lambda.Powertools.Logging.Internal;
using AWS.Lambda.Powertools.Logging.Serializers;
using AWS.Lambda.Powertools.Logging.Tests.Handlers;
+using Microsoft.Extensions.Options;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using NSubstitute.ReturnsExtensions;
@@ -47,8 +47,9 @@ public LogFormatterTest()
[Fact]
public void Serialize_ShouldHandleEnumValues()
{
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
@@ -61,14 +62,14 @@ public void Serialize_ShouldHandleEnumValues()
var handler = new TestHandlers();
handler.TestEnums("fake", lambdaContext);
- consoleOut.Received(1).WriteLine(Arg.Is(i =>
+ consoleOut.Received(1).LogLine(Arg.Is(i =>
i.Contains("\"message\":5")
));
- consoleOut.Received(1).WriteLine(Arg.Is(i =>
+ consoleOut.Received(1).LogLine(Arg.Is(i =>
i.Contains("\"message\":\"Dog\"")
));
- var json = JsonSerializer.Serialize(Pet.Dog, PowertoolsLoggingSerializer.GetSerializerOptions());
+ var json = JsonSerializer.Serialize(Pet.Dog, new PowertoolsLoggingSerializer().GetSerializerOptions());
Assert.Contains("Dog", json);
}
@@ -107,13 +108,6 @@ public void Log_WhenCustomFormatter_LogsCustomFormat()
var configurations = Substitute.For();
configurations.Service.Returns(service);
- var loggerConfiguration = new LoggerConfiguration
- {
- Service = service,
- MinimumLevel = minimumLevel,
- LoggerOutputCase = LoggerOutputCase.PascalCase
- };
-
var globalExtraKeys = new Dictionary
{
{ Guid.NewGuid().ToString(), Guid.NewGuid().ToString() },
@@ -173,12 +167,20 @@ public void Log_WhenCustomFormatter_LogsCustomFormat()
}
};
+ var systemWrapper = Substitute.For();
logFormatter.FormatLogEntry(new LogEntry()).ReturnsForAnyArgs(formattedLogEntry);
- Logger.UseFormatter(logFormatter);
+
+ var config = new PowertoolsLoggerConfiguration
+ {
+ Service = service,
+ MinimumLogLevel = minimumLevel,
+ LoggerOutputCase = LoggerOutputCase.PascalCase,
+ LogFormatter = logFormatter,
+ LogOutput = systemWrapper
+ };
- var systemWrapper = Substitute.For();
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(config, configurations);
var logger = provider.CreateLogger(loggerName);
var scopeExtraKeys = new Dictionary
@@ -227,8 +229,9 @@ public void Log_WhenCustomFormatter_LogsCustomFormat()
[Fact]
public void Should_Log_CustomFormatter_When_Decorated()
{
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
@@ -245,13 +248,13 @@ public void Should_Log_CustomFormatter_When_Decorated()
// in .net 8 it removes null properties
#if NET8_0_OR_GREATER
- consoleOut.Received(1).WriteLine(
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
i.Contains(
"\"correlation_ids\":{\"aws_request_id\":\"requestId\"},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_mb\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\""))
);
#else
- consoleOut.Received(1).WriteLine(
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
i.Contains(
"{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{\"aws_request_id\":\"requestId\",\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_m_b\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\""))
@@ -262,8 +265,9 @@ public void Should_Log_CustomFormatter_When_Decorated()
[Fact]
public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log()
{
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
var lambdaContext = new TestLambdaContext
{
FunctionName = "funtionName",
@@ -281,13 +285,13 @@ public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log()
// in .net 8 it removes null properties
#if NET8_0_OR_GREATER
- consoleOut.Received(1).WriteLine(
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
i ==
"{\"message\":\"test\",\"service\":\"service_undefined\",\"correlation_ids\":{},\"lambda_function\":{\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0}}")
);
#else
- consoleOut.Received(1).WriteLine(
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
i ==
"{\"message\":\"test\",\"service\":\"service_undefined\",\"correlation_ids\":{\"aws_request_id\":null,\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":null,\"arn\":null,\"memory_limit_in_m_b\":null,\"version\":null,\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0}}")
@@ -298,21 +302,21 @@ public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log()
[Fact]
public void Should_Log_CustomFormatter_When_Decorated_No_Context()
{
- var consoleOut = Substitute.For();
- SystemWrapper.Instance.SetOut(consoleOut);
-
+ var consoleOut = Substitute.For();
+ Logger.SetOutput(consoleOut);
+
Logger.UseFormatter(new CustomLogFormatter());
_testHandler.TestCustomFormatterWithDecoratorNoContext("test");
#if NET8_0_OR_GREATER
- consoleOut.Received(1).WriteLine(
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
i ==
"{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{},\"lambda_function\":{\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0.2}}")
);
#else
- consoleOut.Received(1).WriteLine(
+ consoleOut.Received(1).LogLine(
Arg.Is(i =>
i ==
"{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{\"aws_request_id\":null,\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":null,\"arn\":null,\"memory_limit_in_m_b\":null,\"version\":null,\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0.2}}")
@@ -326,7 +330,7 @@ public void Dispose()
Logger.RemoveAllKeys();
LoggingLambdaContext.Clear();
LoggingAspect.ResetForTest();
- PowertoolsLoggingSerializer.ClearOptions();
+ Logger.Reset();
}
}
@@ -351,14 +355,15 @@ public void Log_WhenCustomFormatterReturnNull_ThrowsLogFormatException()
Logger.UseFormatter(logFormatter);
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var config = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = LogLevel.Information,
- LoggerOutputCase = LoggerOutputCase.PascalCase
+ MinimumLogLevel = LogLevel.Information,
+ LoggerOutputCase = LoggerOutputCase.PascalCase,
+ LogFormatter = logFormatter
};
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(config, configurations);
var logger = provider.CreateLogger(loggerName);
// Act
@@ -393,17 +398,17 @@ public void Log_WhenCustomFormatterRaisesException_ThrowsLogFormatException()
var logFormatter = Substitute.For();
logFormatter.FormatLogEntry(new LogEntry()).ThrowsForAnyArgs(new Exception(errorMessage));
- Logger.UseFormatter(logFormatter);
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var config = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = LogLevel.Information,
- LoggerOutputCase = LoggerOutputCase.PascalCase
+ MinimumLogLevel = LogLevel.Information,
+ LoggerOutputCase = LoggerOutputCase.PascalCase,
+ LogFormatter = logFormatter
};
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(config, configurations);
var logger = provider.CreateLogger(loggerName);
// Act
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs
index f9ffd5eb..1bed72bf 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs
@@ -42,6 +42,6 @@ public void Utility_Should_Not_Throw_Exceptions_To_Client()
public void Dispose()
{
LoggingAspect.ResetForTest();
- PowertoolsLoggingSerializer.ClearOptions();
+ Logger.Reset();
}
}
\ No newline at end of file
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs
index 08fe54d4..00d4fbba 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs
@@ -189,19 +189,8 @@ public class TestServiceHandler
{
public void LogWithEnv()
{
- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service");
-
Logger.LogInformation("Service: Environment Service");
}
-
- public void LogWithAndWithoutEnv()
- {
- Logger.LogInformation("Service: service_undefined");
-
- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service");
-
- Logger.LogInformation("Service: service_undefined");
- }
[Logging(Service = "Attribute Service")]
public void Handler()
diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs
index e034ce33..d5252f10 100644
--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs
+++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs
@@ -20,6 +20,7 @@
using System.Linq;
using System.Text;
using AWS.Lambda.Powertools.Common;
+using AWS.Lambda.Powertools.Common.Tests;
using AWS.Lambda.Powertools.Logging.Internal;
using AWS.Lambda.Powertools.Logging.Serializers;
using AWS.Lambda.Powertools.Logging.Tests.Utilities;
@@ -34,10 +35,10 @@ public class PowertoolsLoggerTest : IDisposable
{
public PowertoolsLoggerTest()
{
- Logger.UseDefaultFormatter();
+ // Logger.UseDefaultFormatter();
}
- private static void Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel minimumLevel)
+ private static void Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel MinimumLogLevel)
{
// Arrange
var loggerName = Guid.NewGuid().ToString();
@@ -49,15 +50,17 @@ private static void Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel logLevel,
// Configure the substitute for IPowertoolsConfigurations
configurations.Service.Returns(service);
configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString());
- configurations.LogLevel.Returns(minimumLevel.ToString());
+ configurations.LogLevel.Returns(MinimumLogLevel.ToString());
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
- Service = null,
- MinimumLevel = LogLevel.None
+ Service = service,
+ LoggerOutputCase = LoggerOutputCase.PascalCase,
+ MinimumLogLevel = MinimumLogLevel,
+ LogOutput = systemWrapper // Set the output directly on configuration
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
switch (logLevel)
@@ -93,7 +96,8 @@ private static void Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel logLevel,
);
}
- private static void Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel logLevel, LogLevel minimumLevel)
+ private static void Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel logLevel,
+ LogLevel MinimumLogLevel)
{
// Arrange
var loggerName = Guid.NewGuid().ToString();
@@ -105,15 +109,16 @@ private static void Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel logL
// Configure the substitute for IPowertoolsConfigurations
configurations.Service.Returns(service);
configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString());
- configurations.LogLevel.Returns(minimumLevel.ToString());
+ configurations.LogLevel.Returns(MinimumLogLevel.ToString());
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = minimumLevel
+ MinimumLogLevel = MinimumLogLevel,
+ LogOutput = systemWrapper // Set the output directly on configuration
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
switch (logLevel)
@@ -151,26 +156,26 @@ private static void Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel logL
[Theory]
[InlineData(LogLevel.Trace)]
- public void LogTrace_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
+ public void LogTrace_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Trace, minimumLevel);
+ Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Trace, MinimumLogLevel);
}
[Theory]
[InlineData(LogLevel.Trace)]
[InlineData(LogLevel.Debug)]
- public void LogDebug_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
+ public void LogDebug_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Debug, minimumLevel);
+ Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Debug, MinimumLogLevel);
}
[Theory]
[InlineData(LogLevel.Trace)]
[InlineData(LogLevel.Debug)]
[InlineData(LogLevel.Information)]
- public void LogInformation_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
+ public void LogInformation_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Information, minimumLevel);
+ Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Information, MinimumLogLevel);
}
[Theory]
@@ -178,9 +183,9 @@ public void LogInformation_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimum
[InlineData(LogLevel.Debug)]
[InlineData(LogLevel.Information)]
[InlineData(LogLevel.Warning)]
- public void LogWarning_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
+ public void LogWarning_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Warning, minimumLevel);
+ Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Warning, MinimumLogLevel);
}
[Theory]
@@ -189,9 +194,9 @@ public void LogWarning_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLeve
[InlineData(LogLevel.Information)]
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
- public void LogError_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
+ public void LogError_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Error, minimumLevel);
+ Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Error, MinimumLogLevel);
}
[Theory]
@@ -201,9 +206,9 @@ public void LogError_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
- public void LogCritical_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel)
+ public void LogCritical_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Critical, minimumLevel);
+ Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Critical, MinimumLogLevel);
}
[Theory]
@@ -212,9 +217,9 @@ public void LogCritical_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLev
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
- public void LogTrace_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel)
+ public void LogTrace_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Trace, minimumLevel);
+ Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Trace, MinimumLogLevel);
}
[Theory]
@@ -222,33 +227,33 @@ public void LogTrace_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimum
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
- public void LogDebug_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel)
+ public void LogDebug_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Debug, minimumLevel);
+ Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Debug, MinimumLogLevel);
}
[Theory]
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
- public void LogInformation_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel)
+ public void LogInformation_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Information, minimumLevel);
+ Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Information, MinimumLogLevel);
}
[Theory]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
- public void LogWarning_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel)
+ public void LogWarning_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Warning, minimumLevel);
+ Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Warning, MinimumLogLevel);
}
[Theory]
[InlineData(LogLevel.Critical)]
- public void LogError_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel)
+ public void LogError_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Error, minimumLevel);
+ Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Error, MinimumLogLevel);
}
[Theory]
@@ -258,9 +263,9 @@ public void LogError_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimum
[InlineData(LogLevel.Warning)]
[InlineData(LogLevel.Error)]
[InlineData(LogLevel.Critical)]
- public void LogNone_WithAnyMinimumLevel_DoesNotLog(LogLevel minimumLevel)
+ public void LogNone_WithAnyMinimumLogLevel_DoesNotLog(LogLevel MinimumLogLevel)
{
- Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.None, minimumLevel);
+ Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.None, MinimumLogLevel);
}
[Fact]
@@ -280,16 +285,15 @@ public void Log_ConfigurationIsNotProvided_ReadsFromEnvironmentVariables()
var systemWrapper = Substitute.For();
systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
- Service = null,
- MinimumLevel = LogLevel.None
+ Service = service,
+ MinimumLogLevel = logLevel,
+ LogOutput = systemWrapper,
+ SamplingRate = loggerSampleRate
};
- // Act
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
-
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger("test");
logger.LogInformation("Test");
@@ -318,20 +322,21 @@ public void Log_SamplingRateGreaterThanRandom_ChangedLogLevelToDebug()
configurations.LoggerSampleRate.Returns(loggerSampleRate);
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
-
- var loggerConfiguration = new LoggerConfiguration
+
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
- Service = null,
- MinimumLevel = LogLevel.None
+ Service = service,
+ MinimumLogLevel = logLevel,
+ LogOutput = systemWrapper,
+ SamplingRate = loggerSampleRate,
+ Random = randomSampleRate
};
-
+
// Act
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger("test");
-
+
logger.LogInformation("Test");
// Assert
@@ -359,14 +364,15 @@ public void Log_SamplingRateGreaterThanOne_SkipsSamplingRateConfiguration()
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
- Service = null,
- MinimumLevel = LogLevel.None
- };
-
- // Act
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ Service = service,
+ MinimumLogLevel = logLevel,
+ LogOutput = systemWrapper,
+ SamplingRate = loggerSampleRate
+ };
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
logger.LogInformation("Test");
@@ -387,7 +393,6 @@ public void Log_EnvVarSetsCaseToCamelCase_OutputsCamelCaseLog()
var loggerName = Guid.NewGuid().ToString();
var service = Guid.NewGuid().ToString();
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
@@ -395,16 +400,16 @@ public void Log_EnvVarSetsCaseToCamelCase_OutputsCamelCaseLog()
configurations.LoggerOutputCase.Returns(LoggerOutputCase.CamelCase.ToString());
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
// Act
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -430,24 +435,23 @@ public void Log_AttributeSetsCaseToCamelCase_OutputsCamelCaseLog()
var loggerName = Guid.NewGuid().ToString();
var service = Guid.NewGuid().ToString();
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None,
- LoggerOutputCase = LoggerOutputCase.CamelCase
+ MinimumLogLevel = LogLevel.None,
+ LoggerOutputCase = LoggerOutputCase.CamelCase,
+ LogOutput = systemWrapper
};
-
+
// Act
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -483,13 +487,14 @@ public void Log_EnvVarSetsCaseToPascalCase_OutputsPascalCaseLog()
var systemWrapper = Substitute.For();
systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -515,23 +520,22 @@ public void Log_AttributeSetsCaseToPascalCase_OutputsPascalCaseLog()
var loggerName = Guid.NewGuid().ToString();
var service = Guid.NewGuid().ToString();
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None,
- LoggerOutputCase = LoggerOutputCase.PascalCase
+ MinimumLogLevel = LogLevel.None,
+ LoggerOutputCase = LoggerOutputCase.PascalCase,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -565,13 +569,14 @@ public void Log_EnvVarSetsCaseToSnakeCase_OutputsSnakeCaseLog()
var systemWrapper = Substitute.For();
systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -595,23 +600,22 @@ public void Log_AttributeSetsCaseToSnakeCase_OutputsSnakeCaseLog()
var loggerName = Guid.NewGuid().ToString();
var service = Guid.NewGuid().ToString();
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None,
- LoggerOutputCase = LoggerOutputCase.SnakeCase
+ MinimumLogLevel = LogLevel.None,
+ LoggerOutputCase = LoggerOutputCase.SnakeCase,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -644,13 +648,14 @@ public void Log_NoOutputCaseSet_OutputDefaultsToSnakeCaseLog()
var systemWrapper = Substitute.For();
systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
- };
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
+ };
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
var message = new
@@ -679,13 +684,13 @@ public void BeginScope_WhenScopeIsObject_ExtractScopeKeys()
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = logLevel
+ MinimumLogLevel = logLevel
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = (PowertoolsLogger)provider.CreateLogger(loggerName);
var scopeKeys = new
@@ -722,13 +727,13 @@ public void BeginScope_WhenScopeIsObjectDictionary_ExtractScopeKeys()
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = logLevel
+ MinimumLogLevel = logLevel
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = (PowertoolsLogger)provider.CreateLogger(loggerName);
var scopeKeys = new Dictionary
@@ -765,13 +770,13 @@ public void BeginScope_WhenScopeIsStringDictionary_ExtractScopeKeys()
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = logLevel
+ MinimumLogLevel = logLevel
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = (PowertoolsLogger)provider.CreateLogger(loggerName);
var scopeKeys = new Dictionary
@@ -821,12 +826,13 @@ public void Log_WhenExtraKeysIsObjectDictionary_AppendExtraKeys(LogLevel logLeve
configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString());
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = LogLevel.Trace,
+ MinimumLogLevel = LogLevel.Trace,
+ LogOutput = systemWrapper
};
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = (PowertoolsLogger)provider.CreateLogger(loggerName);
var scopeKeys = new Dictionary
@@ -904,13 +910,14 @@ public void Log_WhenExtraKeysIsStringDictionary_AppendExtraKeys(LogLevel logLeve
configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString());
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = LogLevel.Trace,
+ MinimumLogLevel = LogLevel.Trace,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = (PowertoolsLogger)provider.CreateLogger(loggerName);
var scopeKeys = new Dictionary
@@ -988,13 +995,14 @@ public void Log_WhenExtraKeysAsObject_AppendExtraKeys(LogLevel logLevel, bool lo
configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString());
var systemWrapper = Substitute.For();
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = service,
- MinimumLevel = LogLevel.Trace,
+ MinimumLogLevel = LogLevel.Trace,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = (PowertoolsLogger)provider.CreateLogger(loggerName);
var scopeKeys = new
@@ -1054,22 +1062,21 @@ public void Log_WhenException_LogsExceptionDetails()
var service = Guid.NewGuid().ToString();
var error = new InvalidOperationException("TestError");
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
try
@@ -1087,10 +1094,11 @@ public void Log_WhenException_LogsExceptionDetails()
error.Message + "\"")
));
systemWrapper.Received(1).LogLine(Arg.Is(s =>
- s.Contains("\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()")
+ s.Contains(
+ "\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()")
));
}
-
+
[Fact]
public void Log_Inner_Exception()
{
@@ -1109,17 +1117,18 @@ public void Log_Inner_Exception()
var systemWrapper = Substitute.For();
systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
logger.LogError(
- error,
+ error,
"Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}",
12345);
@@ -1128,12 +1137,13 @@ public void Log_Inner_Exception()
s.Contains("\"exception\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" +
error.Message + "\"")
));
-
+
systemWrapper.Received(1).LogLine(Arg.Is(s =>
- s.Contains("\"level\":\"Error\",\"service\":\"" + service+ "\",\"name\":\"" + loggerName + "\",\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"Very important inner exception message (Parameter 'service')\"}}}")
+ s.Contains("\"level\":\"Error\",\"service\":\"" + service + "\",\"name\":\"" + loggerName +
+ "\",\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"Very important inner exception message (Parameter 'service')\"}}}")
));
}
-
+
[Fact]
public void Log_Nested_Inner_Exception()
{
@@ -1143,7 +1153,7 @@ public void Log_Nested_Inner_Exception()
var error = new InvalidOperationException("Parent exception message",
new ArgumentNullException(nameof(service),
new Exception("Very important nested inner exception message")));
-
+
var logLevel = LogLevel.Information;
var randomSampleRate = 0.5;
@@ -1154,24 +1164,26 @@ public void Log_Nested_Inner_Exception()
var systemWrapper = Substitute.For();
systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
-
+
logger.LogError(
- error,
+ error,
"Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}",
12345);
// Assert
systemWrapper.Received(1).LogLine(Arg.Is(s =>
- s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"service\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Very important nested inner exception message\"}}}}")
+ s.Contains(
+ "\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"service\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Very important nested inner exception message\"}}}}")
));
}
@@ -1183,22 +1195,21 @@ public void Log_WhenNestedException_LogsExceptionDetails()
var service = Guid.NewGuid().ToString();
var error = new InvalidOperationException("TestError");
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For();
- systemWrapper.GetRandom().Returns(randomSampleRate);
- var loggerConfiguration = new LoggerConfiguration
+ var loggerConfiguration = new PowertoolsLoggerConfiguration
{
Service = null,
- MinimumLevel = LogLevel.None
+ MinimumLogLevel = LogLevel.None,
+ LogOutput = systemWrapper
};
-
- var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper);
+
+ var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations);
var logger = provider.CreateLogger(loggerName);
try
@@ -1218,7 +1229,7 @@ public void Log_WhenNestedException_LogsExceptionDetails()
}
[Fact]
- public void Log_WhenByteArray_LogsByteArrayNumbers()
+ public void Log_WhenByteArray_LogsBase64EncodedString()
{
// Arrange
var loggerName = Guid.NewGuid().ToString();
@@ -1226,29 +1237,29 @@ public void Log_WhenByteArray_LogsByteArrayNumbers()
var bytes = new byte[10];
new Random().NextBytes(bytes);
var logLevel = LogLevel.Information;
- var randomSampleRate = 0.5;
var configurations = Substitute.For();
configurations.Service.Returns(service);
configurations.LogLevel.Returns(logLevel.ToString());
var systemWrapper = Substitute.For