Skip to content

JsonConsoleFormatter: (escaped) line breaks of exception stacktraces should be kept #83726

Closed

Description

Description

JsonConsoleFormatter is a great option for logging in cloud infrastructure where logs can be picked up from stdout.

For this JsonConsoleFormatter is probably mostly used with the FormatterOptions SingleLine=true, JsonWriterOptions.Indented=false:

"Console": {
  "FormatterName": "json",
  "FormatterOptions": {
    "SingleLine": true,
    "IncludeScopes": true,
    "TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffzzz"
  }
}

This produces an output similar to the one described in 'Actual behaviour'.

The downside of this is, in case of an Exception, that line breaks for serialized Exception's get stripped (manually, see

if (!FormatterOptions.JsonWriterOptions.Indented)
). For comparison, this is not the case for the Message part of the logged Json. There line breaks are kept in an escaped manner.

These escaped line breaks can then get picked up again and formatted properly in the log viewer of your desire. This is not possible for Exceptions, their serialized form gets broken.

I would like to see escaped line breaks in the serialized Exception string too, as we do for the Message, in the logged Json. Is there any reason this is not the case? Does this need to be backwards compatible? Or could it be simply changed in the line referenced above?

Nothing should change for the JsonWriterOptions.Indented mode, there everything is formatted properly.

Reproduction Steps

Log an exception with JsonConsoleFormatter in SingleLine mode:

"Console": {
  "FormatterName": "json",
  "FormatterOptions": {
    "SingleLine": true,
    "IncludeScopes": true,
    "TimestampFormat": "yyyy-MM-ddTHH:mm:ss.fffzzz"
  }
}
logger.LogError(e, "{Message}", e.Message);

Expected behavior

{"Timestamp":"2023-03-21T14:13:54.973\u002B01:00","EventId":0,"LogLevel":"Warning","Category":"<<redacted>>","Message":"Failed ExecuteActionAsync after: 148ms.\n   HTTP Status: (null)\n   Query: <<redacted>>","Exception":"System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.\n ---\u003E System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot\n   at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)\n   at System.Net.Security.SslStream.CompleteHandshake(SslAuthenticationOptions sslAuthenticationOptions)\n   at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)\n   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)\n   --- End of inner exception stack trace ---\n   at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)\n   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n   at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)\n   at System.Threading.Tasks.TaskCompletionSourceWithCancellation\u00601.WaitWithCancellationAsync(CancellationToken cancellationToken)\n   at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n   at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)\n   at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)\n   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n   at System.Net.Http.HttpClient.\u003CSendAsync\u003Eg__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)"}

Actual behavior

{"Timestamp":"2023-03-21T14:05:49.355\u002B01:00","EventId":0,"LogLevel":"Warning","Category":"<<redacted>>","Message":"Failed ExecuteActionAsync after: 193ms.\n   HTTP Status: (null)\n   Query: <<redacted>>","Exception":"System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.  ---\u003E System.Security.Authentication.AuthenticationException: The remote certificate is invalid because of errors in the certificate chain: UntrustedRoot    at System.Net.Security.SslStream.SendAuthResetSignal(ProtocolToken message, ExceptionDispatchInfo exception)    at System.Net.Security.SslStream.CompleteHandshake(SslAuthenticationOptions sslAuthenticationOptions)    at System.Net.Security.SslStream.ForceAuthenticationAsync[TIOAdapter](TIOAdapter adapter, Boolean receiveFirst, Byte[] reAuthenticationData, Boolean isApm)    at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)    --- End of inner exception stack trace ---    at System.Net.Http.ConnectHelper.EstablishSslConnectionAsync(SslClientAuthenticationOptions sslOptions, HttpRequestMessage request, Boolean async, Stream stream, CancellationToken cancellationToken)    at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)    at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)    at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(HttpRequestMessage request)    at System.Threading.Tasks.TaskCompletionSourceWithCancellation\u00601.WaitWithCancellationAsync(CancellationToken cancellationToken)    at System.Net.Http.HttpConnectionPool.GetHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)    at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)    at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)    at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)    at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)    at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)    at System.Net.Http.HttpClient.\u003CSendAsync\u003Eg__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)"}

Regression?

Looks like it was always like this.

Known Workarounds

No known Workarounds, besides implementing your own JsonConsoleFormatter form scratch.

Configuration

Docker image mcr.microsoft.com/dotnet/aspnet:6.0
As well as local .Net 6 runtime on macOS ARM64 Apple Silicon

It should be configuration independent.

Logger is used in ASP.Net 6 MVC application

Other information

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    area-Extensions-LoggingenhancementProduct code improvement that does NOT require public API changes/additions

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions