Describe the feature
The JsonLogMessageFormatter introduced in Amazon.Lambda.RuntimeSupport 1.12.0 hardcodes its JsonSerializerOptions in the parameterless constructor:
// JsonLogMessageFormatter.cs
private readonly JsonSerializerOptions _jsonSerializationOptions;
public JsonLogMessageFormatter()
{
_jsonSerializationOptions = new JsonSerializerOptions
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
}
There is currently no way for a customer to inject custom JsonSerializerOptions or register custom JsonConverters. The only documented customization path is to apply [JsonConverter(...)] attributes individually on every property of every type that may end up in a {@param} destructured log call.
Use Case
Many .NET codebases use third-party value types that have no public properties (so JsonSerializer.Serialize without converters produces empty {}) but provide official JsonConverters in companion packages. Examples:
- NodaTime (
Instant, LocalDate, LocalDateTime, Duration, Period...) - converters in NodaTime.Serialization.SystemTextJson
- NetTopologySuite geographic types - converters in
NetTopologySuite.IO.GeoJSON4STJ
- Domain primitive / value object libraries like Vogen, StronglyTypedId - generated converters
In our codebase, NodaTime is used pervasively across business entities and SQS messages. Whenever we want to use the new {@Object} destructuring syntax to log a complex object, NodaTime properties serialize as empty objects, which silently loses data:
Annotating each property of every entity with a custom [JsonConverter] attribute is invasive, mixes serialization concerns with persistence/domain code, and is fragile (easy to forget on a new property - silent data loss).
Proposed Solution
Expose a constructor overload (or a factory hook) that accepts a custom JsonSerializerOptions instance. Three possible levels of API, in order of preference:
- Minimal: add a static
JsonLogMessageFormatter.ConfigureOptions(Action<JsonSerializerOptions>) method that the runtime applies after creating its default options. Optionally also support a declarative form via an AWS_LAMBDA_LOG_FORMATTER_OPTIONS_CONFIGURATOR environment variable pointing to a Type::Method, mirroring how [LambdaSerializer] attribute already lets users plug a custom serializer.
- DI-based: allow consumers to register a
JsonSerializerOptions (or an IJsonSerializerOptionsProvider) in DI; the runtime support reads it on first log call.
- Subclass-friendly: change
_jsonSerializationOptions to protected, add a virtual CreateJsonSerializerOptions() method, allow subclassing JsonLogMessageFormatter and selecting it via env var or DI.
Option 1 is the least invasive and matches how Amazon.Lambda.Serialization.SystemTextJson.SourceGeneratorLambdaJsonSerializer<T> already lets customers swap the serializer.
Other Information
Alternatives considered
- Annotating every domain property with
[JsonConverter]: works but invasive, fragile, mixes concerns.
- Avoiding
{@Object} on objects containing custom types: works but defeats much of the structured-logging value.
- Pre-serializing in user code with
JsonSerializer.Serialize(value, customOptions) then logging as a string: loses the "queryable hierarchical fields" benefit on the CloudWatch Logs Insights side.
- Switching to a third-party logger (Serilog, NLog): works but requires customers to leave the AWS-recommended path and re-implement
requestId/traceId enrichment.
Additional context
Adding this hook would close a real gap for any codebase using third-party value types with companion JsonConverters, without affecting the default behavior.
Acknowledgements
AWS .NET SDK and/or Package version used
Amazon.Lambda.RuntimeSupport: 1.12.0+ (provided by the managed runtime; source inspected on master branch as of issue submission)
Amazon.Lambda.Core: 2.4.0+
Amazon.Lambda.Logging.AspNetCore: 5.0.0+
Targeted .NET Platform
.NET 10 (net10.0), running on the AWS Lambda managed dotnet10 runtime. The same limitation applies on net8.0 / dotnet8.
Operating System and version
AWS Lambda managed runtime (Amazon Linux 2023, x86_64). Reproducible identically on arm64. Local repro on Windows 11 / .NET 10 SDK.
Describe the feature
The
JsonLogMessageFormatterintroduced inAmazon.Lambda.RuntimeSupport1.12.0 hardcodes itsJsonSerializerOptionsin the parameterless constructor:There is currently no way for a customer to inject custom
JsonSerializerOptionsor register customJsonConverters. The only documented customization path is to apply[JsonConverter(...)]attributes individually on every property of every type that may end up in a{@param}destructured log call.Use Case
Many .NET codebases use third-party value types that have no public properties (so
JsonSerializer.Serializewithout converters produces empty{}) but provide officialJsonConverters in companion packages. Examples:Instant,LocalDate,LocalDateTime,Duration,Period...) - converters inNodaTime.Serialization.SystemTextJsonNetTopologySuite.IO.GeoJSON4STJIn our codebase, NodaTime is used pervasively across business entities and SQS messages. Whenever we want to use the new
{@Object}destructuring syntax to log a complex object, NodaTime properties serialize as empty objects, which silently loses data:Annotating each property of every entity with a custom
[JsonConverter]attribute is invasive, mixes serialization concerns with persistence/domain code, and is fragile (easy to forget on a new property - silent data loss).Proposed Solution
Expose a constructor overload (or a factory hook) that accepts a custom
JsonSerializerOptionsinstance. Three possible levels of API, in order of preference:JsonLogMessageFormatter.ConfigureOptions(Action<JsonSerializerOptions>)method that the runtime applies after creating its default options. Optionally also support a declarative form via anAWS_LAMBDA_LOG_FORMATTER_OPTIONS_CONFIGURATORenvironment variable pointing to aType::Method, mirroring how[LambdaSerializer]attribute already lets users plug a custom serializer.JsonSerializerOptions(or anIJsonSerializerOptionsProvider) in DI; the runtime support reads it on first log call._jsonSerializationOptionstoprotected, add a virtualCreateJsonSerializerOptions()method, allow subclassingJsonLogMessageFormatterand selecting it via env var or DI.Option 1 is the least invasive and matches how
Amazon.Lambda.Serialization.SystemTextJson.SourceGeneratorLambdaJsonSerializer<T>already lets customers swap the serializer.Other Information
Alternatives considered
[JsonConverter]: works but invasive, fragile, mixes concerns.{@Object}on objects containing custom types: works but defeats much of the structured-logging value.JsonSerializer.Serialize(value, customOptions)then logging as a string: loses the "queryable hierarchical fields" benefit on the CloudWatch Logs Insights side.requestId/traceIdenrichment.Additional context
Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.csAdding this hook would close a real gap for any codebase using third-party value types with companion
JsonConverters, without affecting the default behavior.Acknowledgements
AWS .NET SDK and/or Package version used
Amazon.Lambda.RuntimeSupport: 1.12.0+ (provided by the managed runtime; source inspected onmasterbranch as of issue submission)Amazon.Lambda.Core: 2.4.0+Amazon.Lambda.Logging.AspNetCore: 5.0.0+Targeted .NET Platform
.NET 10 (
net10.0), running on the AWS Lambda manageddotnet10runtime. The same limitation applies onnet8.0/dotnet8.Operating System and version
AWS Lambda managed runtime (Amazon Linux 2023, x86_64). Reproducible identically on
arm64. Local repro on Windows 11 / .NET 10 SDK.