aws / aws/aws-lambda-dotnet

JSON log format pairs ILogger message template placeholders with state values by position, garbling ASP.NET Core hosting logs

Open
#2,468 2 comments 0 reactions 0 assignees View on GitHub
bug investigating p2
Dominant language
C#
Stars
1.7k
Forks
503
Avg merge
1d 5h
Merged PRs (30d)
18

Description

### Describe the bug

When a function uses `LoggingConfig.LogFormat: JSON` and bridges `Microsoft.Extensions.Logging.ILogger` to the Lambda logging API via `Amazon.Lambda.Logging.AspNetCore`, structured log events can come out with their JSON properties holding the **wrong values** (each property shifted to a neighboring placeholder's value).

Root cause, as far as I can tell, is a combination of two behaviors:

1. `LambdaILogger` extracts the message template from `{OriginalFormat}` and collects the state values **in state enumeration order** ([LambdaILogger.cs](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaILogger.cs)).
2. `JsonLogMessageFormatter` then pairs template placeholders with arguments **strictly by position** ([JsonLogMessageFormatter.cs](https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs)).

The `Microsoft.Extensions.Logging` contract is that placeholders are resolved **by name** against the state key/value pairs; nothing guarantees that state enumeration order matches placeholder order. The most prominent violator ships in ASP.NET Core itself: [`HostingRequestStartingLog`](https://github.com/dotnet/aspnetcore/blob/main/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs) enumerates its state as `Protocol, Method, ContentType, ContentLength, Scheme, Host, PathBase, Path, QueryString`, while its template is:

```
Request starting {Protocol} {Method} {Scheme}://{Host}{PathBase}{Path}{QueryString} - {ContentType} {ContentLength}
```

So every `Request starting` log line of every ASP.NET Core app running on Lambda with JSON log format is silently garbled from the third placeholder on.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Expected Behavior

Template placeholders resolved by name, as with every other `Microsoft.Extensions.Logging` sink:

```json
{
"message": "Request starting HTTP/1.1 DELETE https://api.example.com/events/123?preserve=true - application/json 0",
"Method": "DELETE",
"Scheme": "https",
"Host": "api.example.com",
"Path": "/events/123",
"QueryString": "?preserve=true",
"ContentType": "application/json",
"ContentLength": 0
}
```

### Current Behavior

Actual CloudWatch log record (values anonymized). Note `Scheme` holding the content type, `Host` holding the content length, `Path` holding the host, `ContentType` holding the path, etc.:

```json
{
"timestamp": "2026-07-04T22:43:57.529Z",
"level": "Information",
"requestId": "188cbe50-087d-4465-acf4-d4a6067ef0da",
"message": "Request starting {Protocol} DELETE application/json://0httpsapi.example.com - /events/123 ?preserve=true",
"Method": "DELETE",
"Scheme": "application/json",
"Host": 0,
"PathBase": "https",
"Path": "api.example.com",
"QueryString": "",
"ContentType": "/events/123",
"ContentLength": "?preserve=true"
}
```

Each placeholder received the state value at the same index instead of the value with the matching name (`{Scheme}` ← `ContentType`, `{Host}` ← `ContentLength`, `{PathBase}` ← `Scheme`, ...). The rendered `message` is garbled the same way.

### Reproduction Steps

Minimal ASP.NET Core app:

```csharp
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAWSLambdaHosting(LambdaEventSource.RestApi);
builder.Logging.ClearProviders();
builder.Logging.AddLambdaLogger();
var app = builder.Build();
app.MapGet("/", () => "ok");
app.Run();
```

Deploy behind API Gateway with:

```yaml
LoggingConfig:
LogFormat: JSON
```

Invoke any endpoint and inspect the `Request starting` record in CloudWatch: the `Scheme`, `Host`, `PathBase`, `Path`, `QueryString`, `ContentType` and `ContentLength` properties are shifted as shown above.

### Possible Solution

Either:

- In `LambdaILogger` (JSON branch): parse the placeholder names from `{OriginalFormat}` and emit the arguments **in placeholder order**, resolving each one by key against the state; or
- In `JsonLogMessageFormatter`: when arguments come from an `ILogger` state, match them to placeholders by name instead of by position.

### Additional Information/Context

Any `ILogger` event whose state enumeration order differs from its template placeholder order is affected — ASP.NET Core hosting logs are just the guaranteed-out-of-the-box case. Logs written through the standard `LoggerExtensions`/`LoggerMessage` paths happen to be unaffected because `FormattedLogValues` keeps both orders aligned, which is probably why this went unnoticed.

### AWS .NET SDK and/or Package version used

Amazon.Lambda.AspNetCoreServer 10.1.1
Amazon.Lambda.AspNetCoreServer.Hosting 2.1.0
Amazon.Lambda.Logging.AspNetCore 5.0.0
Amazon.Lambda.Core 3.1.0

### Targeted .NET Platform

.NET 10 (managed `dotnet10` runtime)

### Operating System and version

Amazon Linux 2023 (Lambda managed runtime)

Contributor guide

Open the contributing guide

Research direction

Read Libraries/src/Amazon.Lambda.Logging.AspNetCore/LambdaILogger.cs and Libraries/src/Amazon.Lambda.RuntimeSupport/Helpers/Logging/JsonLogMessageFormatter.cs, then run the minimal ASP.NET Core reproduction with JSON logging. Done means the Request starting record resolves each JSON property by its placeholder name and the rendered message is no longer shifted.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, csharp
Domain
backend, observability-sre
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.