Can Microsoft.Extensions.Logging Log to a File?

Microsoft.Extensions.Logging can log to a file, but not out-of-the-box. Instead, you need to use a third-party framework as a Microsoft.Extensions.Logging (MEL) provider, like Serilog or NLog. The closest you can get using no additional packages is logging to the Windows Event Viewer, which has its own ups and downs.

In this article, we will review two file logging examples using the Serilog and NLog MEL packages, then discuss whether file logging is the right fit for every project.

What is a Microsoft.Extensions.Logging Provider?

In short, a provider acts as the output mechanism for MEL logs. For example, the debug console is the default MEL provider, so template applications will push their logs to the debug console if unchanged. Officially, MEL has four supported providers:

  1. The debug console (“output” console in Visual Studio)
  2. The console (the terminal)
  3. The Event Log (Windows Event Viewer)
  4. The Event Source (dotnet trace)

The first three providers are easy to understand and use, as they are literal log destinations. For example, if I wanted to log to the Windows Event Viewer, I could just clear providers, set the Event Log as the provider, and get logging without much fuss. Let’s take a look at that using the .NET 6 hosting model with the Razor Pages template app:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Logging.ClearProviders(); //Clear current providers
builder.Logging.AddEventLog(); //Set provider as the Event Viewer
var app = builder.Build();

I’ll add a test log on the privacy page (I do this to test various logging setups quickly) using basic ILogger syntax:

//... namespace, etc.
public PrivacyModel(ILogger<PrivacyModel> logger)
    _logger = logger;
    logger.LogError("This is the Privacy Page");
//... rest of file

Then let’s look at the event viewer and see the log:

Screenshot of the Event Viewer with four of the privacy page error logs

In this context, the MEL provider is our direct log destination. But the MEL provider doesn’t need to represent a literal location. Instead, I can select an entire logging framework as a provider. Let’s see what that looks like in the next section.

Logging to a File With Microsoft.Extensions.Logging

You can log to a file using a logging framework like Serilog or NLog as a MEL provider. This allows us to log to MEL directly in the application, but use the logging outputs available to the logging framework, including a file.

Serilog uses the Serilog.Extensions.Logging package from Nuget, and NLog has an NLog.Extensions.Logging equivalent. While you could technically use Log4Net as a provider (a framework popular among .NET Framework developers), I wouldn’t recommend it for reasons listed in another post. So let’s just go over a simple Serilog and NLog example using the privacy page error from the last section.

Using Serilog

Serilog is one of the most popular logging frameworks for .NET Core. Below is a simple setup using Serilog.Extensions.Logging, based on the GitHub documentation:

var builder = WebApplication.CreateBuilder(args);

Log.Logger = new LoggerConfiguration()
    .WriteTo.File("logs/Net6Tester.txt", rollingInterval: RollingInterval.Day)

// Add services to the container.
builder.Services.AddLogging(loggingBuilder =>
          loggingBuilder.AddSerilog(dispose: true));

var app = builder.Build();

Here, I add Serilog, specify WriteTo.File, clear the default MEL providers, then add Serilog as the MEL provider. The result is a log file in my project’s “logs” directory, titled “NET6Tester,” appended with the date. Here’s what the log file looks like:

Screenshot of log file titled NET6Tester20220330.txt, viewing some info and an error log

This log file is not pretty, but it took about ten minutes to set up! Better yet, I didn’t need to make any changes to my application outside of the program.cs file. By acting as the MEL provider, Serilog can push logs written for MEL to any Serilog sink.

I personally like Serilog for logging to a file because of its options for formatting the output and adding supplemental data to your logs. For formatting, their wiki has excellent ways to start, including directions to use the outputTemplate parameter and on writing to JSON. Look at the Serilog Expressions package for more options, allowing even greater control.

Using NLog

Another popular logging framework for .NET is NLog. NLog has specific directions for .NET 6, and I stripped them down to the bare minimum code required in the program.cs file. Basically, I removed some of the logging added directly to this file and only implemented NLog as a provider:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

