Serilog

Latest logging framework released in 2013. Designed specifically for structured logging with standard JSON format support. Provides rich sinks (outputs), powerful filtering capabilities, and efficient querying of complex data types.

Logging.NETC#Structured LoggingASP.NET CoreDebug

Library

Serilog

Overview

Serilog is a diagnostic logging library for .NET applications that provides "simple .NET logging with fully structured events." Unlike other logging libraries, it's designed from the ground up to record structured event data, supporting everything from simple applications to complex distributed systems. It features asynchronous logging with virtually zero performance impact.

Details

Serilog remains one of the most popular logging libraries in the .NET ecosystem as of 2025, with active development. It's fully compatible with the latest .NET versions including .NET 8, and can be used across all .NET applications including ASP.NET Core, Blazor, console apps, and desktop applications. Message templates are a simple DSL that extends .NET format strings, helping with parameter naming and value serialization.

Key Features

  • Structured Logging: Records event data in structured format for flexible search and sorting
  • Rich Sinks: Output to files, console, databases, cloud log servers, and more
  • Asynchronous Logging: Near-zero performance impact asynchronous processing
  • Rich Enrichment: Add context information like LogContext, thread ID, correlation ID
  • Flexible Configuration: Support for Fluent API, appsettings.json, and XML configuration
  • Seq Integration: Structured log analysis tool for local development environments

Pros and Cons

Pros

  • Most popular and fastest logging framework in the .NET ecosystem
  • Structured logging as core functionality with rich data types and efficient querying
  • Simple API with intuitive Fluent configuration system
  • High-performance asynchronous logging and message batching
  • Rich ecosystem (100+ Sinks, ASP.NET Core integration, etc.)
  • Full compatibility with latest .NET versions including .NET 8

Cons

  • Initial learning curve for structured logging concepts required
  • Too many third-party Sink options can be overwhelming
  • File and console logging not recommended for production environments
  • Improper LogContext usage can cause memory leaks
  • Complex configurations may impact performance
  • Potential conflicts when used simultaneously with other .NET logging libraries

References

Code Examples

Basic Setup

# Install basic packages
dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

# For ASP.NET Core integration
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Settings.Configuration

# Additional Sink examples
dotnet add package Serilog.Sinks.Seq
dotnet add package Serilog.Sinks.MSSqlServer
dotnet add package Serilog.Sinks.Elasticsearch

Simple Logger Creation

using Serilog;

class Program
{
    static void Main(string[] args)
    {
        // Basic logger configuration
        using var log = new LoggerConfiguration()
            .WriteTo.Console()
            .WriteTo.File("log.txt")
            .CreateLogger();

        // Simple log output
        log.Information("Hello, Serilog!");
        
        // Structured log output
        var position = new { Latitude = 35.6762, Longitude = 139.6503 };
        var elapsedMs = 42;
        log.Information("Processed position {@Position} in {Elapsed} ms", position, elapsedMs);
    }
}

Advanced Logger Configuration

using Serilog;
using Serilog.Events;
using Serilog.Formatting.Json;

