Learnixo
Back to blog
AI Systemsintermediate

Serilog Sinks — Routing Logs to the Right Destinations

Configure Serilog sinks: Console for development, Seq for structured querying, Application Insights for Azure, file rolling sinks, sub-loggers for routing by level, and async sink wrapper for performance.

Asma Hafeez KhanMay 16, 20264 min read
LoggingSerilogSinksSeqApplication InsightsASP.NET Core.NET
Share:𝕏

What Sinks Are

Sinks: where Serilog writes log events.
  Console:              stdout — visible in Docker/Kubernetes logs
  File:                 rolling file — local disk, useful for audit logs
  Seq:                  structured log server — queryable, dev/staging
  Application Insights: Azure telemetry — production, Azure-hosted apps
  Elasticsearch:        distributed search — large-scale production
  EventHub / Kafka:     streaming — high-volume log pipelines

A single Serilog configuration can write to multiple sinks simultaneously.
Route different log levels to different sinks via sub-loggers.

Console and Seq Setup

C#
// NuGet: Serilog.AspNetCore, Serilog.Sinks.Console, Serilog.Sinks.Seq

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .Enrich.FromLogContext()
    .Enrich.WithMachineName()
    .WriteTo.Console(
        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}",
        theme: AnsiConsoleTheme.Code)
    .WriteTo.Seq(
        serverUrl: "http://seq-server:5341",
        restrictedToMinimumLevel: LogEventLevel.Information)
    .CreateLogger();

builder.Host.UseSerilog();

Application Insights Sink

C#
// NuGet: Serilog.Sinks.ApplicationInsights

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .Enrich.FromLogContext()
    .WriteTo.ApplicationInsights(
        telemetryConfiguration: TelemetryConfiguration.CreateDefault(),
        telemetryConverter:     TelemetryConverter.Traces)
    .CreateLogger();

// Or configure via appsettings.json (Serilog.Settings.Configuration)
// appsettings.json:
{
  "Serilog": {
    "WriteTo": [
      {
        "Name": "ApplicationInsights",
        "Args": {
          "connectionString": "InstrumentationKey=...",
          "telemetryConverter": "Serilog.Sinks.ApplicationInsights.TelemetryConverters.TraceTelemetryConverter, Serilog.Sinks.ApplicationInsights"
        }
      }
    ]
  }
}

Rolling File Sink for Audit Logs

C#
// NuGet: Serilog.Sinks.File

Log.Logger = new LoggerConfiguration()
    .WriteTo.File(
        path:             "logs/clinical-audit-.log",
        rollingInterval:  RollingInterval.Day,
        retainedFileCountLimit: 90,           // keep 90 days
        outputTemplate:   "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level}] {Message}{NewLine}{Exception}",
        shared:           false,              // don't share file across processes
        buffered:         false)              // flush immediately (audit logs need this)
    .CreateLogger();

// For clinical audit trails: retainedFileCountLimit should match regulatory requirements.
// HIPAA: 6 years. NHSDigital: 8 years. Check your jurisdiction.

Sub-Loggers for Routing

C#
// Write Warnings/Errors to Application Insights, everything else to Console only
// Sub-loggers let you route different levels to different sinks

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(e =>
            e.Level >= LogEventLevel.Warning)
        .WriteTo.ApplicationInsights(
            TelemetryConfiguration.CreateDefault(),
            TelemetryConverter.Traces))
    .WriteTo.Logger(lc => lc
        .Filter.ByIncludingOnly(e =>
            e.Properties.ContainsKey("AuditEvent"))
        .WriteTo.File(
            "logs/audit-.log",
            rollingInterval: RollingInterval.Day,
            retainedFileCountLimit: 365))
    .CreateLogger();

// Only Warning+ go to Application Insights (cost control)
// Only audit-tagged events go to the audit file

Async Sink Wrapper

C#
// NuGet: Serilog.Sinks.Async
// Wraps any sink in an async queue — fire-and-forget from the calling thread

Log.Logger = new LoggerConfiguration()
    .WriteTo.Async(a =>
    {
        a.Console();
        a.Seq("http://seq-server:5341");
    },
    bufferSize: 10000)    // buffer up to 10,000 events
    .CreateLogger();

// Without async: every log call blocks the thread until the sink writes
// With async: the call returns immediately; background thread drains the queue
// Use for: file sinks, network sinks (Seq, Elasticsearch) in production
// Audit sinks: do NOT use async — you want guaranteed writes even on crash

Reading Sink Configuration from appsettings

C#
// NuGet: Serilog.Settings.Configuration
// Allows changing log levels and sinks without recompiling

// appsettings.json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft":           "Warning",
        "Microsoft.EntityFrameworkCore": "Warning"
      }
    },
    "Enrich": ["FromLogContext", "WithMachineName"],
    "WriteTo": [
      { "Name": "Console" },
      { "Name": "Seq", "Args": { "serverUrl": "http://seq:5341" } }
    ]
  }
}

// Program.cs
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .CreateLogger();

Production issue I've seen: A team wrote all logs synchronously to a remote Seq server. Under traffic spikes, each HTTP request blocked for 15-30ms waiting for the log sink to flush over the network. The Serilog write was on the hot path — every log call blocked the request thread. Adding .WriteTo.Async(a => a.Seq(...)) decoupled logging from the request thread and brought 95th-percentile response time down from 85ms to 45ms. Async sinks are not optional for network sinks in production.


Key Takeaway

Use Console in development, Seq for staging (structured querying), and Application Insights for production Azure environments. Use sub-loggers to route Warnings/Errors to alerting sinks and audit events to file. Always wrap network sinks (Seq, Elasticsearch, Application Insights) in WriteTo.Async() — synchronous network writes block request threads. Do not use async for regulatory audit file sinks — you need guaranteed writes even on crash.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.