How to use NLog in ASP.NET

When you want to use NLog in ASP.NET, the first step is to install and configure NLog. Then you can either use it directly or fully integrate it and ASP.NET.

Use NLog directly if you prefer to have static ILogger properties, instead of using dependency injection. The downside of this approach is that you’ll have NLog-specific code all over the place. Realistically, that’s only a problem if you think you’re ever going to change logging providers in your project. If you consider that problem, or if you simply want to use the built-in Microsoft.Extensions.Logging.ILogger, then go with the full integration option.

In this article, I’ll show how to install and configure NLog, and then I’ll show both integration options – directly using NLog or full integration.

Install and configure NLog

  • Install the NLog.Web.AspNetCore package. Note: This is using Package Console Manager (View > Other Windows > Package Console Manager).
Install-Package NLog.Web.AspNetCore
Code language: PowerShell (powershell)
  • Add nlog.config to your project. Configure it however you want. Note: This example configures it to log all levels of messages to a log file using a simple layout and archives logs by file size.
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">


  <targets>
    <target xsi:type="File"
            name="mainLog"
            fileName="C:\logs\recipeapi-${shortdate}.log"
            layout="${longdate} level=${level} source=${callsite} message=${message}"
            keepFileOpen ="false"
            concurrentWrites ="true"
            archiveNumbering="DateAndSequence"
            archiveAboveSize="1000000"
            maxArchiveFiles="10"/>
  </targets>

  <rules>
    <logger name="*" minlevel="Trace" writeTo="mainLog" />
  </rules>

</nlog>
Code language: HTML, XML (xml)
  • Set nlog.config’s property Copy To Output Directory = Copy If Newer.

Your .csproj file should look like this:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NLog.Web.AspNetCore" Version="4.13.0" />
  </ItemGroup>

  <ItemGroup>
    <Content Update="nlog.config">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </Content>
  </ItemGroup>


</Project>

Code language: HTML, XML (xml)

Option 1 – Use NLog directly

You don’t need to fully integrate with ASP.NET to use NLog. This is the simpler option.

In every controller where you want a logger, add a static NLog.ILogger and initialize it to LogManager.GetCurrentClassLogger(). The reason to make it static is because controller objects are built every time a new request comes in, and GetCurrentClassLogger() is slow.

You can make it a private field, like this:

private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
Code language: C# (cs)

If you want to be able to swap in a mock logger when you unit test a controller, then make it a public (or internal) property instead, like this:

public static ILogger Logger { get; set; } = LogManager.GetCurrentClassLogger();
Code language: C# (cs)

Here’s an example of a controller with a public static ILogger property:

using NLog;

[ApiController]
[Route("[controller]")]
public class RecipesController : ControllerBase
{
	public static ILogger Logger { get; set; } = LogManager.GetCurrentClassLogger();

	[HttpGet("{id}")]
	public Recipe Get(int id)
	{
		Logger.Debug($"GET /recipes/id called with id={id}");

		return GetRecipeFromRepo(id); 
	}
}
Code language: C# (cs)

When a request comes in, this endpoint logs the following to C:\logs\recipeapi-2021-07-30.log:

2021-07-30 16:14:44.5658 level=Debug source=RecipesApi.Controllers.RecipesController.Get message=GET /recipes/id called with id=1Code language: plaintext (plaintext)

Note: Consider using the built-in logging middleware for logging requests/responses.

Option 2 – Fully integrate NLog with ASP.NET

Fully integrating results in the following:

  • You can use Microsoft.Extensions.Logging.ILogger instead of NLog.ILogger.
  • You can constructor inject ILogger’s into your controllers.
  • You have to set the logging level in appsettings.json (or appsettings.development.json).
  • It’ll log the hosting lifetime messages using NLog.

The main benefit is that it’s a generic approach and minimizes NLog-specific code. It allows you to swap in a different logging provider (ex: Serilog) with minimal changes.

Step 1 – Call UseNLog()

To fully integrate, in the Program class where you’re calling .ConfigureWebHostDefaults(), call webBuilder.ConfigureLogging() and UseNLog(), like this:

using NLog.Web;

public class Program
{
	public static void Main(string[] args)
	{
		CreateHostBuilder(args).Build().Run();
	}

	public static IHostBuilder CreateHostBuilder(string[] args) =>
		Host.CreateDefaultBuilder(args)
			.ConfigureWebHostDefaults(webBuilder =>
			{
				webBuilder.UseStartup<Startup>();
				webBuilder.ConfigureLogging(loggerBuilder => 
				{ 
					//configure how you want
				})
				.UseNLog();
				
			});
}
Code language: C# (cs)

What does UseNLog() do?

UseNLog() mainly swaps in NLogLoggerFactory, which the framework will use when it needs to dependency inject ILogger objects into the controllers. Here’s the relevant line in the NLog source code:

services.Replace(ServiceDescriptor.Singleton<ILoggerFactory, NLogLoggerFactory>(serviceProvider => new NLogLoggerFactory(sharedFactory(serviceProvider, configuration, options))));
Code language: C# (cs)

Ref: NLog.Web AddNLogLoggerProvider() method source code

Step 2 – Constructor inject ILogger into your controllers

In each controller where you want a logger, add the appropriate Microsoft.Extensions.Logging.ILogger<T> parameter to the constructor, like this:

using Microsoft.Extensions.Logging;

[ApiController]
[Route("[controller]")]
public class RecipesController : ControllerBase
{
	private readonly ILogger<RecipesController> Logger;
	public RecipesController(ILogger<RecipesController> logger)
	{
		Logger = logger;
	}

	[HttpGet("{id}")]
	public Recipe Get(int id)
	{
		Logger.LogDebug($"GET /recipes/id called with id={id}");

		return GetRecipeFromRepo(id); 
	}
}
Code language: C# (cs)

Step 3 – Set the logging level in appsettings.json

In appsettings.json (or appsettings.Development.json), set the default log level as desired:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  }
}

Code language: JSON / JSON with Comments (json)

To see this logging, send a request (such as with Postman). It will log the following to C:\logs\recipeapi-2021-07-31.log:

2021-07-31 08:45:38.4599 level=Info source=Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted message=Application started. Press Ctrl+C to shut down.
2021-07-31 08:45:38.5089 level=Info source=Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted message=Hosting environment: Development
2021-07-31 08:45:38.5089 level=Info source=Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted message=Content root path: C:\Projects\RecipesApi
2021-07-31 08:45:38.5510 level=Debug source=RecipesApi.Controllers.RecipesController.Get message=GET /recipes/id called with id=1
Code language: plaintext (plaintext)

Notice that it’s logging the hosting lifetime messages (ex: “Application started”). You can disable these startup logging messages if you want. The logging level for hosting lifetime messages is controlled by the Logging.LogLevel.Microsoft.Hosting.Lifetime property in appsettings.json (or appsettings.Development.json).

Note: UseNLog() wires up the routing of hosting lifetime log messages to NLog.

Leave a Comment