Skip to content

Commit b0d7cce

Browse files
authored
Update global logger to support logging with level and arguments. (#2005)
1 parent 70f35d6 commit b0d7cce

File tree

5 files changed

+132
-13
lines changed

5 files changed

+132
-13
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Amazon.Lambda.RuntimeSupport",
5+
"Type": "Minor",
6+
"ChangelogMessages": [
7+
"Add support for parameterized logging method to global logger LambdaLogger in Amazon.Lambda.Core"
8+
]
9+
},
10+
{
11+
"Name": "Amazon.Lambda.Core",
12+
"Type": "Patch",
13+
"ChangelogMessages": [
14+
"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."
15+
]
16+
}
17+
]
18+
}

Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs

+69-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using System;
1+
using System;
2+
using System.Reflection.Emit;
3+
using System.Runtime.Versioning;
4+
using System.Text;
25

36
namespace Amazon.Lambda.Core
47
{
@@ -9,8 +12,11 @@ namespace Amazon.Lambda.Core
912
/// </summary>
1013
public static class LambdaLogger
1114
{
12-
// Logging action, logs to Console by default
15+
// The name of this field must not change or be readonly because Amazon.Lambda.RuntimeSupport will use reflection to replace the
16+
// value with an Action that directs the logging into its logging system.
17+
#pragma warning disable IDE0044 // Add readonly modifier
1318
private static Action<string> _loggingAction = LogToConsole;
19+
#pragma warning restore IDE0044 // Add readonly modifier
1420

1521
// Logs message to console
1622
private static void LogToConsole(string message)
@@ -29,5 +35,66 @@ public static void Log(string message)
2935
{
3036
_loggingAction(message);
3137
}
38+
39+
#if NET6_0_OR_GREATER
40+
41+
// The name of this field must not change or be readonly because Amazon.Lambda.RuntimeSupport will use reflection to replace the
42+
// value with an Action that directs the logging into its logging system.
43+
#pragma warning disable IDE0044 // Add readonly modifier
44+
private static Action<string, string, object[]> _loggingWithLevelAction = LogWithLevelToConsole;
45+
#pragma warning restore IDE0044 // Add readonly modifier
46+
47+
// Logs message to console
48+
private static void LogWithLevelToConsole(string level, string message, params object[] args)
49+
{
50+
// Formatting here is not important, it is used for debugging Amazon.Lambda.Core only.
51+
// In a real scenario Amazon.Lambda.RuntimeSupport will change the value of _loggingWithLevelAction
52+
// to an Action inside it's logging system to handle the real formatting.
53+
var sb = new StringBuilder();
54+
sb.Append(level).Append(": ").Append(message);
55+
if (args?.Length > 0)
56+
{
57+
sb.Append(" Arguments:");
58+
foreach(var arg in args)
59+
{
60+
sb.Append(" \"");
61+
sb.Append(arg);
62+
sb.Append("\"");
63+
}
64+
}
65+
Console.WriteLine(sb.ToString());
66+
}
67+
68+
private const string ParameterizedPreviewMessage =
69+
"This method has been mark as preview till the Lambda .NET Managed runtime has been updated with the backing implementation of this method. " +
70+
"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.";
71+
72+
/// <summary>
73+
/// Logs a message to AWS CloudWatch Logs.
74+
///
75+
/// Logging will not be done:
76+
/// If the role provided to the function does not have sufficient permissions.
77+
/// </summary>
78+
/// <param name="level">The log level of the message</param>
79+
/// <param name="message">Message to log. The message may have format arguments.</param>
80+
/// <param name="args">Arguments to format the message with.</param>
81+
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
82+
public static void Log(string level, string message, params object[] args)
83+
{
84+
_loggingWithLevelAction(level, message, args);
85+
}
86+
87+
/// <summary>
88+
/// Logs a message to AWS CloudWatch Logs.
89+
///
90+
/// Logging will not be done:
91+
/// If the role provided to the function does not have sufficient permissions.
92+
/// </summary>
93+
/// <param name="level">The log level of the message</param>
94+
/// <param name="message">Message to log. The message may have format arguments.</param>
95+
/// <param name="args">Arguments to format the message with.</param>
96+
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
97+
public static void Log(LogLevel level, string message, params object[] args) => Log(level.ToString(), message, args);
98+
#endif
3299
}
33100
}

Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/ConsoleLoggerWriter.cs

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License").
@@ -240,16 +240,25 @@ private void Initialize(TextWriter stdOutWriter, TextWriter stdErrorWriter)
240240
/// </summary>
241241
private void ConfigureLoggingActionField()
242242
{
243-
var lambdaILoggerType = typeof(Amazon.Lambda.Core.LambdaLogger);
244-
if (lambdaILoggerType == null)
243+
var lambdaLoggerType = typeof(Amazon.Lambda.Core.LambdaLogger);
244+
if (lambdaLoggerType == null)
245245
return;
246246

247-
var loggingActionField = lambdaILoggerType.GetTypeInfo().GetField("_loggingAction", BindingFlags.NonPublic | BindingFlags.Static);
248-
if (loggingActionField == null)
249-
return;
247+
var loggingActionField = lambdaLoggerType.GetTypeInfo().GetField("_loggingAction", BindingFlags.NonPublic | BindingFlags.Static);
248+
if (loggingActionField != null)
249+
{
250+
Action<string> loggingAction = (message => FormattedWriteLine(null, message));
251+
loggingActionField.SetValue(null, loggingAction);
252+
}
253+
250254

251-
Action<string> callback = (message => FormattedWriteLine(null, message));
252-
loggingActionField.SetValue(null, callback);
255+
var loggingWithLevelActionField = lambdaLoggerType.GetTypeInfo().GetField("_loggingWithLevelAction", BindingFlags.NonPublic | BindingFlags.Static);
256+
if (loggingWithLevelActionField != null)
257+
{
258+
Action<string, string, object[]> loggingWithLevelAction = ((level, message, args) => FormattedWriteLine(level, message, args));
259+
loggingWithLevelActionField.SetValue(null, loggingWithLevelAction);
260+
261+
}
253262
}
254263

