Open-source project
RehanSaeed/Serilog.Exceptions avatar
RehanSaeed/Serilog.Exceptions

Serilog.Exceptions: logging the exception data ToString throws away

Log exception details and custom properties that are not output in Exception.ToString().

537 stars52 forksC#MIT

At a glance

What is it?
Serilog.Exceptions is a Serilog enricher that walks an exception object and adds its nested properties to the log event, so a validation failure from Entity Framework arrives as structured JSON rather than a single line of text. It ships hand written destructurers for the common exception types and falls back to reflection only for the rest, and it is distributed as a set of small NuGet packages with a core and per provider add-ons.
Who is it for?
Adopt Serilog.Exceptions if you log exceptions through Serilog and find that Exception.ToString() loses the data you actually need, which is the normal case for exceptions that carry a nested collection of errors, and if you consume those logs as JSON. Do not adopt it if your logging pipeline reads plain text lines, because the enriched properties only appear when the sink's formatter emits them.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 9 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap ToString leaves open

Serilog.Exceptions is an add-on to Serilog that logs exception details and custom properties that are not output in Exception.ToString(). That one sentence is the entire value proposition, and the README's worked example shows exactly why it matters.

The example is a DbEntityValidationException from Entity Framework, and the README is blunt about the reason for choosing it: this exception is notorious for having deeply nested custom properties which are not included in the ToString().

That is the problem in general. When you log an exception, the default behaviour gives you the exception type, the message and the stack trace. Anything the exception carries beyond that, and validation exceptions carry a great deal, disappears. In the documented case the useful information is the list of validation errors, each with the property that failed, the error message, and the type of the validation error itself, nested inside a list of validation results, each of which also names the entity entry. All of that is in the exception object and none of it is in the string.

The output the library produces puts it back. The log event carries an ExceptionDetail property containing the EntityValidationErrors collection, and each result carries its ValidationErrors with the PropertyName, the ErrorMessage and the Type, along with the IsValid flag. The standard exception surface is captured too, with Message, Data, InnerException, TargetSite, StackTrace, HelpLink, Source, HResult and Type, so the log is a superset of what ToString gives you rather than a replacement for it.

The audience is any team whose logs are read by a machine, because the whole point of the library is to turn an exception into structured data a query can filter on. The example in the README adds a Source property carrying a GUID alongside the exception detail, which is the sort of correlation identifier you would want to be able to join on.

The repository is the RehanSaeed one, the primary language is C#, the topics are c-sharp, dotnet, dotnet-core, exception, logging, nuget, reflection and serilog, and the licence is MIT with the text in LICENSE.md.

Installing it and switching on the enricher

The package is on NuGet as Serilog.Exceptions, and the README says to add it with the NuGet Package Manager or with a command in the Package Console Window:

powershell
dotnet add package Serilog.Exceptions

Then the enricher has to be added to the logger configuration, and the README's example shows the whole shape:

csharp
using Serilog;
using Serilog.Exceptions;

ILogger logger = new LoggerConfiguration()
    .Enrich.WithExceptionDetails()
    .WriteTo.RollingFile(
        new JsonFormatter(renderMessage: true), 
        @"C:\logs\log-{Date}.txt")    
    .CreateLogger();

One call, WithExceptionDetails(), is the whole integration. It is an enricher rather than a sink, which is the correct place for it in the Serilog pipeline: the enricher runs when the event is created, so the exception detail is attached to the event as a property and every sink downstream receives it.

Two things in the README deserve attention before you copy that snippet.

The first is the warning at the top of the Getting Started section, aimed at Entity Framework Core users, who are told to read the instructions under the SerilogExceptionsEntityFrameworkCore heading. Whatever that section says, it is specific enough that the library's author considered the default behaviour wrong or incomplete for one of the most common logging scenarios in .NET, which is a good sign that the documentation is written by someone who has debugged it.

The second is that the enricher has to be enabled as well as installed. A package reference without the WithExceptionDetails() call produces logs that look exactly as they did before, and the failure mode is silence rather than an exception. That is worth remembering during rollout, because nothing in the log output tells you that the enricher is missing.