var app = builder.Build();

Additionally, I made a slight change to the required XML nlog.config file. I used a different file path and I adjusted the file’s properties. Specifically, right-click the config file, select properties, and change the “Copy to Output Directory.” While it’s not mentioned in the NLog start guide, this property was required in my case. Luckily, the change is not difficult.

Screnshot of properties in Visual Studio

At this point, I can run the program and log successfully. Here’s what the log file looks like:

Screenshot of the log file

Setting up NLog looks very similar to setting up Serilog. Neither framework requires writing logs differently, and the logs have similar formatting and data. The only significant difference is that I configured Serilog in the program.cs file itself while I used an XML config file for NLog. Once you want to create more complicated log files though, I prefer Serilog. I like Serilog’s formatting options, and I’m not fond of XML configs. But if you have familiarity with NLog already, it’s easy to set up as a MEL provider and an effective logging framework.

Is Logging to a Text File Always the Best Option?

It’s the most popular option for sure. Log files are the most approachable version of long-term log storage. Put them in a directory, append the file name with a date, and you have logs saved to your local machine, readable with a simple text editor. But I don’t think it is the best option in every development scenario for the following reasons.

  • It does not work in real-time. When troubleshooting trickier problems, I have found live log viewers handy. With file logging, you generally need to wait until the application’s closed/restarted to access the file. So, when I need to see the logs and application side-by-side, traditional text file logging won’t cut it.

  • Log files are annoying to search and organize at larger scales. A directory with a few log files in it is easy to manage. Once you push that number up though, it becomes more challenging to maintain or find what you need. Searching through multiple log files with directory-based tools (maybe a grep) can be inconsistent for finding the exact data you need, and requires you to know the log file format very well. Additionally, if I’m trying to keep storage in check, I will likely need to limit the log data I have on hand to a specific period.

  • Remote log files can be hard to access. Most log files are stored on the local hardware. This can make things tricky when you require logs from a machine other than yours. Either you will need to get remote access or have someone to send you a log file.

  • Elaborate formatting requires elaborate code. The examples I showed involved very little new code for the applications. But if I want better looking log files with comprehensive supplemental data (which I would for any actual application), that requires a more complex configuration.

Log files are irreplaceably convenient for local development and quick logging, but don’t meet the needs for logging production applications. Once a program is in production, I recommend logging to a dedicated log viewer.

I’ll use Loupe Desktop as a quick example. With a simple setup, Loupe will act as a provider for my MEL logs. It looks very similar to the other ones in this article:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddHttpContextAccessor(); //Required Service for Loupe

builder.Host.AddLoupe(builder => builder.AddAspNetCoreDiagnostics() 
    .AddPerformanceCounters()) //add optional agents here

var app = builder.Build();
// of file below...

Next, add some Loupe settings to the appsettings.json file:

  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Information",
      "Microsoft.Hosting.Lifetime": "Information"
  "Loupe": {
    "Publisher": {
      "ProductName": "YourProductName",
      "ApplicationName": "YourApplicationName",
      "ApplicationType": "AspNet",
      "ApplicationVersionNumber": "1.0.1"
    "AllowedHosts": "*"

From here, I can install Loupe Desktop, watch my logging live, and let it format/organize all the log data automatically, including supplemental data such as the class and method:

Screenshot of privacy page log in Loupe desktop, showing supplemental data such as thread name, class, method, etc.

Eventually, when I’m running the application in production, I can use our centralized logging service to make log access easy.

I hope the information here helps you get started logging with your application. If you want to simply log to a text file, I get it. Do that, learn how to format log files well, and be well ahead of those who haven’t put that much effort into their logging in the first place. If you are interested in trying a dedicated log viewer though, I recommend trying Loupe Desktop, as it’s completely free and is easy to get started with as a MEL provider. You can learn more about Loupe Desktop in the link below.

See Loupe Desktop

Rock solid centralized logging

Unlimited applications, unlimited errors, scalable from solo startup to enterprise.