Library / SDK
serilog/serilog avatar
serilog/serilog

Serilog: .NET logging that captures property values at the call site

Simple .NET logging with fully-structured events

8,071 stars875 forksC#Apache-2.0

At a glance

What is it?
Serilog records structured events rather than pre-formatted text, using message templates with named parameters and a destructuring operator. Installing it needs the core package plus at least one sink, and the performance claim in its feature list arrives without a number attached.
Who is it for?
Adopt Serilog when your .NET application emits events you will later need to search or aggregate, because that is the case its design actually serves and the case where a pre-formatted string has already thrown the data away. Do not adopt it expecting a single package to get you logging, since Serilog writes nowhere until you add a sink, and do not adopt it to reach a prebuilt log platform, because the search capability lives in the backend rather than in the library.
Can I use it commercially?
Yes. Apache-2.0 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 22 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 November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • serilog/serilog8,071 ★ · +30
Star history
Repository2026-09-222026-11-14
serilog/serilog8k8.1k

30 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Serilog captures values at the call site, not rendered text

The design decision that separates Serilog from a conventional logger is when formatting happens. Serilog uses message templates, described as a simple DSL that extends .NET format strings with named as well as positional parameters, and instead of formatting events immediately into text it captures the values associated with each named parameter.

So the call site hands over an object and a variable, not a finished sentence.

csharp
var position = new { Latitude = 25, Longitude = 134 };
var elapsedMs = 34;

log.Information("Processed {@Position} in {Elapsed} ms", position, elapsedMs);

That call records two properties, `Position` and `Elapsed`, alongside the message. Nothing about the object's shape had to be known to produce the string.

The consequence reaches into how you write log statements. You are describing a set of named values, so a change to what you want to know later means changing the template and its arguments, rather than unravelling a concatenated string that has already lost the structure.

The @ operator decides between serializing and calling ToString

One character in that template carries most of the weight. The `@` operator in front of `Position` tells Serilog to serialize the object passed in, rather than convert it using `ToString()`.

Leave the `@` off and the object still becomes a property. It simply becomes the wrong kind of value, because the fallback is whatever that type's string conversion produces.

That failure mode is quiet. The log line looks reasonable, the level is right, nothing throws, and the structured data has already been flattened to a string. The bug surfaces weeks later when someone queries the log store for the nested field and finds a single opaque value where they expected two numbers.

The consequence for review is that the destructuring operator is worth checking in code review the same way a format placeholder would be. Any log statement passing an object without `@` deserves a question, and any log statement passing a scalar does not, since `{Elapsed}` correctly binds the integer.

One event, two renderings, and the sink chooses

The same event can be rendered as structured data or as a line of text, and the difference is entirely the output configuration.

Rendered into JSON format, the properties appear alongside the timestamp, level and message.

json
{"Position": {"Latitude": 25, "Longitude": 134}, "Elapsed": 34}

Written to a file or the console, the template and properties are rendered into friendly human-readable text, just like a traditional logging library would produce.

code
09:14:22 [INF] Processed {"Latitude": 25, "Longitude": 134} in 34 ms.

Supporting structured data does not mean giving up text. The format-agnostic pipeline described in the feature list can emit events in plain text, JSON, in-memory `LogEvent` objects, Rx pipelines and other formats.

The consequence is that you do not choose a format at the call site, you choose it when you configure sinks. And note what the human-readable form actually contains: the position has been serialized inline as JSON. Readable, yes, and still not something you can query without parsing.

Getting started installs three packages, not one

Serilog is installed from NuGet, and the README is explicit that one or more sinks need to be installed as well in order to view log events.

bash
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

The extra line is not optional in practice. The core package holds the API and the pipeline; a sink is what actually writes an event somewhere. The two chosen here are the pretty-printing console sink and a rolling file set, and the wider list covers on-premises and cloud-based log servers, databases and message queues.

The consequence is that the first surprise for a new user is a library that installs cleanly and logs nothing at all. If you add Serilog alone, events are constructed, passed through the pipeline, and then have nowhere to go. Installing at least one sink is the difference between a working setup and a silent one.

The static Log class is a convenience the README makes optional

Two ways of obtaining a logger appear in this README, and the feature list describes the design as zero-shared-state `Logger` objects with an optional global static `Log` class.

The short form creates a logger and holds it in a local.

csharp
using var log = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("log.txt")
    .CreateLogger();

log.Information("Hello, Serilog!");

The longer form assigns to the static class instead, and the README says this is normally done in Program.cs.

The static class exists because passing a logger through every layer of an application is tedious. The cost is that it is global mutable state: harder to substitute in a test, and tied to process lifetime.

The consequence is that the static form and the injectable form are not equally good, and the README presents them as alternatives rather than ranking them. The `using var` form scopes the logger's lifetime to the block and disposes it, which is the behaviour you want in tests and in short-lived tools.