The sink problem: enriched properties need a placeholder

This is the part of the setup that catches people, and the README treats it as a first-class step rather than a footnote.

The instruction is: make sure that the sink's formatter outputs enriched properties. Serilog.Sinks.Console and many more do not do that by default. You may need to add {Properties:j} to your sink's format template, and the README gives the console configuration as an example:

csharp
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception} {Properties:j}")

The mechanism is worth understanding rather than copying. Serilog's default console output template prints the timestamp, the level, the message and the exception, and it leaves the properties out because the usual properties are already represented by those placeholders. The ExceptionDetail property this library adds is not one of those, so it never appears. The {Properties:j} placeholder is what serialises the remaining properties as JSON, and the :j suffix is what makes them machine readable rather than a debug rendering.

The same issue applies to every other sink that has a fixed template, and the JSON formatter in the file sink example handles it differently, because a JSON formatter emits the whole event structure including properties by construction. So the file sink in the setup snippet works without a template change, and the console sink does not.

For a team shipping logs to a collector, this is the difference between an exception you can query by the property that failed and an exception you have to read as English prose in a log viewer. The enrichment work is only half the job; the template is the other half, and the README is unusually clear about it.

Configuration from appsettings.json, not only from code

The README also covers the JSON configuration route, and the reason it exists is that in an ASP.NET application the logger is usually built by Serilog.Settings.Configuration rather than in code.

The configuration block is short:

json
{
  "Serilog": {
    "Using": [ "Serilog.Exceptions" ],
    "Enrich": [ "WithExceptionDetails" ],
    "WriteTo": [
      { "Name": "Console" }
    ]
  }
}

Two entries do the work. The Using array names the assembly that contains the extension method, so that the configuration mechanism can find WithExceptionDetails at all, and the Enrich array calls it. The WriteTo array then lists the sinks, and here it is the plain Console sink with no output template, which is the same trap as before in a different form: with no template, the properties are not rendered, so the configuration file needs a template too if you want to see the exception detail in the console.

The pattern is the standard Serilog configuration approach, and Serilog.Exceptions integrates with it rather than against it. For an application that already keeps its logging configuration in appsettings.json, adding the enricher is two lines in a file rather than a code change and a redeploy of the logger factory.

It is also the configuration route that tends to be forgotten, because the library's own documentation leads with the fluent C# example. A service whose logging is configured in JSON and which was tested against a developer machine with a code configured logger will appear to work and then produce propertyless logs in the deployed environment.

Destructurers: hand written code first, reflection only as a fallback

The performance section of the README explains the mechanism, and it is the design decision that makes the library usable in production.

The 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. The README adds that reflection overhead is present but minimal, because all the expensive reflection-based operations are done only once per exception-type.

That caching detail is the important part. Reflection over an exception's properties is not free, and if it ran on every exception on every log call, a busy service logging exceptions in a loop would pay that cost continuously. Doing the expensive discovery once per type and then reusing it changes the cost profile from per occurrence to per type, which is what makes the reflection path acceptable rather than a reason to avoid it.

The custom destructurers are what remove the reflection cost for the types that matter most, and they are also what produce the useful output. The Entity Framework validation output in the example is not a generic property dump, it is a shaped representation of the validation error collection.

The mechanism is visible in the API, because the destructurers can be configured. The per provider packages work by adding a destructurer to the enricher options, and the README shows the shape for SQL Server:

csharp
.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
    .WithDefaultDestructurers()
    .WithDestructurers(new[] { new SqlExceptionDestructurer() }))

So the default set of destructurers is one call, WithDefaultDestructurers, and additional ones are supplied as an array, WithDestructurers. That is a familiar shape to anyone who has configured Serilog destructuring, and it means you can add your own for your own exception types without replacing the defaults.

The per provider packages, and where the project stands