class AdvancedLoggingExample
{
    static void Main(string[] args)
    {
        // Global logger with advanced configuration
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Debug()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .MinimumLevel.Override("System", LogEventLevel.Warning)
            .Enrich.FromLogContext()
            .Enrich.WithMachineName()
            .Enrich.WithThreadId()
            .WriteTo.Console(
                outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
            .WriteTo.File(
                new JsonFormatter(),
                "logs/app.json",
                rollingInterval: RollingInterval.Day,
                rollOnFileSizeLimit: true,
                fileSizeLimitBytes: 10_000_000,
                retainedFileCountLimit: 30)
            .WriteTo.Seq("http://localhost:5341", apiKey: "your-api-key")
            .CreateLogger();

        try
        {
            // Main application logic
            Log.Information("Application started");
            
            ProcessData();
            
            Log.Information("Application completed successfully");
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Unhandled exception occurred in application");
        }
        finally
        {
            await Log.CloseAndFlushAsync(); // Flush all logs before app termination
        }
    }
    
    static void ProcessData()
    {
        using (LogContext.PushProperty("Operation", "DataProcessing"))
        {
            Log.Information("Starting data processing");
            
            // Data processing simulation
            var records = new[] { "Record1", "Record2", "Record3" };
            
            foreach (var record in records)
            {
                using (LogContext.PushProperty("RecordId", record))
                {
                    Log.Debug("Processing record {RecordId}", record);
                    
                    // Processing logic
                    Thread.Sleep(100);
                    
                    Log.Information("Completed processing record {RecordId}", record);
                }
            }
            
            Log.Information("Data processing completed. Records processed: {RecordCount}", records.Length);
        }
    }
}

ASP.NET Core Integration

// Program.cs (.NET 8 Web API)
using Serilog;
using Serilog.Events;

var builder = WebApplication.CreateBuilder(args);

// Serilog configuration
builder.Host.UseSerilog((context, services, configuration) => configuration
    .ReadFrom.Configuration(context.Configuration)
    .ReadFrom.Services(services)
    .Enrich.FromLogContext()
    .Enrich.WithProperty("ApplicationName", "MyWebAPI")
    .WriteTo.Console()
    .WriteTo.File(
        "logs/webapi-.log",
        rollingInterval: RollingInterval.Day,
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.Seq("http://localhost:5341"));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Serilog request logging middleware
app.UseSerilogRequestLogging(options =>
{
    options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
    options.GetLevel = (httpContext, elapsed, ex) => ex != null
        ? LogEventLevel.Error
        : httpContext.Response.StatusCode > 499
            ? LogEventLevel.Error
            : LogEventLevel.Information;
    options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
    {
        diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
        diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
        diagnosticContext.Set("UserAgent", httpContext.Request.Headers.UserAgent.FirstOrDefault());
    };
});

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.MapControllers();

app.Run();
// appsettings.json
{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Seq"],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
        }
      },
      {
        "Name": "File",
        "Args": {
          "path": "logs/webapi-.log",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
        }
      },
      {
        "Name": "Seq",
        "Args": {
          "serverUrl": "http://localhost:5341"
        }
      }
    ],
    "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
  }
}

Controller Usage Example

using Microsoft.AspNetCore.Mvc;
using Serilog;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly ILogger<UsersController> _logger;

    public UsersController(ILogger<UsersController> logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<User>> GetUser(int id)
    {
        using (_logger.BeginScope(new Dictionary<string, object> { ["UserId"] = id }))
        {
            _logger.LogInformation("User information request: {UserId}", id);

            try
            {
                // Business logic simulation
                if (id <= 0)
                {
                    _logger.LogWarning("Invalid user ID specified: {UserId}", id);
                    return BadRequest("Invalid user ID");
                }

                var user = await GetUserFromDatabase(id);
                
                if (user == null)
                {
                    _logger.LogWarning("User not found: {UserId}", id);
                    return NotFound();
                }

                _logger.LogInformation(
                    "User information retrieved successfully: {UserId}, Name: {UserName}", 
                    user.Id, user.Name);
                
                return Ok(user);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "Error occurred while retrieving user information: {UserId}", id);
                return StatusCode(500, "Internal server error");
            }
        }
    }

    private async Task<User?> GetUserFromDatabase(int id)
    {
        // Database access simulation
        _logger.LogDebug("Retrieving user information from database: {UserId}", id);
        
        await Task.Delay(50); // DB access simulation
        
        return id switch
        {
            1 => new User { Id = 1, Name = "John Doe", Email = "[email protected]" },
            2 => new User { Id = 2, Name = "Jane Smith", Email = "[email protected]" },
            _ => null
        };
    }
}

public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Email { get; set; } = string.Empty;
}

Asynchronous Logging and Performance Optimization

using Serilog;
using Serilog.Events;

class PerformanceOptimizedLogging
{
    static void Main(string[] args)
    {
        // High-performance asynchronous logging configuration
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .WriteTo.Async(a => a.File(
                "logs/performance.log",
                rollingInterval: RollingInterval.Day,
                buffered: true,
                flushToDiskInterval: TimeSpan.FromSeconds(1),
                outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}"))
            .WriteTo.Async(a => a.Console())
            .CreateLogger();

        // Performance test
        var stopwatch = Stopwatch.StartNew();
        
        Parallel.For(0, 10000, i =>
        {
            // Conditional log output (performance optimization)
            if (Log.IsEnabled(LogEventLevel.Debug))
            {
                Log.Debug("Loop processing: {Iteration}", i);
            }
            
            if (i % 1000 == 0)
            {
                Log.Information("Progress: {Progress}%", (double)i / 100);
            }
        });
        
        stopwatch.Stop();
        Log.Information("Processing completed: {ElapsedMs} ms", stopwatch.ElapsedMilliseconds);
        
        await Log.CloseAndFlushAsync();
    }
}