NLog vs Serilog is one of the most common logging debates in the .NET community. Both libraries are mature, battle-tested, and supported under Microsoft.Extensions.Logging via ILogger<T> -- which means either one drops into an ASP.NET Core application with minimal setup. Yet they make very different design choices, and those differences become significant at scale.
This guide compares NLog and Serilog across the dimensions that matter in production: configuration style, structured logging support, performance, rules and filtering power, ecosystem size, and migration paths. By the end, you'll have a clear picture of which library fits your team and your use case -- and you'll see the scenarios where the answer is genuinely "either one works fine."
This article assumes you're already familiar with the basics of both libraries. If you need a foundation, the NLog in .NET Complete Guide and the Serilog in .NET Complete Guide are the starting points. Both sit under the broader Logging in .NET: The Complete Developer's Guide.
TL;DR Decision Matrix
Before the deep dive, here is the summary for teams that need a quick answer:
| Factor | NLog Wins | Serilog Wins | Equal |
|---|---|---|---|
| Configuration style | Ops team manages XML/JSON | Dev team prefers C# fluent API | — |
| Rules / routing power | ✅ Complex routing by name + level + conditions | — | — |
| Structured logging | — | ✅ First-class message templates, object destructuring | — |
| Performance | — | — | ✅ Both fast with async wrappers |
| Ecosystem size | — | ✅ More sinks, more community extensions | — |
| AOT / .NET 8+ | ✅ NLog 6 full AOT support | ⚠️ AOT support improving but not complete in all sinks | — |
| Learning curve | Steeper (XML config) | Shallower (fluent C#) | — |
| Migration effort | Low (both use ILogger |
Low (both use ILogger |
— |
If your team has a strong ops culture and manages config files without touching code, NLog is the natural fit. If your team writes C# and wants maximum structured logging richness with a large ecosystem of prebuilt sinks, Serilog wins. For most new greenfield .NET applications, Serilog has the edge -- but NLog remains an excellent choice with unique strengths in routing and enterprise configuration.
Configuration Style
This is the most visible difference between the two libraries and often the deciding factor for teams. Before evaluating features, performance, or ecosystem size, ask one question: who owns the logging configuration in your organization?
NLog: Config-File First
NLog is designed to be configured by operations teams without changing application code. The config lives in nlog.config (XML) or appsettings.json, it supports hot reload (autoReload="true"), and it can be swapped between environments by your deployment pipeline:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
autoReload="true">
<targets>
<target xsi:type="File" name="file"
fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${message}${exception:format=tostring}" />
</targets>
<rules>
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="*" minlevel="Info" writeTo="file" />
</rules>
</nlog>
The targets and rules can be changed without recompiling. The operations team can suppress a noisy namespace or redirect errors to a new target by editing one file and restarting (or waiting for hot reload). No developer needed.
Serilog: Code-First Fluent API
Serilog is designed by developers for developers. Its configuration is a fluent C# builder chain that reads like a sentence:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.WriteTo.Console()
.WriteTo.File("logs/app-.log", rollingInterval: RollingInterval.Day)
.CreateLogger();
This approach has excellent IntelliSense support, compile-time validation, and integrates naturally with .NET's DI system. However, changing the configuration requires a code change and redeployment. For teams where developers manage the full stack, this is a non-issue. For enterprises with separate dev and ops teams, it can be a friction point.
Serilog also supports appsettings.json configuration via Serilog.Settings.Configuration, so you can get the best of both worlds -- define sink types and minimum levels in config, and fall back to code for complex enricher setup. For a detailed walkthrough, How to Set Up Serilog in ASP.NET Core covers both approaches.
Structured Logging
Both libraries support structured logging with message templates -- the pattern where {PropertyName} tokens in a format string become named properties on the log event rather than just formatted text.
// Both NLog and Serilog handle this identically via ILogger<T>
_logger.LogInformation("Order {OrderId} placed by {CustomerId} totalling {Amount:C}",
order.Id, order.CustomerId, order.Total);
Under ILogger<T>, both libraries receive the same EventId, LogLevel, message template, and parameter values. The difference is in what they do with complex objects.
Object Destructuring: Serilog's Advantage
Serilog introduced the @ destructuring operator, which serializes an entire object as structured data rather than calling ToString():
// Serilog-native API
Log.Information("Order {@Order} was placed", order);
// → order.Id, order.Items[], order.Total captured as structured fields
// NLog equivalent: must use explicit properties or JsonLayout
_logger.LogInformation("Order placed: {OrderJson}", JsonSerializer.Serialize(order));
When using Serilog's native ILogger (not Microsoft.Extensions.Logging.ILogger<T>), the @ prefix causes the entire object graph to be captured as nested structured fields -- visible in Seq, Elasticsearch, or any structured log viewer. This is Serilog's flagship differentiator.
NLog can achieve similar results via JsonLayout and ${all-event-properties}, but it requires configuring the layout appropriately for each target. The structured data is there -- it just takes more explicit setup via layout renderer configuration in NLog's target definitions.
For teams that make heavy use of structured log queries -- filtering by order.CustomerId in Seq or Elasticsearch -- Serilog's first-class destructuring is a meaningful advantage.
Rules and Routing: NLog's Advantage
NLog's rules engine is substantially more powerful than Serilog's level overrides. This is the area where NLog wins most clearly.
NLog: Per-Logger, Per-Level, Per-Condition Routing
NLog can route a single log event to different targets based on logger name, level range, and expression conditions -- all configurable without code:
<rules>
<!-- Audit logs → dedicated audit file, stop here -->
<logger name="SecurityAudit" minlevel="Info" writeTo="auditFile" final="true" />
<!-- Performance metrics → metrics target, stop here -->
<logger name="Performance.*" minlevel="Info" writeTo="metricsTarget" final="true" />
<!-- EF Core query noise → discard -->
<logger name="Microsoft.EntityFrameworkCore.Database.Command" maxlevel="Info" final="true" />
<!-- Framework warnings → file only -->
<logger name="Microsoft.*" minlevel="Warn" writeTo="file" final="true" />
<!-- Application → console + file -->
<logger name="*" minlevel="Debug" writeTo="console,file" />
</rules>
The when condition syntax adds per-event filtering inside rules -- ignore health check endpoints, throttle repeated messages, or route based on message content. The NLog rules and filters system covers the full condition language with production examples across multiple routing scenarios.
Serilog: Minimum Level + Filters
Serilog's primary routing mechanism is minimum level overrides per source context:
new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning)
.WriteTo.Console()
.WriteTo.File(...)
.CreateLogger();
Serilog also supports sub-loggers with independent sinks and levels via .WriteTo.Logger(...):
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(e => e.Properties.ContainsKey("AuditEvent"))
.WriteTo.File("audit-.log", rollingInterval: RollingInterval.Day))
This is powerful and covers most real-world routing needs. However, the NLog rules engine offers more granular control -- particularly for enterprises with complex routing requirements across many namespaces and targets. If your routing logic fits in 3-4 MinimumLevel.Override calls, Serilog handles it cleanly. If you need per-category routing to 6 different targets with level ranges and conditions, NLog's rules are easier to manage.
Performance
At realistic production volumes (hundreds to thousands of events per second), both libraries perform similarly when properly configured with async wrappers. The synchronous vs. asynchronous pattern matters far more than which library you chose.
NLog Performance
NLog's AsyncWrapper and BufferingWrapper decouple the calling thread from I/O, making the write latency irrelevant to request handling. These wrappers are the most important performance tuning step for any NLog configuration handling more than a few hundred events per second. The headline numbers for a typical production file target:
- Synchronous file write: ~0.5–2 ms per call (I/O bound)
AsyncWrapperenqueue: ~50–200 ns per call (memory bound)- Throughput ceiling with
AsyncWrapper: limited by the background thread's write speed, not the caller
Serilog Performance
Serilog's WriteTo.Async(...) wrapper provides equivalent async buffering:
.WriteTo.Async(a => a.File("logs/app.log"))
The async wrapper in Serilog uses a similar background queue pattern. Default queue size is 10,000 events; customize with bufferSize and blockWhenFull parameters.
For teams running BenchmarkDotNet comparisons between the two: the per-call overhead is in the same ballpark (~100–200 ns for async enqueue), and neither library is typically the bottleneck in a well-configured production system. The bottleneck is almost always the underlying I/O (disk writes, database inserts, HTTP calls). For BenchmarkDotNet usage, see How to Use BenchmarkDotNet.
Ecosystem and Sinks vs Targets
Both libraries have extensive output destinations for files, consoles, databases, and structured log servers. The practical difference is in the long tail -- Serilog's ecosystem includes substantially more community-maintained sinks for cloud platforms and observability tools.
Serilog Sinks (200+)
Serilog has over 200 community-maintained sinks. Coverage is broad and generally up to date:
- Storage:
Serilog.Sinks.File,Serilog.Sinks.MSSqlServer,Serilog.Sinks.MongoDB,Serilog.Sinks.AzureBlobStorage - Observability:
Serilog.Sinks.Seq,Serilog.Sinks.Elasticsearch,Serilog.Sinks.Datadog - Cloud:
Serilog.Sinks.AzureEventHub,Serilog.Sinks.ApplicationInsights,Serilog.Sinks.AWSCloudWatch - Messaging:
Serilog.Sinks.Kafka,Serilog.Sinks.RabbitMQ
For a detailed breakdown of the most useful Serilog sinks, Serilog Sinks: Console, File, Seq, and More covers the essentials.
NLog Targets (70+)
NLog has around 70+ official and community targets. The essentials are all covered:
NLog.Targets.File,NLog.Targets.ColoredConsole,NLog.Targets.DatabaseNLog.Targets.Seq(Seq structured logging)NLog.Web.AspNetCorefor request-scoped renderersNLog.Targets.ElasticSearchfor log aggregationNLog.Targets.Mail,NLog.Targets.Slack
For most production needs -- file, console, Seq, Elasticsearch, database -- NLog has solid coverage. If you need a niche cloud service (Azure Event Hub, AWS CloudWatch, Datadog), Serilog is more likely to have a maintained community sink.
ILogger Integration: The Equalizer
The practical gap between NLog and Serilog narrows significantly when both are used through Microsoft.Extensions.Logging.ILogger<T>. Both libraries register as an ILoggerProvider and receive the same log events from the MEL abstraction layer:
// NLog integration
builder.Logging.AddNLog();
// Serilog integration
builder.Host.UseSerilog();
Once either library is wired up as the MEL provider, your application code uses ILogger<T> and never imports NLog or Serilog namespaces directly. Switching between them requires only a one-line change to the Program.cs setup -- not changes to any service or controller.
The implication: if you're on ILogger<T>, the structured logging differences (like Serilog's @ destructuring) are only accessible via the library's native API. Over ILogger<T>, both libraries receive the same structured properties from message templates. For a deep comparison of ILogger<T> vs. Serilog's native API, see Serilog vs Microsoft.Extensions.Logging: Which Should You Use?.
Migration Between NLog and Serilog
Because both libraries integrate through ILogger<T>, migration is lower effort than most teams expect:
From NLog to Serilog:
- Remove
NLog.Web.AspNetCorepackage reference, addSerilog.AspNetCore - Replace
builder.Logging.AddNLog()withbuilder.Host.UseSerilog() - Rewrite configuration (NLog XML rules → Serilog C# fluent API or
appsettings.json) - Migrate custom targets to Serilog custom sinks (same interface, different base class)
- Verify layout renderers → Serilog output templates produce equivalent output
The configuration rewrite is the bulk of the work. Application code (ILogger<T> calls) does not need to change at all.
From Serilog to NLog: The same process in reverse. Translate fluent sink configuration to NLog targets and rules, and replace enrichers with MDLC setup or NLog's built-in renderers.
Most migrations complete in a day for small applications. For large applications with custom sinks/targets or complex routing, budget a sprint.
When to Choose NLog
NLog is the better choice when your team's priorities align with its design philosophy. The following scenarios reflect where NLog's specific strengths deliver real value over Serilog's defaults:
- Your operations team (not developers) manages logging configuration and needs XML/JSON config that can change without code deployment
- You need complex log routing -- different namespaces to different targets based on level ranges, name patterns, and conditions
- You need the most complete Native AOT support (NLog 6 is further along than Serilog on AOT)
- You're extending an existing NLog-based codebase and consistency matters more than the switching cost
- You need hot-reload config without application restart (
autoReload="true")
When to Choose Serilog
Serilog is the better default for most modern .NET applications, particularly when the development team owns the full stack and values a rich structured logging experience with minimal boilerplate:
- Your team prefers C# fluent configuration with IntelliSense and compile-time validation
- You make heavy use of object destructuring with the
@operator and need rich structured data in your log viewer - You need a specific sink from Serilog's larger ecosystem (particularly newer cloud platforms)
- You're building a new greenfield .NET application and want maximum community support and documentation
- Your team is already deeply invested in Serilog enrichers (from Serilog Enrichers: Adding Context to Every Log Entry) and their MEL integration patterns
The Honest Answer
For most new .NET projects in 2026, Serilog has a slight edge due to its larger ecosystem, better structured logging defaults, and gentler learning curve for teams comfortable with C# fluent APIs. The community documentation is extensive, and the most common use cases are covered by highly maintained sinks.
NLog earns its place in enterprise environments where operations teams own log configuration, where complex routing rules are a real requirement, and in AOT-sensitive scenarios. It is not the inferior library -- it makes different trade-offs that are genuinely better fits for certain teams and architectures.
The practical answer for teams evaluating their options: if you haven't started yet, try Serilog first. If you find its routing too limited or if your ops team needs XML config, NLog is an excellent alternative with near-zero migration cost via ILogger<T>.
Frequently Asked Questions
Is NLog or Serilog faster?
Both are fast when properly configured with async wrappers. The difference in per-call overhead is negligible (~50–200 ns) -- the real performance determinant is whether you've wrapped targets in async wrappers and configured file targets with keepFileOpen="true". Neither library is typically the performance bottleneck in a production ASP.NET Core application.
Can I use NLog and Serilog in the same application?
Technically yes -- both register as MEL providers via ILoggerProvider. However, running two logging frameworks simultaneously means all log events flow through both pipelines, doubling the overhead. This is not recommended for production. Use one framework as the MEL provider.
Does NLog support structured logging like Serilog?
Yes. NLog supports structured logging via message templates (the same {PropertyName} syntax), JsonLayout for JSON output, and ${all-event-properties} to capture all named properties. The difference is in object destructuring -- Serilog's native @ operator serializes entire object graphs automatically, while NLog requires explicit configuration to achieve equivalent structured output.
Is NLog still actively maintained?
Yes. NLog 6.0 (released 2024) brought Native AOT support, improved JSON configuration, and .NET 8 optimizations. The core maintainers are active and release updates regularly. NLog is not a legacy library -- it's a mature library with continued investment.
Should I migrate from NLog to Serilog?
If your current NLog setup works well, migrating for its own sake is not worth the effort. Both libraries provide equivalent quality through ILogger<T>. Migrate only if you have a specific need that Serilog meets better -- such as a sink that only exists for Serilog, or a structured logging requirement that NLog's configuration cannot meet cleanly.
Which logging library is better for ASP.NET Core?
Both have first-class ASP.NET Core support. Serilog via Serilog.AspNetCore (the UseSerilog() host extension) and NLog via NLog.Web.AspNetCore. For ASP.NET Core specifically, Serilog's request logging middleware (app.UseSerilogRequestLogging()) is a popular feature that replaces verbose built-in request logs with a single structured summary line per request -- NLog does not have a direct equivalent, though you can achieve similar results with rules and filters.
Wrapping Up
NLog and Serilog are both excellent logging libraries for .NET -- the right choice depends on your team's preferences and your application's routing requirements.
The key differences to remember:
- Configuration style: NLog = XML/JSON config files (ops-friendly). Serilog = C# fluent API (dev-friendly).
- Routing power: NLog's rules engine handles complex per-namespace, per-level routing without code. Serilog's level overrides cover most real-world cases but are less flexible for complex fan-out routing.
- Structured logging: Serilog's
@destructuring and nativeILoggerare richer. Both work fine throughMicrosoft.Extensions.Logging. - Ecosystem: Serilog has more sinks. NLog has everything you need for common destinations.
- Migration cost: Near zero -- both use
ILogger<T>as the application interface.
For the complete picture of logging in .NET across all libraries and patterns, the Logging in .NET: The Complete Developer's Guide is the authoritative overview.