255264
/// <inheritdoc/>

Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License").
@@ -106,6 +106,7 @@ protected virtual async Task TestAllHandlersAsync()
106106
await RunTestSuccessAsync(lambdaClient, "UnintendedDisposeTest", "not-used", "UnintendedDisposeTest-SUCCESS");
107107
await RunTestSuccessAsync(lambdaClient, "LoggingStressTest", "not-used", "LoggingStressTest-success");
108108

109+
await RunGlobalLoggingTestAsync(lambdaClient, "GlobalLoggingTest");
109110
await RunJsonLoggingWithUnhandledExceptionAsync(lambdaClient);
110111

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

124-
125125
await RunUnformattedLoggingTestAsync(lambdaClient, "LoggingTest");
126126

127127
await RunTestSuccessAsync(lambdaClient, "ToUpperAsync", "message", "ToUpperAsync-MESSAGE");
@@ -317,6 +317,19 @@ private async Task RunLoggingTestAsync(AmazonLambdaClient lambdaClient, string h
317317
}
318318
}
319319

320+
private async Task RunGlobalLoggingTestAsync(AmazonLambdaClient lambdaClient, string handler)
321+
{
322+
await UpdateHandlerAsync(lambdaClient, handler);
323+
324+
var invokeResponse = await InvokeFunctionAsync(lambdaClient, JsonConvert.SerializeObject(""));
325+
Assert.True(invokeResponse.HttpStatusCode == System.Net.HttpStatusCode.OK);
326+
Assert.True(invokeResponse.FunctionError == null);
327+
328+
var log = System.Text.UTF8Encoding.UTF8.GetString(Convert.FromBase64String(invokeResponse.LogResult));
329+
330+
Assert.Contains("This is a global log message with foobar as an argument", log);
331+
}
332+
320333
private async Task RunUnformattedLoggingTestAsync(AmazonLambdaClient lambdaClient, string handler)
321334
{
322335
var environmentVariables = new Dictionary<string, string>();

Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunction.cs

+13-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Amazon.Lambda.Core;
1+
using Amazon.Lambda.Core;
22
using Amazon.Lambda.RuntimeSupport;
33
using Amazon.Lambda.Serialization.SystemTextJson;
44
using System;
@@ -48,6 +48,9 @@ private static async Task Main(string[] args)
4848
case nameof(LoggingStressTest):
4949
bootstrap = new LambdaBootstrap(LoggingStressTest);
5050
break;
51+
case nameof(GlobalLoggingTest):
52+
bootstrap = new LambdaBootstrap(GlobalLoggingTest);
53+
break;
5154
case nameof(LoggingTest):
5255
bootstrap = new LambdaBootstrap(LoggingTest);
5356
break;
@@ -169,6 +172,15 @@ Task UseLoggerAsync()
169172
return Task.FromResult(GetInvocationResponse(nameof(LoggingStressTest), "success"));
170173
}
171174

175+
176+
private static Task<InvocationResponse> GlobalLoggingTest(InvocationRequest invocation)
177+
{
178+
#pragma warning disable CA2252
179+
LambdaLogger.Log(LogLevel.Information, "This is a global log message with {argument} as an argument", "foobar");
180+
#pragma warning restore CA2252
181+
return Task.FromResult(GetInvocationResponse(nameof(GlobalLoggingTest), true));
182+
}
183+
172184
private static Task<InvocationResponse> LoggingTest(InvocationRequest invocation)
173185
{
174186
invocation.LambdaContext.Logger.LogTrace("A trace log");

0 commit comments

Comments
 (0)