Serilog sinks are the output destinations where your log events land. Unlike most logging frameworks where you get a handful of providers, Serilog has over 100 community-maintained sink packages -- from console and rolling files to Elasticsearch, Splunk, Azure Table Storage, and Slack. Understanding how to configure, combine, and filter sinks is what makes Serilog genuinely powerful in production.
This guide covers the most important Serilog sinks you'll use as a .NET developer: the Console sink with JSON formatting, rolling File sink, Seq for structured log querying, async buffering for production performance, and how to combine multiple sinks with per-sink level filtering.
Make sure you have the basics of Serilog in .NET and how to set up Serilog in ASP.NET Core covered before diving into sink-specific configuration.
What Are Serilog Sinks?
A sink is a destination that receives a log event after it passes through the enrichment and filter pipeline. Sinks are responsible for:
- Formatting the event (text template or JSON)
- Writing it to the output (console, file, database, HTTP API)
- Buffering if the output has latency (async sinks)
Sinks implement ILogEventSink in Serilog's core library, making it easy for the community to build and publish new destinations. Multiple sinks can be active simultaneously; every event that meets a sink's level threshold is delivered to all configured sinks.
The sink pipeline follows the Observer pattern: log events are notifications published by your application, and each sink is an observer that reacts to them independently. The Chain of Responsibility pattern governs which sinks receive which events based on configured minimum levels.
The Console Sink
The Console sink is Serilog's most used output. It writes to standard output (stdout), which Docker and Kubernetes collect automatically.
dotnet add package Serilog.Sinks.Console
Basic Text Output
The default Console sink emits plain text. With no formatter specified, Serilog uses the output template to control what each line looks like:
.WriteTo.Console()
Default output template:
[14:23:01 INF] Creating order for customer cust-456
Custom Output Template
Output templates let you define exactly which properties appear on each line and in what order. The ExpressionTemplate formatter from Serilog.Expressions provides the most power for custom layouts:
.WriteTo.Console(
outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext}{NewLine}" +
" {Message:lj}{NewLine}{Exception}")
Output:
[14:23:01 INF] MyApp.OrderController
Creating order for customer cust-456 with 3 items
Useful template tokens:
| Token | Description |
|---|---|
{Timestamp:HH:mm:ss} |
Time with format |
{Level:u3} |
Three-char uppercase level (INF, WRN, ERR) |
{Level:w4} |
Four-char lowercase level (info, warn, erro) |
{SourceContext} |
Logger category (the T in ILogger<T>) |
{Message:lj} |
Message with literal string formatting |
{Exception} |
Full exception with stack trace |
{Properties} |
All structured properties as key=value |
{NewLine} |
System newline character |
JSON Console Output (Production/Container)
For production containers, emit JSON so log aggregators (Fluent Bit, Logstash, Vector, Azure Monitor) can parse structured properties without regex extraction:
dotnet add package Serilog.Formatting.Compact
using Serilog.Formatting.Compact;
.WriteTo.Console(new CompactJsonFormatter())
Or via appsettings.json:
{
"Serilog": {
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
}
}
]
}
}
CompactJsonFormatter produces one-line JSON per event:
{"@t":"2026-07-13T14:23:01.123Z","@mt":"Creating order for customer {CustomerId}","@l":"Information","CustomerId":"cust-456","SourceContext":"MyApp.OrderController","MachineName":"prod-worker-01"}
The File Sink with Rolling
The File sink writes log events to disk, with support for rolling by time interval or file size. Configure rolling and retention limits to prevent log files from filling your storage volume in production.
dotnet add package Serilog.Sinks.File
Daily Rolling File
Rolling by day creates a new file each day, with the date embedded in the filename. Set retainedFileCountLimit to automatically delete old files and prevent unbounded storage growth:
.WriteTo.File(
path: "logs/app-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 30,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}")
The - in the path becomes the date stamp: logs/app-20260713.log. Files older than retainedFileCountLimit days are deleted automatically.
Rolling by File Size
Size-based rolling creates a new file when the current file reaches a specified size limit, appending a numeric suffix to the filename:
.WriteTo.File(
path: "logs/app.log",
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 10_000_000, // 10 MB
retainedFileCountLimit: 5,
shared: true) // Allow multiple processes to write to the same file
Combined Daily + Size Rolling
For high-volume services, combine daily rolling with size limits. This ensures files roll both at midnight and whenever a single day's file grows too large:
.WriteTo.File(
path: "logs/app-.log",
rollingInterval: RollingInterval.Day,
rollOnFileSizeLimit: true,
fileSizeLimitBytes: 50_000_000, // 50 MB max per file
retainedFileCountLimit: 7)
When both are configured, files roll on whichever limit is hit first.
JSON File Output
For log aggregators that ingest files -- such as Filebeat, Fluentd, or Azure Monitor Agent -- use the CompactJsonFormatter or RenderedCompactJsonFormatter to emit structured JSON that preserves all properties:
using Serilog.Formatting.Compact;
.WriteTo.File(
formatter: new CompactJsonFormatter(),
path: "logs/app-.json",
rollingInterval: RollingInterval.Day)
Seq: The Structured Log Viewer
Seq is the most popular local log viewer for Serilog development. It's a .NET application that runs in Docker and provides a powerful web interface for querying structured log properties.
dotnet add package Serilog.Sinks.Seq
Start Seq Locally
Run Seq with Docker for local development -- no installation required. The Seq UI is available on port 8081 and accepts log events on port 5341:
docker run -d
--name seq
-e ACCEPT_EULA=Y
-v seq-data:/data
-p 5341:80
datalust/seq
Navigate to http://localhost:5341 to access the Seq dashboard.
Configure the Seq Sink
Add the Seq sink to your Serilog configuration and point it at your local or production Seq server. The API key is optional for local development but required for production Seq instances:
.WriteTo.Seq("http://localhost:5341")
Or in appsettings.Development.json:
{
"Serilog": {
"WriteTo": [
{
"Name": "Seq",
"Args": {
"serverUrl": "http://localhost:5341",
"apiKey": "" // Optional: leave empty for local dev
}
}
]
}
}
Querying in Seq
Once your application is running and sending events to Seq, you can query on any structured property:
// Find all errors for a specific order
OrderId = 5678 and @Level = 'Error'
// Find slow requests
Elapsed > 1000
// Find all logs for a specific user in the last hour
UserId = 'user-abc' and @t > Now() - 1h
This is where structured logging pays dividends. Instead of grep-ing through text files, you run property-based queries that return results in milliseconds.
Per-Sink Level Filtering
Write different levels to different sinks. For example, log everything to console during development but only Warning and above to a file:
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug)
.WriteTo.File(
path: "logs/warnings-.log",
rollingInterval: RollingInterval.Day,
restrictedToMinimumLevel: LogEventLevel.Warning)
.WriteTo.Seq(
serverUrl: "http://localhost:5341",
restrictedToMinimumLevel: LogEventLevel.Information)
Or in appsettings.json:
{
"Serilog": {
"WriteTo": [
{
"Name": "Console",
"Args": { "restrictedToMinimumLevel": "Debug" }
},
{
"Name": "File",
"Args": {
"path": "logs/warnings-.log",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Warning"
}
}
]
}
}
Sub-Loggers for Advanced Routing
For more complex routing (e.g., send only Error events from a specific namespace to a different sink), use a sub-logger:
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(e => e.Level >= LogEventLevel.Error)
.Filter.ByIncludingOnly(Matching.FromSource("MyApp.PaymentService"))
.WriteTo.File("logs/payment-errors-.log"))
This pattern creates a filtered sub-pipeline that routes only critical payment service errors to a dedicated file -- useful for compliance and security audit trails.
Async Sink Wrapper
By default, Serilog sinks write synchronously. For production scenarios where sink I/O (especially network calls to Seq, Elasticsearch, or cloud logging services) could block the request thread, wrap sinks in the async adapter:
dotnet add package Serilog.Sinks.Async
.WriteTo.Async(a =>
{
a.Console();
a.File("logs/app-.log", rollingInterval: RollingInterval.Day);
a.Seq("https://logs.example.com");
}, bufferSize: 10_000, blockWhenFull: false)
bufferSize controls the in-memory queue size. blockWhenFull: false (default) drops events if the queue fills up rather than blocking the application -- the right choice for most web applications where dropping a log entry is preferable to adding latency to a user request.
For applications where log delivery is critical (financial audit trails, compliance logging), set blockWhenFull: true -- but be aware this can impact application throughput if the sink falls behind.
Production Sink Recommendations
Choosing the right sink depends on your deployment environment and observability requirements. These are the most commonly used production sinks for different deployment targets.
For Azure Applications
Application Insights is the natural choice for Azure-hosted services. It integrates with Azure Monitor for alerting, dashboards, and distributed tracing, requiring minimal additional infrastructure:
dotnet add package Serilog.Sinks.ApplicationInsights
.WriteTo.ApplicationInsights(
services.GetRequiredService<TelemetryConfiguration>(),
TelemetryConverter.Traces)
Or via the Azure Monitor OpenTelemetry exporter -- increasingly the preferred approach in .NET 9/10:
builder.Services.AddOpenTelemetry()
.WithLogging(logging => logging.AddAzureMonitorLogExporter());
For Elasticsearch / OpenSearch
The Elasticsearch sink is the standard choice for ELK (Elasticsearch, Logstash, Kibana) and OpenSearch stacks, common in containerized microservices environments:
dotnet add package Serilog.Sinks.Elasticsearch
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
AutoRegisterTemplate = true,
IndexFormat = "myapp-logs-{0:yyyy.MM.dd}"
})
For SQL Server (compliance and audit logs)
When you need queryable log storage with retention policies enforced by your database, or when compliance requirements mandate log data in SQL Server, the MSSqlServer sink provides column mapping and table creation:
dotnet add package Serilog.Sinks.MSSqlServer
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: new MSSqlServerSinkOptions
{
TableName = "AppLogs",
AutoCreateSqlTable = true
})
Combining Multiple Sinks
A typical production configuration routes events to multiple sinks with different minimum levels. Console gets everything for container log capture, while the structured sink (Seq, Application Insights) gets Information and above:
{
"Serilog": {
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact",
"restrictedToMinimumLevel": "Information"
}
},
{
"Name": "File",
"Args": {
"path": "logs/errors-.log",
"rollingInterval": "Day",
"retainedFileCountLimit": 30,
"restrictedToMinimumLevel": "Warning"
}
}
],
"Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
}
}
This setup:
- Writes all
Information+ events as JSON to console (picked up by container log aggregators) - Writes all
Warning+ events to a rolling file (local disk backup, useful for on-premises) - Retains 30 days of warning/error logs locally
Frequently Asked Questions
These questions cover the most common points of confusion when working with Serilog sinks in .NET applications.
What are Serilog sinks?
Serilog sinks are output destinations that receive log events after they pass through the enrichment and filter pipeline. Each sink independently formats and writes events to a specific target -- console, file, Seq, Elasticsearch, cloud services, etc. Multiple sinks can be active simultaneously.
How many sinks can I configure at once?
As many as you need. Every log event that meets a sink's level threshold is delivered to all configured sinks. Use per-sink restrictedToMinimumLevel to route different severity levels to different destinations.
Should I use the async sink wrapper?
Use async wrapping for any sink with I/O latency -- network calls (Seq, Elasticsearch, Azure), large file writes. For simple console sinks in lightweight apps, sync is fine. For production APIs, async wrapping keeps log I/O off the request thread.
What is Seq and do I need it?
Seq is a local structured log viewer. It's optional but highly recommended for development: it provides a query interface for your structured log properties, making it much easier to debug issues than scanning flat text files. Run it in Docker with a single command and point Serilog.Sinks.Seq at http://localhost:5341.
What is CompactJsonFormatter?
CompactJsonFormatter (from Serilog.Formatting.Compact) formats each log event as a single-line JSON object. It implements the Compact Log Event Format (CLEF) -- a standard supported by Seq and other log aggregators. @t is the timestamp, @mt is the message template, @l is the level, and custom properties appear as top-level fields.
How do I write errors to a different file than info logs?
Use restrictedToMinimumLevel on the file sink: configure one File sink with restrictedToMinimumLevel: Warning and another with restrictedToMinimumLevel: Debug. Both sinks receive events at or above their threshold independently.
Can I use Serilog sinks without ASP.NET Core?
Yes. Serilog works in any .NET application -- console apps, Worker Services, Azure Functions, WPF. The sink packages are framework-agnostic. Only Serilog.AspNetCore and UseSerilogRequestLogging() require ASP.NET Core.

