Skip to content

Update global logger to support logging with level and arguments. #2005

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

Merged
merged 2 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .autover/changes/d0822afd-daf4-437a-9437-2a9492c135b7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"Projects": [
{
"Name": "Amazon.Lambda.RuntimeSupport",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to have 2 entries for RuntimeSupport? Seems like both can be combined into 1.

Copy link
Member Author

@normj normj Mar 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The second one is supposed to be Amazon.Lambda.Core which I marked as preview since the API is in preview status. It will get a minor version bump once the API is out of preview.

"Type": "Minor",
"ChangelogMessages": [
"Add support for parameterized logging method to global logger LambdaLogger in Amazon.Lambda.Core"
]
},
{
"Name": "Amazon.Lambda.RuntimeSupport",
"Type": "Patch",
"ChangelogMessages": [
"Add support for parameterized logging method to global logger LambdaLogger. Method is marked as preview till new version of Amazon.Lambda.RuntimeSupport is deployed to managed runtime."
]
}
]
}
71 changes: 69 additions & 2 deletions Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System;
using System.Reflection.Emit;
using System.Runtime.Versioning;
using System.Text;

namespace Amazon.Lambda.Core
{
Expand All @@ -9,8 +12,11 @@ namespace Amazon.Lambda.Core
/// </summary>
public static class LambdaLogger
{
// Logging action, logs to Console by default
// The name of this field must not change or be readonly because Amazon.Runtime.Support will use reflection to replace the
// value with an Action that directs the logging into its logging system.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong name "Amazon.Runtime.Support"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

#pragma warning disable IDE0044 // Add readonly modifier
private static Action<string> _loggingAction = LogToConsole;
#pragma warning restore IDE0044 // Add readonly modifier

// Logs message to console
private static void LogToConsole(string message)
Expand All @@ -29,5 +35,66 @@ public static void Log(string message)
{
_loggingAction(message);
}

#if NET6_0_OR_GREATER

// The name of this field must not change or be readonly because Amazon.Runtime.Support will use reflection to replace the
// value with an Action that directs the logging into its logging system.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong name "Amazon.Runtime.Support"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

#pragma warning disable IDE0044 // Add readonly modifier
private static Action<string, string, object[]> _loggingWithLevelAction = LogWithLevelToConsole;
#pragma warning restore IDE0044 // Add readonly modifier

// Logs message to console
private static void LogWithLevelToConsole(string level, string message, params object[] args)
{
// Formatting here is not important, it is used for debugging Amazon.Lambda.Core only.
// In a real scenario Amazon.Runtime.Support will change the value of _loggingWithLevelAction
// to an Action inside it's logging system to handle the real formatting.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong name "Amazon.Runtime.Support"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

var sb = new StringBuilder();
sb.Append(level).Append(": ").Append(message);
if (args?.Length > 0)
{
sb.Append(" Arguments:");
foreach(var arg in args)
{
sb.Append(" \"");
sb.Append(arg);
sb.Append("\"");
}
}
Console.WriteLine(sb.ToString());
}

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 whilein preview if the Lambda function is deployed as an executable and uses the latest version of Amazon.Lambda.RuntimeSupport.";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo in "whilein"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

/// <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="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);
}

/// <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="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);
#endif
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
Expand Down Expand Up @@ -240,16 +240,25 @@ private void Initialize(TextWriter stdOutWriter, TextWriter stdErrorWriter)
/// </summary>
private void ConfigureLoggingActionField()
{
var lambdaILoggerType = typeof(Amazon.Lambda.Core.LambdaLogger);
if (lambdaILoggerType == null)
var lambdaLoggerType = typeof(Amazon.Lambda.Core.LambdaLogger);
if (lambdaLoggerType == null)
return;

var loggingActionField = lambdaILoggerType.GetTypeInfo().GetField("_loggingAction", BindingFlags.NonPublic | BindingFlags.Static);
if (loggingActionField == null)
return;
var loggingActionField = lambdaLoggerType.GetTypeInfo().GetField("_loggingAction", BindingFlags.NonPublic | BindingFlags.Static);
if (loggingActionField != null)
{
Action<string> loggingAction = (message => FormattedWriteLine(null, message));
loggingActionField.SetValue(null, loggingAction);
}


Action<string> callback = (message => FormattedWriteLine(null, message));
loggingActionField.SetValue(null, callback);
var loggingWithLevelActionField = lambdaLoggerType.GetTypeInfo().GetField("_loggingWithLevelAction", BindingFlags.NonPublic | BindingFlags.Static);
if (loggingWithLevelActionField != null)
{
Action<string, string, object[]> loggingWithLevelAction = ((level, message, args) => FormattedWriteLine(level, message, args));
loggingWithLevelActionField.SetValue(null, loggingWithLevelAction);

}
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
Expand Down Expand Up @@ -106,6 +106,7 @@ protected virtual async Task TestAllHandlersAsync()
await RunTestSuccessAsync(lambdaClient, "UnintendedDisposeTest", "not-used", "UnintendedDisposeTest-SUCCESS");
await RunTestSuccessAsync(lambdaClient, "LoggingStressTest", "not-used", "LoggingStressTest-success");

await RunGlobalLoggingTestAsync(lambdaClient, "GlobalLoggingTest");
await RunJsonLoggingWithUnhandledExceptionAsync(lambdaClient);

await RunLoggingTestAsync(lambdaClient, "LoggingTest", RuntimeLogLevel.Trace, LogConfigSource.LambdaAPI);
Expand All @@ -121,7 +122,6 @@ protected virtual async Task TestAllHandlersAsync()
await RunLoggingTestAsync(lambdaClient, "LoggingTest", RuntimeLogLevel.Error, LogConfigSource.DotnetEnvironment);
await RunLoggingTestAsync(lambdaClient, "LoggingTest", RuntimeLogLevel.Critical, LogConfigSource.DotnetEnvironment);


await RunUnformattedLoggingTestAsync(lambdaClient, "LoggingTest");

await RunTestSuccessAsync(lambdaClient, "ToUpperAsync", "message", "ToUpperAsync-MESSAGE");
Expand Down Expand Up @@ -317,6 +317,19 @@ private async Task RunLoggingTestAsync(AmazonLambdaClient lambdaClient, string h
}
}

private async Task RunGlobalLoggingTestAsync(AmazonLambdaClient lambdaClient, string handler)
{
await UpdateHandlerAsync(lambdaClient, handler);

var invokeResponse = await InvokeFunctionAsync(lambdaClient, JsonConvert.SerializeObject(""));
Assert.True(invokeResponse.HttpStatusCode == System.Net.HttpStatusCode.OK);
Assert.True(invokeResponse.FunctionError == null);

var log = System.Text.UTF8Encoding.UTF8.GetString(Convert.FromBase64String(invokeResponse.LogResult));

Assert.Contains("This is a global log message with foobar as an argument", log);
}

private async Task RunUnformattedLoggingTestAsync(AmazonLambdaClient lambdaClient, string handler)
{
var environmentVariables = new Dictionary<string, string>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Amazon.Lambda.Core;
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;
using System;
Expand Down Expand Up @@ -48,6 +48,9 @@ private static async Task Main(string[] args)
case nameof(LoggingStressTest):
bootstrap = new LambdaBootstrap(LoggingStressTest);
break;
case nameof(GlobalLoggingTest):
bootstrap = new LambdaBootstrap(GlobalLoggingTest);
break;
case nameof(LoggingTest):
bootstrap = new LambdaBootstrap(LoggingTest);
break;
Expand Down Expand Up @@ -169,6 +172,15 @@ Task UseLoggerAsync()
return Task.FromResult(GetInvocationResponse(nameof(LoggingStressTest), "success"));
}


private static Task<InvocationResponse> GlobalLoggingTest(InvocationRequest invocation)
{
#pragma warning disable CA2252
LambdaLogger.Log(LogLevel.Information, "This is a global log message with {argument} as an argument", "foobar");
#pragma warning restore CA2252
return Task.FromResult(GetInvocationResponse(nameof(GlobalLoggingTest), true));
}

private static Task<InvocationResponse> LoggingTest(InvocationRequest invocation)
{
invocation.LambdaContext.Logger.LogTrace("A trace log");
Expand Down
Loading