The additional destructurers are shipped as separate NuGet packages, one per data access provider, and the reason is stated plainly: the package exists to avoid the reflection based destructurer for SqlException when using System.Data.SqlClient.

Serilog.Exceptions.SqlServer is the older one, for System.Data.SqlClient, and Serilog.Exceptions.MsSqlServer is the Microsoft data stack equivalent. The installation is the same shape as the core package:

powershell
dotnet add package Serilog.Exceptions.SqlServer

The pattern of splitting by provider is a good one for a different reason than code size. A destructurer for SqlException has to understand the shape of that specific exception, including the collection of error numbers that SQL Server returns, and that shape differs between the two SqlClient implementations. Shipping one package per provider keeps the core free of provider specific code and lets a user install only the one they need.

The package is also published to an Azure Artifacts feed alongside NuGet, with a badge for each package pointing at the feed, so a team that wants to pin and audit the binaries can host them internally rather than pulling from the public feed.

The maintenance picture needs stating carefully. The recent release tags are 8.4.0 from 2022-08-15, 8.3.0 from 2022-06-30 and 8.2.0 from 2022-05-20, while the last push to the repository was on 2026-10-14. So the tree is being worked on and the newest published package on NuGet is more than four years old. Whether that matters depends on the .NET version you are on, since the enricher is bound to the Serilog version and the runtime, and the repository's tooling shows attention to that: there is a global.json pinning the SDK, Directory.Build.props and Directory.Build.targets for shared build settings, a Benchmarks directory, a build.cake script, dotnet-tools.json for the tool manifest, renovate.json for dependency updates, and both appveyor.yml and azure-pipelines.yml for CI. Key.snk at the root is the strong name key for the assemblies.

For a team on a current .NET, the practical check is whether the published 8.4.0 package loads and behaves correctly on your runtime, because the commits after it are not in a NuGet release.

Editorial conclusion

Adopt Serilog.Exceptions if you log exceptions through Serilog and find that Exception.ToString() loses the data you actually need, which is the normal case for exceptions that carry a nested collection of errors, and if you consume those logs as JSON. Do not adopt it if your logging pipeline reads plain text lines, because the enriched properties only appear when the sink's formatter emits them. Verify first that your sink renders enriched properties, since the console sink does not do so by default and needs a {Properties:j} placeholder added, that the exception types you care about are covered by the built in destructurers, and whether the extra package for your database provider is worth adding, because the SqlException destructurers exist specifically to avoid the reflection path for SqlException.

Frequently asked questions

What does Serilog.Exceptions do?

It is an add-on to Serilog that logs exception details and custom properties that are not output in Exception.ToString(). The exception is walked and its nested properties are added to the log event, so a validation exception arrives as structured JSON rather than a single line of text.

How do I add Serilog.Exceptions to a project?

Run dotnet add package Serilog.Exceptions, then add the .Enrich.WithExceptionDetails() call to your logger configuration, either fluently in C# or through the Serilog section of appsettings.json with Serilog.Exceptions in the Using array and WithExceptionDetails in the Enrich array.

Why is nothing showing up in my console sink?

The sink's formatter has to output enriched properties, and Serilog.Sinks.Console does not do that by default. The README says you may need to add {Properties:j} to the sink's output template, and gives an example console template that includes it.

Does Serilog.Exceptions use reflection?

Only as a fallback. The library has custom code for the extra properties of most common exception types and falls back to reflection when the exception is not supported internally, and the README notes that the expensive reflection operations are done only once per exception type, which keeps the overhead minimal.

What are the extra Serilog.Exceptions packages for?

They provide destructurers for provider specific exceptions so the reflection path is not needed. Serilog.Exceptions.SqlServer avoids the reflection based destructurer for SqlException when using System.Data.SqlClient, and Serilog.Exceptions.MsSqlServer covers the Microsoft data stack.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. RehanSaeed/Serilog.Exceptions on GitHub
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/rehansaeed-serilog-exceptions.svg)](https://hysenlabs.com/projects/rehansaeed-serilog-exceptions)

Was this useful?