FlatJsonConsoleFormatter 2.0.0
dotnet add package FlatJsonConsoleFormatter --version 2.0.0
NuGet\Install-Package FlatJsonConsoleFormatter -Version 2.0.0
<PackageReference Include="FlatJsonConsoleFormatter" Version="2.0.0" />
<PackageVersion Include="FlatJsonConsoleFormatter" Version="2.0.0" />
<PackageReference Include="FlatJsonConsoleFormatter" />
paket add FlatJsonConsoleFormatter --version 2.0.0
#r "nuget: FlatJsonConsoleFormatter, 2.0.0"
#:package FlatJsonConsoleFormatter@2.0.0
#addin nuget:?package=FlatJsonConsoleFormatter&version=2.0.0
#tool nuget:?package=FlatJsonConsoleFormatter&version=2.0.0
FlatJsonConsoleFormatter
This project emits log messages as json, but constructs the log message differently than the default JsonConsoleFormatter. The default JSON formatter creates an object that is deeply nested, includes unnecessary information, and repeats log messages. This log message formatter gives you a simple collection of key/value pairs formatted as a json object that includes state and scopes, while avoiding unnecessary information.
Breaking Changes in v2.0
In version 2.0, default options were introduced to make the most common use cases produce more succinct log messages.
- Category names are truncated by default to include only the text after the lat period. Typically, this means that the log category will be the class name without the namespace.
- Duplicate scope keys are merged instead of numbered.
To use old behavior, set explicit options in startup:
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConfiguration(configuration.GetSection("Logging"));
builder.AddFlatJsonConsole(options => {
options.TruncateCategory = false;
options.MergeDuplicateKeys = false;
});
});
Usage
Add the nuget reference
dotnet add package FlatJsonConsoleFormatter
Setup the library in your project
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConfiguration(configuration.GetSection("Logging"));
builder.AddFlatJsonConsole();
});
Why another formatter?
Here is an example of a message logged using JsonConsoleFormatter that includes scopes, where the scope object is Dictionary<string, object>.
using (_log.BeginScope(new Dictionary<string, object> { { "MessageId", messageId } }))
using (_log.BeginScope(new Dictionary<string, object> { { "BaseUrl", url }, { "CustomerId", customerId } }))
{
_log.LogDebug("GET {Endpoint}", endpoint);
}
The above code results in the following log message:
{
"EventId": 0,
"LogLevel": "Debug",
"Category": "ACME.Project.ApiInvoker",
"Message": "GET https://example.com/api/endpoint/32120",
"State": {
"Message": "GET https://example.com/api/endpoint/32120",
"Endpoint": "https://example.com/api/endpoint/32120",
"{OriginalFormat}": "GET {Endpoint}"
},
"Scopes": [
{
"Message": "System.Collections.Generic.Dictionary\u00602[System.String,System.Object]",
"MessageId": "a38cb57d-4719-4d39-a36c-19f75b289bb4"
},
{
"Message": "System.Collections.Generic.Dictionary\u00602[System.String,System.Object]",
"BaseUrl": "https://example.com/api",
"CustomerId": 32120
}
]
}
What is wrong with that log message?
- The message
GET https://example.com/apiis repeated multiple times - I don't care what the original format string was. I care about having the format values and the result.
- The state and scope information is contained as nested objects, which makes it harder than necessary to parse using tools such as AWS CloudWatch Log Insights
- I don't care about the type of the individual scope objects. The scope object is just a collection of values I want logged.
- Not shown above, but if you want your json formatted on a single line and log an exception, then newlines will be stripped from the exception message making the stack trace unreadable. JSON has an escape sequence for newline characters. This is unnecessary and weird, and seems to be removed in .NET 8.
How does this project address those shortcomings?
FlatJsonConsoleFormatter will log the following message instead:
{
"EventId": 0,
"LogLevel": "Debug",
"Category": "ACME.Project.ApiInvoker",
"Message": "GET https://example.com/api/endpoint/32120",
"Endpoint": "https://example.com/api/endpoint/32120",
"MessageId": "a38cb57d-4719-4d39-a36c-19f75b289bb4"
"BaseUrl": "https://example.com/api",
"CustomerId": 32120
}
It does this by merging the state and scope keys into a dictionary and writing them as top-level properties. When there is a conflict between the names of keys from different sources, the default strategy is "last one wins", but you can set an option in startup to keep all scope items and differentiate them by appending a number to the key.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Microsoft.Extensions.Logging (>= 9.0.0)
- Microsoft.Extensions.Logging.Console (>= 9.0.0)
-
net8.0
- Microsoft.Extensions.Logging (>= 9.0.0)
- Microsoft.Extensions.Logging.Console (>= 9.0.0)
-
net9.0
- Microsoft.Extensions.Logging (>= 9.0.0)
- Microsoft.Extensions.Logging.Console (>= 9.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
BREAKING CHANGES IN 2.0: Default options were changed to truncate category names and merge duplicate log scope keys. To use old behavior, set explicit options in startup.
Remove net6.0; Add net9.0 target; Merge changes made in v9.0.0 of Microsoft.Extensions.Logging.Console