The finally block exists because the pipeline buffers

The fullest example in the README wraps the entire program in a try, catch and finally, and the reason is visible in a single line.

csharp
finally
{
    await Log.CloseAndFlushAsync(); // ensure all logs written before app exits
}

A comment states the intent directly: all logs written before app exits.

Two details follow from that. The call is awaited rather than invoked and forgotten, because it is asynchronous. And it lives in a finally, so it runs on the exception path as well as the normal one.

The consequence for anyone copying a snippet is that the finally is the part that gets dropped, and it is the part that matters most in exactly the situations people care about, which are crashes and deploys. Note also the catch block logs the exception object itself, `Log.Error(ex, "Unhandled exception")`, which is how the exception's structure is preserved rather than flattened into a message.

The build is PowerShell, with a pinned SDK and a custom NuGet config

The repository root describes a build that is not a bare `dotnet build`.

`Build.ps1` and `RunPerfTests.ps1` are PowerShell scripts, so the supported entry point assumes PowerShell. `global.json` pins the .NET SDK the project builds against. `Directory.Build.props` and `Directory.Version.props` hold build-wide and version-wide MSBuild settings, `NuGet.config` configures package sources, and `Serilog.sln` is the solution, with `Serilog.sln.DotSettings` carrying ReSharper settings beside it.

Two files in that list are unusual enough to be worth opening first. `INFLUENCES.md` is not standard in a .NET repository and appears to record what shaped the project. And `results/` sits in the tree alongside the performance test script, which suggests benchmark output is committed.

One more piece of housekeeping: the default branch is `dev` rather than `main`. For a contributor that is the branch to work from, and for someone reading release notes it is worth knowing that the branch name does not match the convention most tooling assumes.

What Serilog cannot do for you, and where that work moves

Four limits, and each one relocates work rather than removing it.

It cannot write anything on its own. One or more sinks must be installed separately, so the first thing Serilog cannot do is be the only package you add.

It cannot give you a searchable log store. The README's argument is that back-ends capable of recording structured event data make log searches and analysis possible without log parsing or regular expressions. That capability belongs to the backend, not to the library, and Serilog's role ends at handing a structured event to a sink.

It cannot supply the performance claim with evidence. The feature list says the library is efficient when enabled and has extremely low overhead when a logging level is switched off. That is a qualitative statement and no figure appears alongside it, although the repository contains a performance test script and a results directory. Measure it in your own application, at your own volumes.

And enrichment is opt-in. Scoped `LogContext` properties, thread and process identifiers, and correlation ids such as `HttpRequestId` all have to be pushed in. Nothing is populated for you, which means the correlation data that makes distributed logs useful is exactly the data you have to remember to add.

Editorial conclusion

Adopt Serilog when your .NET application emits events you will later need to search or aggregate, because that is the case its design actually serves and the case where a pre-formatted string has already thrown the data away. Do not adopt it expecting a single package to get you logging, since Serilog writes nowhere until you add a sink, and do not adopt it to reach a prebuilt log platform, because the search capability lives in the backend rather than in the library. Before you commit, read the message template documentation rather than copying a format string out of habit, since the named-parameter and destructuring syntax is a DSL that extends .NET format strings, and measure the overhead yourself rather than relying on the feature list's qualitative claim about performance when a level is switched off.

Frequently asked questions

What is serilog?

A diagnostic logging library for .NET applications that records structured event data instead of pre-formatted text. It runs on all recent .NET platforms and supports sinks for files, the console, on-premises and cloud-based log servers, databases and message queues.

Is serilog a NuGet package?

Yes, it is installed from NuGet with dotnet add package Serilog. You also add at least one sink to view log events, and the README uses Serilog.Sinks.Console together with Serilog.Sinks.File.

What are the benefits of using serilog?

Back-ends capable of recording structured event data make log searches and analysis possible without log parsing or regular expressions. Serilog captures the values behind each named parameter at the call site instead of formatting text immediately, and it still renders to human-readable text for files and the console.

How do you install serilog?

With three package references. Serilog itself provides the API and pipeline, and at least one sink is needed as well, because without a sink there is nowhere for events to go. The README lists Serilog, Serilog.Sinks.Console and Serilog.Sinks.File.

How do you use serilog in C#?

Build a LoggerConfiguration, add sinks with WriteTo, call CreateLogger, then write events with a message template. The simplest form assigns the result to the static Log.Logger, normally in Program.cs, and the fuller example wraps the program in try, catch and finally with CloseAndFlushAsync.

Which is better, serilog or NLog?

The README offers no head-to-head verdict against NLog. It states that, unlike other logging libraries, Serilog is built from the ground up to record structured event data.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. serilog/serilog on GitHub
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/serilog-serilog.svg)](https://hysenlabs.com/projects/serilog-serilog)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/serilog-serilog.svg)](https://hysenlabs.com/projects/serilog-serilog)

Was this useful?