NLog
Overview
NLog is a mature, flexible logging framework for .NET that supports a wide range of log targets (file, database, email, network, cloud services) with powerful routing rules, layout renderers, and filtering. It can be configured entirely via XML or JSON configuration files, allowing operations teams to adjust logging without code changes. NLog integrates with Microsoft.Extensions.Logging through the NLog.Extensions.Logging package, making it usable as a provider in ASP.NET Core and generic host applications.
NLog's key differentiator is its routing architecture: log rules can direct different log levels from different sources to different targets, enabling scenarios like sending errors to email while writing debug logs to file, all from the same configuration.
ASP.NET Core Integration
Use NLog.Web.AspNetCore to integrate NLog as the logging provider for ASP.NET Core.
using Microsoft.AspNetCore.Builder;
using NLog;
using NLog.Web;
// Load NLog configuration early
var logger = LogManager.Setup()
.LoadConfigurationFromAppSettings()
.GetCurrentClassLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Host.UseNLog();
var app = builder.Build();
app.MapGet("/", () => "Hello");
app.Run();
}
catch (Exception ex)
{
logger.Error(ex, "Application stopped due to exception");
throw;
}
finally
{
LogManager.Shutdown();
}
XML Configuration
NLog uses nlog.config for declarative configuration of targets, rules, and layouts.
<?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"
autoReload="true"
throwConfigExceptions="true">
<targets>
<target name="console" xsi:type="ColoredConsole"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}${onexception:inner=${newline}${exception:format=tostring}}" />
<target name="file" xsi:type="AsyncWrapper">
<target xsi:type="File"
fileName="logs/app-${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message}${onexception:inner=${newline}${exception:format=tostring}}"
archiveAboveSize="10485760"
maxArchiveFiles="10" />
</target>
<target name="jsonFile" xsi:type="File"
fileName="logs/app-${shortdate}.json">
<layout xsi:type="JsonLayout">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level:uppercase=true}" />
<attribute name="logger" layout="${logger}" />
<attribute name="message" layout="${message}" />
<attribute name="exception" layout="${exception:format=tostring}" />
<attribute name="properties" encode="false">
<layout xsi:type="JsonLayout" includeEventProperties="true" />
</attribute>
</layout>
</target>
<target name="database" xsi:type="Database"
dbProvider="Microsoft.Data.SqlClient.SqlConnection, Microsoft.Data.SqlClient"
connectionString="${configsetting:name=ConnectionStrings.Logging}">
<commandText>
INSERT INTO Logs (Timestamp, Level, Logger, Message, Exception)
VALUES (@timestamp, @level, @logger, @message, @exception)
</commandText>
<parameter name="@timestamp" layout="${longdate}" />
<parameter name="@level" layout="${level}" />
<parameter name="@logger" layout="${logger}" />
<parameter name="@message" layout="${message}" />
<parameter name="@exception" layout="${exception:format=tostring}" />
</target>
</targets>
<rules>
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="*" minlevel="Info" writeTo="console" />
<logger name="*" minlevel="Debug" writeTo="file" />
<logger name="*" minlevel="Debug" writeTo="jsonFile" />
<logger name="*" minlevel="Error" writeTo="database" />
</rules>
</nlog>
Structured Logging
NLog supports structured logging with named parameters that can be captured as properties in JSON and database targets.
using NLog;
namespace MyApp.Services;
public class OrderService
{
private static readonly Logger Logger =
LogManager.GetCurrentClassLogger();
public void ProcessOrder(string orderId, decimal total, int itemCount)
{
Logger.Info(
"Processing order {OrderId} with {ItemCount} items totaling {Total}",
orderId, itemCount, total);
Logger.WithProperty("OrderId", orderId)
.WithProperty("CustomerId", "C-1234")
.Info("Order enriched with customer context");
}
public void HandleError(string orderId, Exception ex)
{
Logger.Error(ex,
"Order {OrderId} processing failed", orderId);
}
}
Scoped Logging with MappedDiagnosticsLogicalContext
NLog provides MappedDiagnosticsLogicalContext (MDLC) for async-safe scoped properties, similar to ILogger.BeginScope.
using NLog;
namespace MyApp.Services;
public class RequestPipeline
{
private static readonly Logger Logger =
LogManager.GetCurrentClassLogger();
public async Task HandleRequestAsync(
string requestId, string tenantId)
{
using (MappedDiagnosticsLogicalContext.SetScoped(
"RequestId", requestId))
using (MappedDiagnosticsLogicalContext.SetScoped(
"TenantId", tenantId))
{
Logger.Info("Request started");
await ProcessAsync();
Logger.Info("Request completed");
// Both log entries include RequestId and TenantId
}
}
private Task ProcessAsync() => Task.CompletedTask;
}
Use the ${mdlc} layout renderer to include scoped properties:
<target name="console" xsi:type="Console"
layout="${longdate}|${mdlc:item=RequestId}|${message}" />
Programmatic Configuration
Configure NLog entirely in code when XML configuration is not desired.
using NLog;
using NLog.Config;
using NLog.Targets;
var config = new LoggingConfiguration();
var consoleTarget = new ConsoleTarget("console")
{
Layout = "${longdate}|${level:uppercase=true}|${logger}|${message}"
};
var fileTarget = new FileTarget("file")
{
FileName = "logs/app-${shortdate}.log",
ArchiveAboveSize = 10 * 1024 * 1024,
MaxArchiveFiles = 10
};
config.AddTarget(consoleTarget);
config.AddTarget(fileTarget);
config.AddRule(NLog.LogLevel.Info, NLog.LogLevel.Fatal, consoleTarget);
config.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Fatal, fileTarget);
LogManager.Configuration = config;
NLog Target Types
| Target | Use Case | Async Recommended |
|---|---|---|
| File | Local log files with rotation | Yes |
| Console / ColoredConsole | Development output | No |
| Database | SQL Server, PostgreSQL, etc. | Yes |
| Email alerts for critical errors | Yes | |
| Network | Remote syslog or TCP/UDP endpoint | Yes |
| EventLog | Windows Event Log | No |
| NLogViewer | Real-time viewer (Chainsaw/Sentinel) | Yes |
| Null | Discard (for benchmarking) | No |
Best Practices
- •Wrap file and database targets with
AsyncWrapperto prevent I/O latency from blocking application threads; NLog's async wrappers batch and flush writes on a background thread. - •Use
autoReload="true"innlog.configso logging configuration changes take effect without restarting the application, enabling runtime verbosity adjustments. - •Set
final="true"on framework noise rules (e.g.,<logger name="Microsoft.*" maxlevel="Info" final="true" />) to prevent framework logs from reaching expensive targets. - •Use structured logging parameters (
{OrderId}) instead of string concatenation so JSON and database targets can index and query individual properties. - •Configure
archiveAboveSizeandmaxArchiveFileson file targets to prevent unbounded disk growth; set archive size to 10 MB and retain 10-30 archive files. - •Use
MappedDiagnosticsLogicalContext(MDLC) for correlation IDs in async workflows instead of the legacyMappedDiagnosticsContext(MDC), which is not async-safe. - •Call
LogManager.Shutdown()in the application's finally block orIHostApplicationLifetime.ApplicationStoppingto flush all buffered log entries before process exit. - •Use
throwConfigExceptions="true"during development to catch configuration errors (typos in target names, invalid layout renderers) at startup rather than silently losing logs. - •Separate log rules by severity so that
ErrorandCriticallogs reach alerting targets (email, PagerDuty) whileDebuglogs go only to file, reducing alert noise. - •Prefer NLog's
ILoggerintegration viaNLog.Extensions.Loggingover directLogManager.GetCurrentClassLogger()in new projects to maintain compatibility with theMicrosoft.Extensions.Loggingabstraction.