38

Serilog has a convenient way of destructuring objects as shown in this example:

logger.Debug(exception, "This is an {Exception} text", exception);
logger.Debug(exception, "This is an {@Exception} structure", exception);

The first line causes the logger to log an exception as plain text (by calling ToString()), and the second line causes the logger to write exception properties as separate fields. But what about this overload:

logger.Debug(exception, "This is an exception", exception);

This one takes an exception as its first argument, and it is always written as a string. What I would like to make possible is to enable logging exception in a structured way. Is it possible to configure Serilog to achieve this?

UPDATE. I guess this question leads to another aspect of logging exceptions: how can I ensure that messages are enriched with exception properties (so they are logged in a structured way to the rich sinks like Elasticsearch) without writing all exception properties to the rendered text message (so plain text loggers are not filled with huge piles of exception details).

JotaBe
  • 38,030
  • 8
  • 98
  • 117
Vagif Abilov
  • 9,835
  • 8
  • 55
  • 100

3 Answers3

45

Take a look at Serilog.Exceptions logs exception details and custom properties that are not output in Exception.ToString().

This library has custom code to deal with extra properties on most common exception types and only falls back to using reflection to get the extra information if the exception is not supported by Serilog.Exceptions internally.

Add the NuGet package and then add the enricher like so:

using Serilog;
using Serilog.Exceptions;

ILogger logger = new LoggerConfiguration()
    .Enrich.WithExceptionDetails()
    .WriteTo.Sink(new RollingFileSink(
        @"C:\logs",
        new JsonFormatter(renderMessage: true))
    .CreateLogger();

Your JSON logs will now be supplemented with detailed exception information and even custom exception properties. Here is an example of what happens when you log a DbEntityValidationException from EntityFramework (This exception is notorious for having deeply nested custom properties which are not included in the .ToString()).

try
{
    ...
}
catch (DbEntityValidationException exception)
{
    logger.Error(exception, "Hello World");
}

The code above logs the following:

{
  "Timestamp": "2015-12-07T12:26:24.0557671+00:00",
  "Level": "Error",
  "MessageTemplate": "Hello World",
  "RenderedMessage": "Hello World",
  "Exception": "System.Data.Entity.Validation.DbEntityValidationException: Message",
  "Properties": {
    "ExceptionDetail": {
      "EntityValidationErrors": [
        {
          "Entry": null,
          "ValidationErrors": [
            {
              "PropertyName": "PropertyName",
              "ErrorMessage": "PropertyName is Required.",
              "Type": "System.Data.Entity.Validation.DbValidationError"
            }
          ],
          "IsValid": false,
          "Type": "System.Data.Entity.Validation.DbEntityValidationResult"
        }
      ],
      "Message": "Validation failed for one or more entities. See 'EntityValidationErrors' property for more details.",
      "Data": {},
      "InnerException": null,
      "TargetSite": null,
      "StackTrace": null,
      "HelpLink": null,
      "Source": null,
      "HResult": -2146232032,
      "Type": "System.Data.Entity.Validation.DbEntityValidationException"
    },
    "Source": "418169ff-e65f-456e-8b0d-42a0973c3577"
  }
}

Serilog.Exceptions supports the .NET Standard and supports many common exception types without reflection but we'd like to add more, so please feel free to contribute.

Top Tip - Human Readable Stack Traces

You can use the Ben.Demystifier NuGet package to get human readable stack traces for your exceptions or the serilog-enrichers-demystify NuGet package if you are using Serilog.

Muhammad Rehan Saeed
  • 35,627
  • 39
  • 202
  • 311
  • The default JsonFormatter does not use any formatting. How do we get it to use a nicely printed JSON object in the Console sink? – Rob L Jul 03 '19 at 19:23
  • I am using `Serilog.Exceptions` but unfortunately, it logs the entire stack trace of exception in "Exception" property of the log rather than "ExceptionDetails". How can I fix this? – Junaid Sep 02 '21 at 09:23
  • @Junaid That is Serilogs default behaviour. Serilog.Exceptions just adds additional metadata in ExceptionDetails. If you really want, you can enable an option to also add the StackTrace to ExceptionDetails. – Muhammad Rehan Saeed Sep 02 '21 at 10:33
  • I have done that and now stack trace is in the "ExceptionDetails" section. I have also renamed "ExceptionDetails" to "Exception" only. Is there a way to remove the original/default "Exception" field? Kindly check this question: https://stackoverflow.com/questions/69028506/log-without-exception-field-in-serilog – Junaid Sep 02 '21 at 10:54
  • @Muhammad Rehan Saeed did you see my question? – Junaid Sep 03 '21 at 05:32
  • @MuhammadRehanSaeed is there a way to exclude any of the sub-property like "EntityValidationErrors" or "ValidationErrors"? – Junaid Sep 16 '21 at 18:31
29

There's a forum thread discussing this, in which a couple of solutions are presented. Thomas Bolon has created an 'exception destructuring' extension you can find in a Gist.

In this case you use only this syntax:

logger.Debug(exception, "This is an exception");

There's no need to add the exception into the format string.

To ensure the exception is printed to text sinks, just make sure {Exception} is included in the output template. The standard built-in ones already have this, e.g.:

outputTemplate: "{Timestamp} [{Level}] {Message}{NewLine}{Exception}";
Nicholas Blumhardt
  • 30,271
  • 4
  • 90
  • 101
  • 2
    Thanks for the answer. I will look at Thomas's implementation. – Vagif Abilov Aug 19 '14 at 11:53
  • 2
    Note that the answer below refers to a complete implementation of the original idea in the linked Gist, available in github, and as the Nuget package Serilog.Exceptions. – JotaBe Feb 18 '16 at 19:47
3

This should be avoided altogether. Both ElasticSearch and Serilog aren't designed with the idea in mind that you will be serializing arbitrary objects. Logging objects with the conflicting shapes will result in mapping exceptions in ElasticSearch. If you are using the ElasticSearch sink in NuGet anything that results in a mapping conflict will be lost. Also Serilog does not handle Cyclical relationships so this will result in depth limiter selflog errors. There is a project that attempts to address this by destructuring into dictionaries and passing this to Serilog but you will still wind up with messy logs and mapping exceptions.

Serilog: https://nblumhardt.com/2016/02/serilog-tip-dont-serialize-arbitrary-objects/

I've found it best to be specific about logging exception properties based on what you find useful in the exception.

Daniel Leach
  • 5,517
  • 4
  • 18
  • 32
  • I'm using ElasicSearch 5 with Serilog.Exceptions to log the entire message but I'm using the RollingFile sink with Filebeat 5, not the ElasticSearch Sink. I haven't got any mapping exceptions but I do have a long list of properties in Kibana. I've dealth with that by documenting a list of common properties that occur in all logs. Regarding serilalization, Serilog.Exceptions provides a way to log exception properties without using reflection for common Exception types and you can create your own. That said, I've not had any trouble using reflection where an Exception type is not supported. – Muhammad Rehan Saeed Jun 06 '17 at 10:30
  • 2
    One of the problems with the Serilog ElasticSearch sink is that you won't see mapping exceptions, it will just simply be lost, because of this issue: https://github.com/serilog/serilog-sinks-elasticsearch/issues/25 – Daniel Leach Jun 07 '17 at 02:13