CodeGym /Courses /C# SELF /Structured logging and Ser...

Structured logging and Serilog

C# SELF
Level 64 , Lesson 1
Available

1. Introduction

If plain text logging is your diary with notes like "I ate today", structured logging turns each entry into a card with fields: {Date: ..., Event: "Ate", Calories: 500, Dish: "Fried meat"}. That means you can not only read the diary later, but also build a calories chart for the month, filter events by dish, and find out when you ate too late.

Why is plain text not enough?

Plain text is simple... until it's not. Try to collect statistics on errors in logs, sales volumes in an online shop, or the action trail of a single user by their ID (for example, 42) when all data is just pages of text. Structured logging lets you attach analysis and even AI to logs. With it you can spot anomalies, build dashboards and react to issues automatically.

Advantages

  • Allows logging not only messages but also related data (fields/properties).
  • Logs can be analyzed automatically: counted, filtered, used to build reports.
  • Standardized formats like JSON are used, which are easy for machines to parse.

Serilog: what is it and why use it?

Serilog (official site: serilog.net, docs: github.com/serilog/serilog/wiki) is a popular library for structured logging in .NET. It integrates nicely with Microsoft.Extensions.Logging, supports output to dozens of systems (file, console, Seq, ElasticSearch, Grafana, Azure, etc.), has minimal performance impact and is easy to configure.

How does Serilog differ from "just logging"?

  • Structure: logs are objects with fields you can filter by (for example, all errors from user with ID 42).
  • Formats: can write not only text but also JSON, XML, which is handy for further processing.
  • Flexibility: many ready-made sink packages to send logs anywhere.

Structure of a log entry with Serilog

Let's look at a structured log before writing code.

{
  "Timestamp": "2024-06-22T10:23:45.123Z",
  "Level": "Information",
  "MessageTemplate": "User {UserId} logged in",
  "Properties": {
    "UserId": 42,
    "IpAddress": "127.0.0.1"
  }
}

Even a basic analysis will understand: this is about user №42 and their IP address.

2. Installation and basic setup of Serilog in a C# project

Step 1. Install NuGet packages

In Rider/Visual Studio via NuGet Package Manager install:

  • Serilog
  • Serilog.Sinks.Console (console output)
  • Serilog.Extensions.Logging (for integration with Microsoft.Extensions.Logging)

Via command line:

dotnet add package Serilog
dotnet add package Serilog.Sinks.Console

Step 2. Minimal setup

Add configuration in Program.cs and write the first log.

using System;
using Serilog;

namespace MySuperApp
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. Basic Serilog setup: console output
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Debug()
                .WriteTo.Console()
                .CreateLogger();

            // 2. Example of structured logging
            int userId = 42;
            string ip = "127.0.0.1";
            Log.Information("User {UserId} logged in with IP {IpAddress}", userId, ip);

            Log.CloseAndFlush();
        }
    }
}

In the console you'll see something like:

[10:30:16 INF] User 42 logged in with IP 127.0.0.1

From here it's easy to direct output to a JSON file, Seq or another system.

3. Log formatting: Message Template

In Serilog you use template syntax instead of string concatenation:

Log.Information("Operation {Operation} on file {FileName}", "delete", "test.txt");

This is not just a pretty syntax — it's structured logging: the entry will contain fields Operation and FileName, available for filtering and aggregation.

How is this different from string.Format?

string.Format("Operation {0} on file {1}", operation, fileName) simply stitches a string with placeholders {0}, {1}. Serilog creates separate fields you can later analyze.

Flexible configuration: levels, filters, multiple "sinks"

Serilog can write logs to multiple places at once.

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

Now logs go both to the console and to a file with daily rotation.

4. Example

Suppose we're making a console "notes" app that lets users create entries. Let's add structured logging of actions.

using System;
using Serilog;

namespace NotesApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Information()
                .WriteTo.Console()
                .WriteTo.File("notes-log.json", rollingInterval: RollingInterval.Day, 
                              formatter: new Serilog.Formatting.Json.JsonFormatter())
                .CreateLogger();

            Console.WriteLine("Enter user name:");
            string userName = Console.ReadLine();

            Log.Information("User {UserName} started the NotesApp", userName);

            while (true)
            {
                Console.WriteLine("Enter note text (or type 'exit'):");
                string note = Console.ReadLine();

                if (note == "exit")
                {
                    Log.Information("User {UserName} ended the session", userName);
                    break;
                }

                Log.Information("User {UserName} created a note: {NoteText}", userName, note);
            }

            Log.CloseAndFlush();
        }
    }
}

Comment:
The log records who starts the program, what they type and when they finish. In the file notes-log.json each entry is a JSON object that's easy to analyze.

5. Useful nuances

Best practices for structured logging

  • Don't overuse Debug/Trace levels in production — use Information and Warning reasonably.
  • Use named parameters in templates instead of string concatenation.
  • Never log sensitive data (passwords, tokens, keys).
  • Log important business events, not only errors and exceptions.
  • Set up rotation and cleanup so logs don't fill up disk space.

Visualization and analysis: Seq, Kibana, Application Insights

Serilog supports many sinks — endpoints where logs are sent.

Sink Short description Where used
Console Directly to the console Development, tests
File To a local or network file Small projects, dev
Seq Web UI with filtering and dashboards Enterprise, analytics
ElasticSearch Powerful storage and analysis system Large companies
Azure Application Insights Cloud monitoring and telemetry Azure-heavy services

Seq (datalust.co/seq) is a very popular solution for development and internal use: convenient filtering, field search and quick deployment.

Tables and visualization

Below is a short table of what you can log structurally:

What we log How it looks in Serilog Example value
User ID
{UserId}
123
Action
{Action}
"Delete"
Error
Log.Error(ex, "Error in {Module}")
"Registration module"
Operation time {Elapsed:0.000} sec 1.234
File name
{FileName}
"report.pdf"

Cool tricks and additional capabilities of Serilog

  • Enrichers: add properties to every log (for example, .Enrich.WithMachineName()).
  • Correlated logs: add a RequestId to correlate a chain of events.
  • Configuration via appsettings.json: convenient for production.
{
  "Serilog": {
    "MinimumLevel": "Debug",
    "WriteTo": [
      { "Name": "Console" },
      { "Name": "File", "Args": { "path": "log.txt" } }
    ]
  }
}

Advanced sinks: you can send logs to Slack, Telegram, email (but be careful not to get a thousand emails for every error).

6. Practical part: integration with Microsoft.Extensions.Logging

In .NET it's common to use the standard ILogger interface to avoid being tied to a specific library. Serilog can be plugged in as a provider.

Step 1. Install the package

dotnet add package Serilog.Extensions.Logging

Step 2. Configuration

using Microsoft.Extensions.Logging;
using Serilog;

// ...

// Configure Serilog as usual:
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateLogger();

// Now use Microsoft.Extensions.Logging
var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddSerilog();
});

ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Test message: {TestValue}", 123);

// Don't forget to close:
Log.CloseAndFlush();

Comment:
Now all your code using ILogger<T> is independent of the provider — you can switch to NLog or Log4Net if you want.

7. Common mistakes when working with Serilog

Log overload: if you log everything, useful information becomes hard to find.

Logging exceptions as text: use the overload that accepts the exception — that way the error structure gets into the log.

try
{
    // some code
}
catch (Exception ex)
{
    Log.Error(ex, "An error occurred while executing the request");
}

Config abuse: don't turn configuration into a mess — add only necessary sinks and levels.

2
Task
C# SELF, level 64, lesson 1
Locked
Basic Structured Logging with Serilog
Basic Structured Logging with Serilog
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION