From cfedc29b689d110cac83d4f07943adfd843cfcf2 Mon Sep 17 00:00:00 2001 From: Tem Revil Date: Sat, 11 Jul 2026 02:59:26 +0300 Subject: [PATCH 1/2] Honor IncludeCategory in the JSON log format In text format the ILogger category is written on every line when LambdaLoggerOptions.IncludeCategory is true (the default), but the JSON branch of LambdaILogger.Log never emitted the category, so migrating a function from text to JSON logging silently lost it with no way to opt back in (#2469). When IncludeCategory is set, prepend a {Category} placeholder to the message template and supply the category value, so it surfaces as a queryable property in the JSON record instead of being dropped. Text format is unchanged, and JSON output is unchanged when IncludeCategory is false. Fixes #2469 --- .../LambdaILogger.cs | 136 ++++++++++-------- .../LoggingTests.cs | 70 +++++++++ 2 files changed, 143 insertions(+), 63 deletions(-) diff --git a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaILogger.cs b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaILogger.cs index 2898a8472..96191465d 100644 --- a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaILogger.cs +++ b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaILogger.cs @@ -2,32 +2,32 @@ using System.Collections.Generic; namespace Microsoft.Extensions.Logging -{ - internal class LambdaILogger : ILogger - { - // Private fields - private readonly string _categoryName; - private readonly LambdaLoggerOptions _options; - - - internal IExternalScopeProvider ScopeProvider { get; set; } - - // Constructor - public LambdaILogger(string categoryName, LambdaLoggerOptions options) - { - _categoryName = categoryName; - _options = options; - } - - // ILogger methods - public IDisposable BeginScope(TState state) => ScopeProvider?.Push(state) ?? new NoOpDisposable(); - - public bool IsEnabled(LogLevel logLevel) - { - return ( - _options.Filter == null || - _options.Filter(_categoryName, logLevel)); - } +{ + internal class LambdaILogger : ILogger + { + // Private fields + private readonly string _categoryName; + private readonly LambdaLoggerOptions _options; + + + internal IExternalScopeProvider ScopeProvider { get; set; } + + // Constructor + public LambdaILogger(string categoryName, LambdaLoggerOptions options) + { + _categoryName = categoryName; + _options = options; + } + + // ILogger methods + public IDisposable BeginScope(TState state) => ScopeProvider?.Push(state) ?? new NoOpDisposable(); + + public bool IsEnabled(LogLevel logLevel) + { + return ( + _options.Filter == null || + _options.Filter(_categoryName, logLevel)); + } /// /// The Log method called by the ILogger framework to log message to logger's target. In the Lambda case the formatted logging will be @@ -39,17 +39,17 @@ public bool IsEnabled(LogLevel logLevel) /// /// /// - /// - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - if (formatter == null) - { - throw new ArgumentNullException(nameof(formatter)); - } - - if (!IsEnabled(logLevel)) - { - return; + /// + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (formatter == null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + if (!IsEnabled(logLevel)) + { + return; } var lambdaLogLevel = ConvertLogLevel(logLevel); @@ -75,6 +75,16 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except messageTemplate = formatter.Invoke(state, exception); } + if (_options.IncludeCategory) + { + // Unlike the text format, the JSON format otherwise drops the + // category entirely, so IncludeCategory has no effect. Prepend a + // "{Category}" placeholder (which the JSON formatter turns into a + // queryable property) and supply the category value. + messageTemplate = "[{Category}] " + messageTemplate; + parameters.Insert(0, _categoryName); + } + Amazon.Lambda.Core.LambdaLogger.Log(lambdaLogLevel, exception, messageTemplate, parameters.ToArray()); } else @@ -133,26 +143,26 @@ private static Amazon.Lambda.Core.LogLevel ConvertLogLevel(LogLevel logLevel) default: return Amazon.Lambda.Core.LogLevel.Information; } - } - - private void GetScopeInformation(List logMessageComponents) - { - var scopeProvider = ScopeProvider; - - if (_options.IncludeScopes && scopeProvider != null) - { - var initialCount = logMessageComponents.Count; - - scopeProvider.ForEachScope((scope, list) => - { - list.Add(scope.ToString()); - }, (logMessageComponents)); - - if (logMessageComponents.Count > initialCount) - { - logMessageComponents.Add("=>"); - } - } + } + + private void GetScopeInformation(List logMessageComponents) + { + var scopeProvider = ScopeProvider; + + if (_options.IncludeScopes && scopeProvider != null) + { + var initialCount = logMessageComponents.Count; + + scopeProvider.ForEachScope((scope, list) => + { + list.Add(scope.ToString()); + }, (logMessageComponents)); + + if (logMessageComponents.Count > initialCount) + { + logMessageComponents.Add("=>"); + } + } } private bool IsLambdaJsonFormatEnabled @@ -164,12 +174,12 @@ private bool IsLambdaJsonFormatEnabled } // Private classes - private class NoOpDisposable : IDisposable - { - public void Dispose() - { - } + private class NoOpDisposable : IDisposable + { + public void Dispose() + { + } } - + } } diff --git a/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs b/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs index e9997cdf3..51f1626ba 100644 --- a/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs +++ b/Libraries/test/Amazon.Lambda.Logging.AspNetCore.Tests/LoggingTests.cs @@ -574,6 +574,76 @@ public void TestJSONParameterLogging() } + [Fact] + public void TestJSONParameterLoggingIncludesCategoryWhenEnabled() + { + Environment.SetEnvironmentVariable("AWS_LAMBDA_LOG_FORMAT", "JSON"); + try + { + using (var writer = new StringWriter()) + { + ConnectLoggingActionToLogger(message => writer.Write(message)); + + var configuration = new ConfigurationBuilder() + .AddJsonFile(GetAppSettingsPath("appsettings.json")) + .Build(); + + var loggerOptions = new LambdaLoggerOptions(configuration); + loggerOptions.IncludeCategory = true; + var loggerFactory = new TestLoggerFactory() + .AddLambdaLogger(loggerOptions); + + var logger = loggerFactory.CreateLogger("JSONLogging"); + + logger.LogError(new Exception("Too Cheap"), "User {name} fail to by {product} for {price}", "Gilmour", "Guitar", 55.55); + + var text = writer.ToString(); + // Category is prepended as a placeholder + argument, so it becomes + // a queryable JSON property instead of being dropped. + Assert.Contains("{Category}", text); + Assert.Contains("parameter count: 4", text); + } + } + finally + { + Environment.SetEnvironmentVariable("AWS_LAMBDA_LOG_FORMAT", null); + } + } + + [Fact] + public void TestJSONParameterLoggingOmitsCategoryWhenDisabled() + { + Environment.SetEnvironmentVariable("AWS_LAMBDA_LOG_FORMAT", "JSON"); + try + { + using (var writer = new StringWriter()) + { + ConnectLoggingActionToLogger(message => writer.Write(message)); + + var configuration = new ConfigurationBuilder() + .AddJsonFile(GetAppSettingsPath("appsettings.json")) + .Build(); + + var loggerOptions = new LambdaLoggerOptions(configuration); + loggerOptions.IncludeCategory = false; + var loggerFactory = new TestLoggerFactory() + .AddLambdaLogger(loggerOptions); + + var logger = loggerFactory.CreateLogger("JSONLogging"); + + logger.LogError(new Exception("Too Cheap"), "User {name} fail to by {product} for {price}", "Gilmour", "Guitar", 55.55); + + var text = writer.ToString(); + Assert.DoesNotContain("{Category}", text); + Assert.Contains("parameter count: 3", text); + } + } + finally + { + Environment.SetEnvironmentVariable("AWS_LAMBDA_LOG_FORMAT", null); + } + } + [Fact] public void JsonLoggingWithNoOriginalFormat() { From 53fb69d8f0d8b8e34bdc0c0516ee1dfc7e5d1e19 Mon Sep 17 00:00:00 2001 From: Tem Revil Date: Sun, 12 Jul 2026 18:14:57 +0300 Subject: [PATCH 2/2] chore: add change file for logging category fix --- .../changes/541b1775-ecf8-4e75-99c8-675924a5c34e.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .autover/changes/541b1775-ecf8-4e75-99c8-675924a5c34e.json diff --git a/.autover/changes/541b1775-ecf8-4e75-99c8-675924a5c34e.json b/.autover/changes/541b1775-ecf8-4e75-99c8-675924a5c34e.json new file mode 100644 index 000000000..8b7312c31 --- /dev/null +++ b/.autover/changes/541b1775-ecf8-4e75-99c8-675924a5c34e.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "Amazon.Lambda.Logging.AspNetCore", + "Type": "Patch", + "ChangelogMessages": [ + "Fixed the JSON log formatter ignoring IncludeCategory" + ] + } + ] +}