Skip to content

Commit 1583a26

Browse files
authored
Lambda Logging improvements (#2062)
1 parent 1727317 commit 1583a26

File tree

19 files changed

+528
-129
lines changed

19 files changed

+528
-129
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Amazon.Lambda.RuntimeSupport",
5+
"Type": "Patch",
6+
"ChangelogMessages": [
7+
"Add support for parameterized logging method with exception to global logger LambdaLogger in Amazon.Lambda.Core"
8+
]
9+
},
10+
{
11+
"Name": "Amazon.Lambda.Core",
12+
"Type": "Minor",
13+
"ChangelogMessages": [
14+
"Added log level version of the static logging functions on Amazon.Lambda.Core.LambdaLogger"
15+
]
16+
}
17+
{
18+
"Name": "Amazon.Lambda.AspNetCoreServer",
19+
"Type": "Patch",
20+
"ChangelogMessages": [
21+
"Update Amazon.Lambda.Logging.AspNetCore dependency"
22+
]
23+
},
24+
{
25+
"Name": "Amazon.Lambda.AspNetCoreServer.Hosting",
26+
"Type": "Patch",
27+
"ChangelogMessages": [
28+
"Update Amazon.Lambda.Logging.AspNetCore dependency"
29+
]
30+
},
31+
{
32+
"Name": "Amazon.Lambda.Logging.AspNetCore",
33+
"Type": "Major",
34+
"ChangelogMessages": [
35+
"Add support Lambda log levels",
36+
"Change build target from .NET Standard 2.0 to .NET 6 and NET 8 to match Amazon.Lambda.AspNetCoreServer"
37+
]
38+
},
39+
{
40+
"Name": "Amazon.Lambda.TestUtilities",
41+
"Type": "Major",
42+
"ChangelogMessages": [
43+
"Update Amazon.Lambda.TestUtitlies to have implementation of the newer logging methods",
44+
"Change build target from .NET Standard 2.0 to .NET 6 and NET 8 to match Amazon.Lambda.AspNetCoreServer"
45+
]
46+
}
47+
]
48+
}

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

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public static void Log(string message)
4242
// value with an Action that directs the logging into its logging system.
4343
#pragma warning disable IDE0044 // Add readonly modifier
4444
private static Action<string, string, object[]> _loggingWithLevelAction = LogWithLevelToConsole;
45+
private static Action<string, Exception, string, object[]> _loggingWithLevelAndExceptionAction = LogWithLevelAndExceptionToConsole;
4546
#pragma warning restore IDE0044 // Add readonly modifier
4647

4748
// Logs message to console
@@ -65,6 +66,15 @@ private static void LogWithLevelToConsole(string level, string message, params o
6566
Console.WriteLine(sb.ToString());
6667
}
6768

69+
private static void LogWithLevelAndExceptionToConsole(string level, Exception exception, string message, params object[] args)
70+
{
71+
// Formatting here is not important, it is used for debugging Amazon.Lambda.Core only.
72+
// In a real scenario Amazon.Lambda.RuntimeSupport will change the value of _loggingWithLevelAction
73+
// to an Action inside it's logging system to handle the real formatting.
74+
LogWithLevelToConsole(level, message, args);
75+
Console.WriteLine(exception);
76+
}
77+
6878
private const string ParameterizedPreviewMessage =
6979
"This method has been mark as preview till the Lambda .NET Managed runtime has been updated with the backing implementation of this method. " +
7080
"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
7888
/// <param name="level">The log level of the message</param>
7989
/// <param name="message">Message to log. The message may have format arguments.</param>
8090
/// <param name="args">Arguments to format the message with.</param>
81-
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
8291
public static void Log(string level, string message, params object[] args)
8392
{
8493
_loggingWithLevelAction(level, message, args);
@@ -93,8 +102,36 @@ public static void Log(string level, string message, params object[] args)
93102
/// <param name="level">The log level of the message</param>
94103
/// <param name="message">Message to log. The message may have format arguments.</param>
95104
/// <param name="args">Arguments to format the message with.</param>
96-
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
97105
public static void Log(LogLevel level, string message, params object[] args) => Log(level.ToString(), message, args);
106+
107+
/// <summary>
108+
/// Logs a message to AWS CloudWatch Logs.
109+
///
110+
/// Logging will not be done:
111+
/// If the role provided to the function does not have sufficient permissions.
112+
/// </summary>
113+
/// <param name="level">The log level of the message</param>
114+
/// <param name="exception">Exception to include with the logging.</param>
115+
/// <param name="message">Message to log. The message may have format arguments.</param>
116+
/// <param name="args">Arguments to format the message with.</param>
117+
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
118+
public static void Log(string level, Exception exception, string message, params object[] args)
119+
{
120+
_loggingWithLevelAndExceptionAction(level, exception, message, args);
121+
}
122+
123+
/// <summary>
124+
/// Logs a message to AWS CloudWatch Logs.
125+
///
126+
/// Logging will not be done:
127+
/// If the role provided to the function does not have sufficient permissions.
128+
/// </summary>
129+
/// <param name="level">The log level of the message</param>
130+
/// <param name="exception">Exception to include with the logging.</param>
131+
/// <param name="message">Message to log. The message may have format arguments.</param>
132+
/// <param name="args">Arguments to format the message with.</param>
133+
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
134+
public static void Log(LogLevel level, Exception exception, string message, params object[] args) => Log(level.ToString(), exception, message, args);
98135
#endif
99136
}
100137
}

Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<PropertyGroup>
66
<Description>Amazon Lambda .NET Core support - Logging ASP.NET Core package.</Description>
7-
<TargetFramework>netstandard2.0</TargetFramework>
7+
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
88
<AssemblyTitle>Amazon.Lambda.Logging.AspNetCore</AssemblyTitle>
99
<Version>3.1.1</Version>
1010
<AssemblyName>Amazon.Lambda.Logging.AspNetCore</AssemblyName>
Lines changed: 135 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,109 +1,140 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33

