-
Notifications
You must be signed in to change notification settings - Fork 490
Lambda Logging improvements #2062
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{ | ||
"Projects": [ | ||
{ | ||
"Name": "Amazon.Lambda.RuntimeSupport", | ||
"Type": "Patch", | ||
"ChangelogMessages": [ | ||
"Add support for parameterized logging method with exception to global logger LambdaLogger in Amazon.Lambda.Core" | ||
] | ||
}, | ||
{ | ||
"Name": "Amazon.Lambda.Core", | ||
"Type": "Minor", | ||
"ChangelogMessages": [ | ||
"Added log level version of the static logging functions on Amazon.Lambda.Core.LambdaLogger" | ||
] | ||
} | ||
{ | ||
"Name": "Amazon.Lambda.AspNetCoreServer", | ||
"Type": "Patch", | ||
"ChangelogMessages": [ | ||
"Update Amazon.Lambda.Logging.AspNetCore dependency" | ||
] | ||
}, | ||
{ | ||
"Name": "Amazon.Lambda.AspNetCoreServer.Hosting", | ||
"Type": "Patch", | ||
"ChangelogMessages": [ | ||
"Update Amazon.Lambda.Logging.AspNetCore dependency" | ||
] | ||
}, | ||
{ | ||
"Name": "Amazon.Lambda.Logging.AspNetCore", | ||
"Type": "Major", | ||
"ChangelogMessages": [ | ||
"Add support Lambda log levels", | ||
"Change build target from .NET Standard 2.0 to .NET 6 and NET 8 to match Amazon.Lambda.AspNetCoreServer" | ||
] | ||
}, | ||
{ | ||
"Name": "Amazon.Lambda.TestUtilities", | ||
"Type": "Major", | ||
"ChangelogMessages": [ | ||
"Update Amazon.Lambda.TestUtitlies to have implementation of the newer logging methods", | ||
"Change build target from .NET Standard 2.0 to .NET 6 and NET 8 to match Amazon.Lambda.AspNetCoreServer" | ||
] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,6 +42,7 @@ public static void Log(string message) | |
// value with an Action that directs the logging into its logging system. | ||
#pragma warning disable IDE0044 // Add readonly modifier | ||
private static Action<string, string, object[]> _loggingWithLevelAction = LogWithLevelToConsole; | ||
private static Action<string, Exception, string, object[]> _loggingWithLevelAndExceptionAction = LogWithLevelAndExceptionToConsole; | ||
#pragma warning restore IDE0044 // Add readonly modifier | ||
|
||
// Logs message to console | ||
|
@@ -65,6 +66,15 @@ private static void LogWithLevelToConsole(string level, string message, params o | |
Console.WriteLine(sb.ToString()); | ||
} | ||
|
||
private static void LogWithLevelAndExceptionToConsole(string level, Exception exception, string message, params object[] args) | ||
{ | ||
// Formatting here is not important, it is used for debugging Amazon.Lambda.Core only. | ||
// In a real scenario Amazon.Lambda.RuntimeSupport will change the value of _loggingWithLevelAction | ||
// to an Action inside it's logging system to handle the real formatting. | ||
LogWithLevelToConsole(level, message, args); | ||
Console.WriteLine(exception); | ||
} | ||
|
||
private const string ParameterizedPreviewMessage = | ||
"This method has been mark as preview till the Lambda .NET Managed runtime has been updated with the backing implementation of this method. " + | ||
"It is possible to use this method while in preview if the Lambda function is deployed as an executable and uses the latest version of Amazon.Lambda.RuntimeSupport."; | ||
|
@@ -78,7 +88,6 @@ private static void LogWithLevelToConsole(string level, string message, params o | |
/// <param name="level">The log level of the message</param> | ||
/// <param name="message">Message to log. The message may have format arguments.</param> | ||
/// <param name="args">Arguments to format the message with.</param> | ||
[RequiresPreviewFeatures(ParameterizedPreviewMessage)] | ||
public static void Log(string level, string message, params object[] args) | ||
{ | ||
_loggingWithLevelAction(level, message, args); | ||
|
@@ -93,8 +102,36 @@ public static void Log(string level, string message, params object[] args) | |
/// <param name="level">The log level of the message</param> | ||
/// <param name="message">Message to log. The message may have format arguments.</param> | ||
/// <param name="args">Arguments to format the message with.</param> | ||
[RequiresPreviewFeatures(ParameterizedPreviewMessage)] | ||
public static void Log(LogLevel level, string message, params object[] args) => Log(level.ToString(), message, args); | ||
|
||
/// <summary> | ||
/// Logs a message to AWS CloudWatch Logs. | ||
/// | ||
/// Logging will not be done: | ||
/// If the role provided to the function does not have sufficient permissions. | ||
/// </summary> | ||
/// <param name="level">The log level of the message</param> | ||
/// <param name="exception">Exception to include with the logging.</param> | ||
/// <param name="message">Message to log. The message may have format arguments.</param> | ||
/// <param name="args">Arguments to format the message with.</param> | ||
[RequiresPreviewFeatures(ParameterizedPreviewMessage)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this here but not in others? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because this PR adds the implementation of these methods in Amazon.Lambda.RuntimeSupport and we will need to wait till that change in Amazon.Lambda.RuntimeSupport gets deployed to managed runtime. Ideally I should have added these at the same time I did the versions that didn't take an Exception. That was a miss on my part that I'm fixing. |
||
public static void Log(string level, Exception exception, string message, params object[] args) | ||
{ | ||
_loggingWithLevelAndExceptionAction(level, exception, message, args); | ||
} | ||
|
||
/// <summary> | ||
/// Logs a message to AWS CloudWatch Logs. | ||
/// | ||
/// Logging will not be done: | ||
/// If the role provided to the function does not have sufficient permissions. | ||
/// </summary> | ||
/// <param name="level">The log level of the message</param> | ||
/// <param name="exception">Exception to include with the logging.</param> | ||
/// <param name="message">Message to log. The message may have format arguments.</param> | ||
/// <param name="args">Arguments to format the message with.</param> | ||
[RequiresPreviewFeatures(ParameterizedPreviewMessage)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered above |
||
public static void Log(LogLevel level, Exception exception, string message, params object[] args) => Log(level.ToString(), exception, message, args); | ||
#endif | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
|
||
<PropertyGroup> | ||
<Description>Amazon Lambda .NET Core support - Logging ASP.NET Core package.</Description> | ||
<TargetFramework>netstandard2.0</TargetFramework> | ||
<TargetFrameworks>net6.0;net8.0</TargetFrameworks> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need to add support for net6? Since we are doing a major version, might as well go all the way to net8 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what our usage is still on .NET 6 and I'm not ready to make that call for this update. Also this change would spiral with having to remove .NET 6 from the Amazon.Lambda.AspNetCoreServer and Amazon.Lambda.AspNetCoreServer.Hosting. |
||
<AssemblyTitle>Amazon.Lambda.Logging.AspNetCore</AssemblyTitle> | ||
<Version>3.1.1</Version> | ||
<AssemblyName>Amazon.Lambda.Logging.AspNetCore</AssemblyName> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
using System; | ||
using System; | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.Extensions.Logging | ||
|
@@ -28,54 +28,85 @@ public bool IsEnabled(LogLevel logLevel) | |
_options.Filter == null || | ||
_options.Filter(_categoryName, logLevel)); | ||
} | ||
|
||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | ||
{ | ||
if (formatter == null) | ||
{ | ||
throw new ArgumentNullException(nameof(formatter)); | ||
} | ||
|
||
if (!IsEnabled(logLevel)) | ||
{ | ||
return; | ||
} | ||
|
||
// Format of the logged text, optional components are in {} | ||
// {[LogLevel] }{ => Scopes : }{Category: }{EventId: }MessageText {Exception}{\n} | ||
|
||
var components = new List<string>(4); | ||
if (_options.IncludeLogLevel) | ||
{ | ||
components.Add($"[{logLevel}]"); | ||
} | ||
|
||
GetScopeInformation(components); | ||
|
||
if (_options.IncludeCategory) | ||
{ | ||
components.Add($"{_categoryName}:"); | ||
} | ||
if (_options.IncludeEventId) | ||
{ | ||
components.Add($"[{eventId}]:"); | ||
} | ||
|
||
var text = formatter.Invoke(state, exception); | ||
components.Add(text); | ||
|
||
if (_options.IncludeException) | ||
{ | ||
components.Add($"{exception}"); | ||
} | ||
if (_options.IncludeNewline) | ||
{ | ||
components.Add(Environment.NewLine); | ||
} | ||
|
||
var finalText = string.Join(" ", components); | ||
Amazon.Lambda.Core.LambdaLogger.Log(finalText); | ||
} | ||
|
||
/// <summary> | ||
/// The Log method called by the ILogger framework to log message to logger's target. In the Lambda case the formatted logging will be | ||
/// sent to the Amazon.Lambda.Core.LambdaLogger's Log method. | ||
/// </summary> | ||
/// <typeparam name="TState"></typeparam> | ||
/// <param name="logLevel"></param> | ||
/// <param name="eventId"></param> | ||
/// <param name="state"></param> | ||
/// <param name="exception"></param> | ||
/// <param name="formatter"></param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add documentation to this method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
{ | ||
if (formatter == null) | ||
{ | ||
throw new ArgumentNullException(nameof(formatter)); | ||
} | ||
|
||
if (!IsEnabled(logLevel)) | ||
{ | ||
return; | ||
} | ||
|
||
var components = new List<string>(4); | ||
if (_options.IncludeLogLevel) | ||
{ | ||
components.Add($"[{logLevel}]"); | ||
} | ||
|
||
GetScopeInformation(components); | ||
|
||
if (_options.IncludeCategory) | ||
{ | ||
components.Add($"{_categoryName}:"); | ||
} | ||
if (_options.IncludeEventId) | ||
{ | ||
components.Add($"[{eventId}]:"); | ||
} | ||
|
||
var text = formatter.Invoke(state, exception); | ||
components.Add(text); | ||
|
||
if (_options.IncludeException) | ||
{ | ||
components.Add($"{exception}"); | ||
} | ||
if (_options.IncludeNewline) | ||
{ | ||
components.Add(Environment.NewLine); | ||
} | ||
|
||
var finalText = string.Join(" ", components); | ||
|
||
var lambdaLogLevel = ConvertLogLevel(logLevel); | ||
Amazon.Lambda.Core.LambdaLogger.Log(lambdaLogLevel, finalText); | ||
} | ||
|
||
private static Amazon.Lambda.Core.LogLevel ConvertLogLevel(LogLevel logLevel) | ||
{ | ||
switch (logLevel) | ||
{ | ||
case LogLevel.Trace: | ||
return Amazon.Lambda.Core.LogLevel.Trace; | ||
case LogLevel.Debug: | ||
return Amazon.Lambda.Core.LogLevel.Debug; | ||
case LogLevel.Information: | ||
return Amazon.Lambda.Core.LogLevel.Information; | ||
case LogLevel.Warning: | ||
return Amazon.Lambda.Core.LogLevel.Warning; | ||
case LogLevel.Error: | ||
return Amazon.Lambda.Core.LogLevel.Error; | ||
case LogLevel.Critical: | ||
return Amazon.Lambda.Core.LogLevel.Critical; | ||
default: | ||
return Amazon.Lambda.Core.LogLevel.Information; | ||
} | ||
} | ||
|
||
private void GetScopeInformation(List<string> logMessageComponents) | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,8 @@ internal class UserCodeLoader | |
{ | ||
private const string UserInvokeException = "An exception occurred while invoking customer handler."; | ||
private const string LambdaLoggingActionFieldName = "_loggingAction"; | ||
private const string LambdaLoggingWithLevelActionFieldName = "_loggingWithLevelAction"; | ||
private const string LambdaLoggingWithLevelAndExceptionActionFieldName = "_loggingWithLevelAndExceptionAction"; | ||
|
||
internal const string LambdaCoreAssemblyName = "Amazon.Lambda.Core"; | ||
|
||
|
@@ -148,6 +150,13 @@ public void Invoke(Stream lambdaData, ILambdaContext lambdaContext, Stream outSt | |
_invokeDelegate(lambdaData, lambdaContext, outStream); | ||
} | ||
|
||
/// <summary> | ||
/// Sets the backing logger action field in Amazon.Logging.Core to redirect logs into Amazon.Lambda.RuntimeSupport. | ||
/// </summary> | ||
/// <param name="coreAssembly"></param> | ||
/// <param name="customerLoggingAction"></param> | ||
/// <param name="internalLogger"></param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
internal static void SetCustomerLoggerLogAction(Assembly coreAssembly, Action<string> customerLoggingAction, InternalLogger internalLogger) | ||
{ | ||
if (coreAssembly == null) | ||
|
@@ -186,6 +195,97 @@ internal static void SetCustomerLoggerLogAction(Assembly coreAssembly, Action<st | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Sets the backing logger action field in Amazon.Logging.Core to redirect logs into Amazon.Lambda.RuntimeSupport. | ||
/// </summary> | ||
/// <param name="coreAssembly"></param> | ||
/// <param name="loggingWithLevelAction"></param> | ||
/// <param name="internalLogger"></param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
|
||
internal static void SetCustomerLoggerLogAction(Assembly coreAssembly, Action<string, string, object[]> loggingWithLevelAction, InternalLogger internalLogger) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add documentation for this method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
{ | ||
if (coreAssembly == null) | ||
{ | ||
throw new ArgumentNullException(nameof(coreAssembly)); | ||
} | ||
|
||
if (loggingWithLevelAction == null) | ||
{ | ||
throw new ArgumentNullException(nameof(loggingWithLevelAction)); | ||
} | ||
|
||
internalLogger.LogDebug($"UCL : Retrieving type '{Types.LambdaLoggerTypeName}'"); | ||
var lambdaILoggerType = coreAssembly.GetType(Types.LambdaLoggerTypeName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isnt this library NativeAOT compatible? Why are you using reflection here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
if (lambdaILoggerType == null) | ||
{ | ||
throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.Internal.UnableToLocateType, Types.LambdaLoggerTypeName); | ||
} | ||
|
||
internalLogger.LogDebug($"UCL : Retrieving field '{LambdaLoggingWithLevelActionFieldName}'"); | ||
var loggingActionField = lambdaILoggerType.GetTypeInfo().GetField(LambdaLoggingWithLevelActionFieldName, BindingFlags.NonPublic | BindingFlags.Static); | ||
if (loggingActionField == null) | ||
{ | ||
throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.Internal.UnableToRetrieveField, LambdaLoggingWithLevelActionFieldName, Types.LambdaLoggerTypeName); | ||
} | ||
|
||
internalLogger.LogDebug($"UCL : Setting field '{LambdaLoggingWithLevelActionFieldName}'"); | ||
try | ||
{ | ||
loggingActionField.SetValue(null, loggingWithLevelAction); | ||
} | ||
catch (Exception e) | ||
{ | ||
throw LambdaExceptions.ValidationException(e, Errors.UserCodeLoader.Internal.UnableToSetField, | ||
Types.LambdaLoggerTypeName, LambdaLoggingWithLevelActionFieldName); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Sets the backing logger action field in Amazon.Logging.Core to redirect logs into Amazon.Lambda.RuntimeSupport. | ||
/// </summary> | ||
/// <param name="coreAssembly"></param> | ||
/// <param name="loggingWithAndExceptionLevelAction"></param> | ||
/// <param name="internalLogger"></param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
internal static void SetCustomerLoggerLogAction(Assembly coreAssembly, Action<string, Exception, string, object[]> loggingWithAndExceptionLevelAction, InternalLogger internalLogger) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add docs for this method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
{ | ||
if (coreAssembly == null) | ||
{ | ||
throw new ArgumentNullException(nameof(coreAssembly)); | ||
} | ||
|
||
if (loggingWithAndExceptionLevelAction == null) | ||
{ | ||
throw new ArgumentNullException(nameof(loggingWithAndExceptionLevelAction)); | ||
} | ||
|
||
internalLogger.LogDebug($"UCL : Retrieving type '{Types.LambdaLoggerTypeName}'"); | ||
var lambdaILoggerType = coreAssembly.GetType(Types.LambdaLoggerTypeName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question abotut reflection There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Answered above |
||
if (lambdaILoggerType == null) | ||
{ | ||
throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.Internal.UnableToLocateType, Types.LambdaLoggerTypeName); | ||
} | ||
|
||
internalLogger.LogDebug($"UCL : Retrieving field '{LambdaLoggingWithLevelAndExceptionActionFieldName}'"); | ||
var loggingActionField = lambdaILoggerType.GetTypeInfo().GetField(LambdaLoggingWithLevelAndExceptionActionFieldName, BindingFlags.NonPublic | BindingFlags.Static); | ||
if (loggingActionField == null) | ||
{ | ||
throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.Internal.UnableToRetrieveField, LambdaLoggingWithLevelAndExceptionActionFieldName, Types.LambdaLoggerTypeName); | ||
} | ||
|
||
internalLogger.LogDebug($"UCL : Setting field '{LambdaLoggingWithLevelAndExceptionActionFieldName}'"); | ||
try | ||
{ | ||
loggingActionField.SetValue(null, loggingWithAndExceptionLevelAction); | ||
} | ||
catch (Exception e) | ||
{ | ||
throw LambdaExceptions.ValidationException(e, Errors.UserCodeLoader.Internal.UnableToSetField, | ||
Types.LambdaLoggerTypeName, LambdaLoggingWithLevelAndExceptionActionFieldName); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Constructs customer-specified serializer, specified either on the method, | ||
/// the assembly, or not specified at all. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are these no longer needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the implementation in Amazon.Lambda.RuntimeSupport of these methods have been deployed to the managed runtimes. That were marked as preview till that deployment was done.