BrandGhost
Serilog vs Microsoft.Extensions.Logging: Which Should You Use?

Serilog vs Microsoft.Extensions.Logging: Which Should You Use?

Serilog vs Microsoft.Extensions.Logging: Which Should You Use?

The Serilog vs Microsoft.Extensions.Logging question is one of the most common points of confusion for .NET developers getting serious about logging -- and it's mostly a category error. MEL (Microsoft.Extensions.Logging) is a logging abstraction. Serilog is a logging implementation. In most production applications you'll use both, not one or the other.

Understanding what each one is, what it provides, and where the line is makes this an easy decision. This guide covers the actual differences, the architecture behind them, and the concrete scenarios where Serilog's sink ecosystem and structured logging pipeline deliver value beyond what MEL's built-in providers offer.

If you're new to .NET logging overall, start with the complete .NET logging guide for foundational context.

Watch this video on Dev Leader

What Is Microsoft.Extensions.Logging?

Microsoft.Extensions.Logging (MEL) is the standard .NET logging abstraction, shipped as part of the runtime and available in every .NET 6+ project template. It provides:

  • The ILogger<T> and ILogger interfaces your application code calls
  • ILoggerProvider and ILoggerFactory infrastructure for routing log events to providers
  • Log levels, categories, and scopes via BeginScope()
  • The [LoggerMessage] source generator for zero-allocation hot-path logging
  • Built-in providers: Console, Debug, EventSource, EventLog (Windows)
  • Configuration via appsettings.json using "Logging" section

MEL is the standard interface the entire .NET ecosystem -- ASP.NET Core, Entity Framework Core, HttpClientFactory, and every well-behaved library -- uses internally. When you configure MEL, you're configuring what happens to ALL logging in your application.

MEL Architecture

Application code → ILogger<T>
                        ↓
                 ILoggerFactory
                        ↓
               [ILoggerProvider1] → Console
               [ILoggerProvider2] → Debug
               [ILoggerProvider3] → Serilog ← (Serilog.Extensions.Logging)

MEL routes log events to one or more registered providers simultaneously. Providers are the actual sinks -- they decide where logs go. The built-in providers ship with .NET; third-party libraries like Serilog, NLog, and log4net can register themselves as providers.


What Is Serilog?

Serilog is a third-party structured logging library for .NET. Its primary design goal is structured logging: preserving log event data as rich objects rather than flattening everything to strings.

Serilog provides:

  • A native ILogger interface (Serilog.ILogger) separate from MEL's
  • A destructuring pipeline for complex objects (@notation in templates)
  • A large ecosystem of sinks (Seq, Elasticsearch, Application Insights, Azure, SQL Server, etc.)
  • Enrichers for adding context automatically
  • Sub-loggers, log event filters, and minimum level overrides
  • ASP.NET Core integration via Serilog.AspNetCore (request logging middleware)
  • A Serilog.Extensions.Logging bridge that registers Serilog as an MEL provider

Serilog as an MEL Provider

builder.Host.UseSerilog((ctx, services, config) =>
    config
        .ReadFrom.Configuration(ctx.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
        .WriteTo.Console()
        .WriteTo.Seq("http://localhost:5341"));

When you call UseSerilog(), it replaces all other MEL providers and routes every ILogger<T> call through Serilog's pipeline. Your application code never changes -- it still calls _logger.LogInformation(...). Serilog just becomes the engine that processes and routes those events.


The Core Difference: Abstraction vs Implementation

Concern MEL Serilog
Interface your code calls ILogger<T> ❌ (use MEL interface)
Structured logging pipeline ❌ Limited ✅ Full pipeline
Sink ecosystem ❌ 4 built-in 100+ community sinks
Enrichers ❌ Scopes only ✅ Full enricher API
Sub-loggers and filtering
Request logging middleware UseSerilogRequestLogging()
Source-generated logging [LoggerMessage] ✅ (works through MEL bridge)
Configuration in appsettings ✅ via Serilog.Settings.Configuration
NuGet required No (built-in) Yes

The correct mental model: MEL defines the interface; Serilog is the engine. They're complementary, not competing.


When MEL Built-in Providers Are Enough

For some scenarios, MEL's built-in providers are entirely sufficient:

Small internal tools and utilities: If you're building a console app or background service that logs to the console for developer feedback only, the built-in Console provider is fine.

Simple Azure-hosted applications: Azure App Service captures ILogger<T> console output automatically and pipes it to Application Insights if the Microsoft.ApplicationInsights package is installed. No Serilog required.

Local development: During development, the Console and Debug providers give you all the feedback you need.

Azure Functions with Application Insights: Azure Functions has tight built-in integration with Application Insights via MEL -- adding Serilog here adds complexity without much benefit.

The key question isn't "should I use Serilog" -- it's "do I need capabilities beyond what the built-in providers give me?"


When Serilog Adds Real Value

1. Seq for Development and Self-Hosted Logging

Seq is the killer app for Serilog. It's a structured log server you can run locally with Docker:

docker run -d --name seq -p 5341:5341 -p 8081:80 -e ACCEPT_EULA=Y datalust/seq
.WriteTo.Seq("http://localhost:5341")

Seq's query interface lets you write queries like:

select OrderId, Amount, ElapsedMs from stream
where @Level = 'Warning' and ElapsedMs > 500

This is only possible when logs are structured data -- strings can't be queried. Seq + Serilog is the fastest way to get structured observability into any .NET application.

2. Multiple Sinks with Different Level Filters

.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information)
.WriteTo.File("logs/errors.txt", restrictedToMinimumLevel: LogEventLevel.Error)
.WriteTo.Seq("http://localhost:5341", restrictedToMinimumLevel: LogEventLevel.Debug)

MEL can route to multiple providers, but per-provider minimum level configuration in appsettings is awkward. Serilog's sink-level filtering is clean and flexible.

3. Elasticsearch, OpenSearch, and Observability Backends

For applications that ship logs to ELK, OpenSearch, Datadog, or Grafana Loki, Serilog has well-maintained sinks with proper JSON output. MEL's built-in providers don't have these.

dotnet add package Serilog.Sinks.Elasticsearch
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
    AutoRegisterTemplate = true,
    IndexFormat = "dotnet-logs-{0:yyyy.MM.dd}"
})

4. Rich Request Logging

UseSerilogRequestLogging() replaces ASP.NET Core's verbose per-event request logging (start, stop, headers, status) with a single structured event per request that includes duration, status code, path, and method:

[13:15:22 INF] HTTP GET /api/orders 200 45ms

Plus MachineName, RequestId, SourceContext, and any properties you add via IDiagnosticContext. MEL's built-in providers don't have an equivalent.

5. Destructuring Complex Objects

_logger.LogInformation("Created order {@Order}", order);

With @notation, Serilog destructures the Order object into structured fields -- preserving order.Id, order.CustomerId, order.Items.Count, etc. as queryable fields in Seq or Elasticsearch. MEL with a Console provider would just call ToString().


Migrating from MEL Built-in to Serilog

Migration is incremental. Your application code changes nothing:

Step 1: Add packages

dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Settings.Configuration
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

Step 2: Replace builder.Logging with builder.Host.UseSerilog()

// Remove this (or leave it -- UseSerilog will override)
// builder.Logging.AddConsole();

// Add this
builder.Host.UseSerilog((ctx, services, config) =>
    config
        .ReadFrom.Configuration(ctx.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
        .WriteTo.Console(new ExpressionTemplate(
            "[{@t:HH:mm:ss} {@l:u3}] {#if SourceContext is not null}{Substring(SourceContext, LastIndexOf(SourceContext, '.') + 1)}: {#end}{@m}
{@x}"))
        .WriteTo.File("logs/app.log", rollingInterval: RollingInterval.Day));

Step 3: Add bootstrap logger for startup

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .CreateBootstrapLogger();

try
{
    // builder setup...
    builder.Host.UseSerilog(...);
    var app = builder.Build();
    await app.RunAsync();
}
catch (Exception ex)
{
    Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
    await Log.CloseAndFlushAsync();
}

Step 4: Move logging config to Serilog section in appsettings

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning",
        "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
      }
    }
  }
}

Remove the existing "Logging" section -- it's no longer relevant once Serilog is the provider.


Source-Generated Logging with Serilog

[LoggerMessage] source generation works through the MEL bridge -- when you call a source-generated method via ILogger<T>, it flows through to Serilog:

public static partial class AppLoggers
{
    [LoggerMessage(EventId = 100, Level = LogLevel.Information, Message = "Order {OrderId} processed in {ElapsedMs}ms")]
    public static partial void OrderProcessed(this ILogger logger, string orderId, long elapsedMs);
}
_logger.OrderProcessed(order.Id, stopwatch.ElapsedMilliseconds);