44
namespace Microsoft.Extensions.Logging
55
{
6-
internal class LambdaILogger : ILogger
7-
{
8-
// Private fields
9-
private readonly string _categoryName;
10-
private readonly LambdaLoggerOptions _options;
11-
12-
13-
internal IExternalScopeProvider ScopeProvider { get; set; }
14-
15-
// Constructor
16-
public LambdaILogger(string categoryName, LambdaLoggerOptions options)
17-
{
18-
_categoryName = categoryName;
19-
_options = options;
20-
}
21-
22-
// ILogger methods
23-
public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? new NoOpDisposable();
24-
25-
public bool IsEnabled(LogLevel logLevel)
26-
{
27-
return (
28-
_options.Filter == null ||
29-
_options.Filter(_categoryName, logLevel));
30-
}
31-
32-
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
33-
{
34-
if (formatter == null)
35-
{
36-
throw new ArgumentNullException(nameof(formatter));
37-
}
38-
39-
if (!IsEnabled(logLevel))
40-
{
41-
return;
42-
}
43-
44-
// Format of the logged text, optional components are in {}
45-
// {[LogLevel] }{ => Scopes : }{Category: }{EventId: }MessageText {Exception}{\n}
46-
47-
var components = new List<string>(4);
48-
if (_options.IncludeLogLevel)
49-
{
50-
components.Add($"[{logLevel}]");
51-
}
52-
53-
GetScopeInformation(components);
54-
55-
if (_options.IncludeCategory)
56-
{
57-
components.Add($"{_categoryName}:");
58-
}
59-
if (_options.IncludeEventId)
60-
{
61-
components.Add($"[{eventId}]:");
62-
}
63-
64-
var text = formatter.Invoke(state, exception);
65-
components.Add(text);
66-
67-
if (_options.IncludeException)
68-
{
69-
components.Add($"{exception}");
70-
}
71-
if (_options.IncludeNewline)
72-
{
73-
components.Add(Environment.NewLine);
74-
}
75-
76-
var finalText = string.Join(" ", components);
77-
Amazon.Lambda.Core.LambdaLogger.Log(finalText);
78-
}
79-
80-
private void GetScopeInformation(List<string> logMessageComponents)
81-
{
82-
var scopeProvider = ScopeProvider;
83-
84-
if (_options.IncludeScopes && scopeProvider != null)
85-
{
86-
var initialCount = logMessageComponents.Count;
87-
88-
scopeProvider.ForEachScope((scope, list) =>
89-
{
90-
list.Add(scope.ToString());
91-
}, (logMessageComponents));
92-
93-
if (logMessageComponents.Count > initialCount)
94-
{
95-
logMessageComponents.Add("=>");
96-
}
97-
}
98-
}
99-
100-
// Private classes
101-
private class NoOpDisposable : IDisposable
102-
{
103-
public void Dispose()
104-
{
105-
}
106-
}
107-
108-
}
6+
internal class LambdaILogger : ILogger
7+
{
8+
// Private fields
9+
private readonly string _categoryName;
10+
private readonly LambdaLoggerOptions _options;
11+
12+
13+
internal IExternalScopeProvider ScopeProvider { get; set; }
14+
15+
// Constructor
16+
public LambdaILogger(string categoryName, LambdaLoggerOptions options)
17+
{
18+
_categoryName = categoryName;
19+
_options = options;
20+
}
21+
22+
// ILogger methods
23+
public IDisposable BeginScope<TState>(TState state) => ScopeProvider?.Push(state) ?? new NoOpDisposable();
24+
25+
public bool IsEnabled(LogLevel logLevel)
26+
{
27+
return (
28+
_options.Filter == null ||
29+
_options.Filter(_categoryName, logLevel));
30+
}
31+
32+
/// <summary>
33+
/// The Log method called by the ILogger framework to log message to logger's target. In the Lambda case the formatted logging will be
34+
/// sent to the Amazon.Lambda.Core.LambdaLogger's Log method.
35+
/// </summary>
36+
/// <typeparam name="TState"></typeparam>
37+
/// <param name="logLevel"></param>
38+
/// <param name="eventId"></param>
39+
/// <param name="state"></param>
40+
/// <param name="exception"></param>
41+
/// <param name="formatter"></param>
42+
/// <exception cref="ArgumentNullException"></exception>
43+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
44+
{
45+
if (formatter == null)
46+
{
47+
throw new ArgumentNullException(nameof(formatter));
48+
}
49+
50+
if (!IsEnabled(logLevel))
51+
{
52+
return;
53+
}
54+
55+
var components = new List<string>(4);
56+
if (_options.IncludeLogLevel)
57+
{
58+
components.Add($"[{logLevel}]");
59+
}
60+
61+
GetScopeInformation(components);
62+
63+
if (_options.IncludeCategory)
64+
{
65+
components.Add($"{_categoryName}:");
66+
}
67+
if (_options.IncludeEventId)
68+
{
69+
components.Add($"[{eventId}]:");
70+
}
71+
72+
var text = formatter.Invoke(state, exception);
73+
components.Add(text);
74+
75+
if (_options.IncludeException)
76+
{
77+
components.Add($"{exception}");
78+
}
79+
if (_options.IncludeNewline)
80+
{
81+
components.Add(Environment.NewLine);
82+
}
83+
84+
var finalText = string.Join(" ", components);
85+
86+
var lambdaLogLevel = ConvertLogLevel(logLevel);
87+
Amazon.Lambda.Core.LambdaLogger.Log(lambdaLogLevel, finalText);
88+
}
89+
90+
private static Amazon.Lambda.Core.LogLevel ConvertLogLevel(LogLevel logLevel)
91+
{
92+
switch (logLevel)
93+
{
94+
case LogLevel.Trace:
95+
return Amazon.Lambda.Core.LogLevel.Trace;
96+
case LogLevel.Debug:
97+
return Amazon.Lambda.Core.LogLevel.Debug;
98+
case LogLevel.Information:
99+
return Amazon.Lambda.Core.LogLevel.Information;
100+
case LogLevel.Warning:
101+
return Amazon.Lambda.Core.LogLevel.Warning;
102+
case LogLevel.Error:
103+
return Amazon.Lambda.Core.LogLevel.Error;
104+
case LogLevel.Critical:
105+
return Amazon.Lambda.Core.LogLevel.Critical;
106+
default:
107+
return Amazon.Lambda.Core.LogLevel.Information;
108+
}
109+
}
110+
111+
private void GetScopeInformation(List<string> logMessageComponents)
112+
{
113+
var scopeProvider = ScopeProvider;
114+
115+
if (_options.IncludeScopes && scopeProvider != null)
116+
{
117+
var initialCount = logMessageComponents.Count;
118+
119+
scopeProvider.ForEachScope((scope, list) =>
120+
{
121+
list.Add(scope.ToString());
122+
}, (logMessageComponents));
123+
124+
if (logMessageComponents.Count > initialCount)
125+
{
126+
logMessageComponents.Add("=>");
127+
}
128+
}
129+
}
130+
131+
// Private classes
132+
private class NoOpDisposable : IDisposable
133+
{
134+
public void Dispose()
135+
{
136+
}
137+
}
138+
139+
}
109140
}

Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaLoggerOptions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Microsoft.Extensions.Configuration;
1+
using Microsoft.Extensions.Configuration;
22
using System;
33
using System.Collections.Generic;
44
using System.Linq;
@@ -18,8 +18,8 @@ public class LambdaLoggerOptions
1818
private const string INCLUDE_CATEGORY_KEY = "IncludeCategory";
1919
private const string INCLUDE_NEWLINE_KEY = "IncludeNewline";
2020
private const string INCLUDE_EXCEPTION_KEY = "IncludeException";
21-
private const string INCLUDE_EVENT_ID_KEY = "IncludeEventId";
22-
private const string INCLUDE_SCOPES_KEY = "IncludeScopes";
21+
private const string INCLUDE_EVENT_ID_KEY = "IncludeEventId";
22+
private const string INCLUDE_SCOPES_KEY = "IncludeScopes";
2323
private const string LOG_LEVEL_KEY = "LogLevel";
2424
private const string DEFAULT_CATEGORY = "Default";
2525

@@ -60,7 +60,7 @@ public class LambdaLoggerOptions
6060
/// </summary>
6161
public bool IncludeScopes { get; set; }
6262

63-
/// <summary>
63+
/// <summary>
6464
/// Function used to filter events based on the log level.
6565
/// Default value is null and will instruct logger to log everything.
6666
/// </summary>

0 commit comments

Comments
 (0)