Exception Handling  

🧱 Lecture 12: Structured Logging & Monitoring

Previous lesson: 🧱 Lecture 11: Continuous Integration and Continuous Deployment (CI/CD) with Jenkins

🎯 Introduction

Logging is the eyes and ears of your application. When things go wrong in production, a well-structured log file is often the only thing standing between a quick fix and a sleepless night. In this guide, we'll walk through implementing Serilog in a .NET 8 Web API, configuring it to write structured JSON logs to separate files for Error, Information, and Debug levels.

Why Serilog?

Serilog is a diagnostic logging library for .NET applications. Unlike default logging, Serilog is built from the ground up to support structured data. This means your logs aren't just text strings; they are data objects that can be easily queried, filtered, and analyzed.

Step 1: Install the Essentials

First, we need to add the necessary NuGet packages to our project. These packages allow Serilog to integrate with ASP.NET Core, write to files, and format logs as JSON.

Run the following commands in your project directory:

dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.File
dotnet add package Serilog.Formatting.Compact
  • Serilog.AspNetCore: Integrates Serilog with the ASP.NET Core framework.

  • Serilog.Sinks.File: Writes log events to files.

  • Serilog.Formatting.Compact: Formats logs as compact JSON, saving space and making them machine-readable.

Step 2: Configuration Magic

One of the most powerful features of Serilog is its ability to be configured entirely via appsettings.json. We want to separate our logs into three distinct files based on severity:

  1. Error-yyyyMMdd.json: Critical issues that need immediate attention.

  2. Info-yyyyMMdd.json: General application flow and operational events.

  3. Debug-yyyyMMdd.json: Detailed diagnostic information for development and troubleshooting.

Open your

appsettings.json

and replace the default Logging section with this Serilog configuration:

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "logs/Error-.json",
          "restrictedToMinimumLevel": "Error",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/Info-.json",
          "restrictedToMinimumLevel": "Information",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/Debug-.json",
          "restrictedToMinimumLevel": "Debug",
          "rollingInterval": "Day",
          "formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
        }
      }
    ]
  }
}

Key Configurations:

  • rollingInterval: Sets the log file to roll over every Day, creating files like Error-20231025.json.

  • restrictedToMinimumLevel: Ensures that the "Error" file only contains errors (and higher), while "Info" captures information and above.

  • formatter: Uses the CompactJsonFormatter to output clean, structured JSON.

Step 3: Wire It Up in Program.cs

Now, let's hook Serilog into the application startup pipeline. We'll configure it to load settings from our JSON configuration and replace the default logger. We also want to wrap our application startup in a try/catch block to capture any fatal errors that prevent the app from starting.

Update Program.cs

using Serilog;
// 1. Initialize the bootstrap logger
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();
try
{
    var builder = WebApplication.CreateBuilder(args);

    // 2. Add Serilog to the Host
    builder.Host.UseSerilog((context, services, configuration) => configuration
        .ReadFrom.Configuration(context.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext());
    // ... (Rest of your service registrations) ...
    var app = builder.Build();
    // ... (Middleware pipeline) ...
    app.Run();
}
catch (Exception ex)
{
    // 3. Capture fatal startup errors
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    // 4. Ensure logs are flushed before exit
    Log.CloseAndFlush();
}
https___dev-to-uploads.s3.amazonaws.com_uploads_articles_l96jeykck2uaxpze784x

The Result

Run your application, and you will see a logs folder appear in your project root. Inside, you'll find your structured JSON log files:

logs/Error-20260206.json

logs/Info-20260206.json

logs/Debug-20260206.json

Each line in these files is a valid JSON object, making it incredibly easy to ingest into log management tools like Datadog, Splunk,Seq, or the ELK stack (Elasticsearch, Logstash, Kibana).

Conclusion

By implementing structured logging with Serilog, you've moved beyond simple text files to a robust, queryable logging infrastructure. Configuring separate files for different log levels ensures that critical errors don't get lost in the noise of information logs, helping you maintain a healthy and debuggable application.

Next Lecture Preview

Lecture 13 : Centralized Error Handling & Validation

Using middleware for error handling, implementing FluentValidation, and maintaining consistent API responses.