Allow custom `JsonSerializerOptions` (and converters) in `JsonLogMessageFormatter`

Open
#2,350 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
45/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
aws, csharp

Research direction

Read Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs and the related structured-logging context in issue #1747 first. Decide and implement one supported options-injection API; done means custom converters affect destructured output while the existing default options and behavior remain unchanged.

Written by the indexing model from the issue text.

Description

feature-request p2 queued
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:

// Current output for `_logger.LogInformation("Email queued {@Message}", sqsMessage)`:
{
  "timestamp": "2026-04-29T10:00:00.000Z",
  "level": "Information",
  "requestId": "...",
  "message": "Email queued {@Message}",
  "Message": {
    "To": "alice@example.com",
    "EventDate": {},               // ← lost: should be "2026-04-29T10:00:00Z"
    "TemplateKey": "ACCOUNT_CREATED"
  }
}

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:

  1. 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.
  2. DI-based: allow consumers to register a JsonSerializerOptions (or an IJsonSerializerOptionsProvider) in DI; the runtime support reads it on first log call.
  3. 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
  • I may be able to implement this feature request
  • This feature might incur a breaking change
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.

Dominant language
C#
Stars
1.7k
Forks
503
Avg merge
1d 18h
Merged PRs (30d)
21

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from aws/aws-lambda-dotnet

All issues in aws/aws-lambda-dotnet

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.