The event flows through Serilog's pipeline: enrichers add machine name, correlation ID, and user context; the event is routed to Seq, Elasticsearch, or any configured sink. You get zero-allocation logging AND Serilog's full pipeline.


Performance: MEL vs Serilog

For high-throughput scenarios, both approaches perform well when configured correctly:

Approach Allocation Notes
Source-generated [LoggerMessage] via MEL Zero-alloc Fastest option -- works with Serilog
Serilog.ILogger with message templates Low Parses template once
ILogger.LogInformation() + Serilog Low Small overhead from MEL bridge
String interpolation $"..." High Allocates even when filtered
ILogger.LogInformation() without check Medium Evaluates args even when filtered

The Proxy pattern is evident here: the MEL bridge proxies ILogger<T> calls to Serilog, adding minimal overhead. For most applications the overhead is negligible -- measured in nanoseconds. For hot paths, use [LoggerMessage] source generation -- it works regardless of whether your provider is Serilog or MEL built-in.


MEL Minimum Level Filtering with Serilog

One gotcha: MEL has its own minimum level filtering that runs before Serilog's pipeline. If MEL filters out an event, Serilog never sees it.

Ensure MEL passes everything to Serilog by setting the MEL minimum to Trace:

{
  "Logging": {
    "LogLevel": {
      "Default": "Trace"  // Let Serilog control filtering
    }
  },
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning"
      }
    }
  }
}

Or clear all MEL providers and let Serilog handle everything:

builder.Logging.ClearProviders(); // Remove Console, Debug, etc.
builder.Host.UseSerilog(...);     // Serilog is the only provider

Frequently Asked Questions

Should I use Serilog or Microsoft.Extensions.Logging?

Use both. Write your application code against ILogger<T> from MEL -- this keeps your code portable and decoupled from a specific logging library. Then configure Serilog as the MEL provider to get structured logging, enrichers, and the full sink ecosystem. The two are complementary, not competing.

Does Serilog replace ILogger?

No. When using Serilog.AspNetCore, your code still uses ILogger<T> from Microsoft.Extensions.Logging. Serilog registers itself as an MEL provider via UseSerilog() and processes all the events. You don't need to change your injection points or log call sites.

Why does Serilog have its own ILogger interface?

Serilog existed before Microsoft.Extensions.Logging was standardized. Serilog.ILogger supports Serilog-specific features like contextual loggers (ForContext<T>()) and is used in the Serilog configuration API. In application code, always prefer Microsoft.Extensions.Logging.ILogger<T> for dependency injection and portability.

Is MEL logging structured?

Partially. MEL supports message templates with named parameters ("Order {OrderId} placed") which produce structured output when the provider supports it. But MEL's built-in Console and Debug providers don't render the structured properties in a queryable format. Serilog and its sinks do.

What is the performance difference between Serilog and MEL built-in?

For most applications, negligible. Serilog adds a small overhead for the MEL bridge call and property creation, but this is measured in nanoseconds. Source-generated [LoggerMessage] methods eliminate allocations entirely and work identically with MEL built-in or Serilog as the provider. Profile before optimizing.

Can I use Serilog with the [LoggerMessage] source generator?

Yes. Source-generated methods work through the MEL ILogger<T> interface, which routes to Serilog when UseSerilog() is configured. You get zero-allocation logging AND Serilog's structured pipeline, enrichers, and sinks.

When should I use NLog instead of Serilog?

NLog is another strong .NET logging library with different strengths: more flexible conditional logging, richer built-in targets, and some infrastructure teams prefer its XML/NLog.config configuration style. The choice between Serilog and NLog is often team preference. Both integrate with MEL as providers. Serilog has a larger community ecosystem in the .NET OSS space and is more commonly referenced in modern .NET documentation.

Serilog in .NET: Complete Guide to Structured Logging

Learn Serilog in .NET from scratch. Setup, sinks, enrichers, appsettings configuration, and best practices for ASP.NET Core with .NET 9 and .NET 10.

How to Set Up Serilog in ASP.NET Core: Step-by-Step Guide

Learn how to set up Serilog in ASP.NET Core with two-stage initialization, appsettings.json configuration, and structured request logging. .NET 9 and .NET 10.

Logging in .NET: The Complete Developer's Guide

Master logging in .NET with ILogger, structured logging, log levels, Serilog, and OpenTelemetry. Complete guide for .NET 9 and .NET 10 developers.

An error has occurred. This application may no longer respond until reloaded. Reload