C# NLog
last modified October 17, 2026
In this article we show how to do logging in C# with the NLog library.
NLog is a free logging library for .NET. It is easy to use,
performant and extensible. It can be configured in code or with an XML
configuration file, and it can also be used as a provider for the
Microsoft.Extensions.Logging API.
$ dotnet add package NLog
We add the package to the project. The examples in this article use NLog 6.
Logging
Logging is the process of writing information into log targets. The logged data include information about various events that happened in the operating system, software, or in communication.
The reasons for logging information are:
- information gathering
- troubleshooting
- generating statistics
- auditing
- profiling
In addition to identifying errors in software development, logging is also used in detecting security incidents, monitoring policy violations, providing information in case of problems, finding application bottlenecks, or generating usage data.
Logging levels
Logging levels indicate message priority. From the least to the most severe, NLog has these levels:
- Trace - very detailed and noisy, the most verbose level
- Debug - less detailed, for internal events of interest
- Info - informational messages
- Warn - warnings about validation issues or temporary failures that can be recovered; may indicate future problems
- Error - more serious error messages indicating loss of data or functionality
- Fatal - most critical messages where applications often terminate
In addition, the Off level turns logging off.
Targets
Logging targets are destinations where logging information can be stored.
NLog supports many targets including:
- files
- consoles
- email addresses
- databases
- message queues
- remote machines
- remote services
- memory
Layouts
The layout defines what kind of information in what format is being logged.
Layouts consist of layout renderers such as ${longdate}
or ${logger}.
${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}
This is the default layout. It contains the date and time, the log level, the name of the logger, and the message with the exception details, if any.
NLog console
In the first example, we write messages to the console.
using NLog;
LogManager.Setup().LoadConfiguration(builder =>
{
builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole();
});
Logger logger = LogManager.GetLogger("simple");
logger.Debug("debug message");
logger.Info("info message");
logger.Error("error message");
The example logs messages to the console.
LogManager.Setup().LoadConfiguration(builder =>
{
builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole();
});
We configure NLog with its fluent API. The ForLogger method
without arguments creates a rule for all loggers. The rule accepts messages
with the Info level and above and writes them to a console
target.
Logger logger = LogManager.GetLogger("simple");
We create a named logger.
logger.Debug("debug message");
logger.Info("info message");
logger.Error("error message");
We log three messages with various log levels.
$ dotnet run 2026-10-17 18:56:38.4161|INFO|simple|info message 2026-10-17 18:56:38.4524|ERROR|simple|error message
Since the Debug level is below the Info level, the
debug message is not logged.
NLog layout example
In the next example, we choose our custom log layout.
using NLog;
var layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} ${level:uppercase=true:padding=-5} ${logger} ${message}";
LogManager.Setup().LoadConfiguration(builder =>
{
builder.ForLogger().FilterMinLevel(LogLevel.Debug)
.WriteToColoredConsole(layout: layout);
});
Logger logger = LogManager.GetLogger("simple");
logger.Debug("debug message");
logger.Info("info message");
logger.Error("error message");
We define a coloured console target with a custom layout.
var layout = "${date:format=yyyy-MM-dd HH\\:mm\\:ss} ${level:uppercase=true:padding=-5} ${logger} ${message}";
In the layout, we include a custom formatted date, the log level, the name of
the logger, and the message. The colons in the date format must be escaped,
because the colon separates the options of a layout renderer. Note that
MM stands for months and mm for minutes. The
padding=-5 option pads the level name to five characters, aligned
to the left.
builder.ForLogger().FilterMinLevel(LogLevel.Debug)
.WriteToColoredConsole(layout: layout);
Since the minimum level is set to Debug, all three messages are
logged. The coloured console target uses different colours for different log
levels.
$ dotnet run 2026-10-17 18:56:40 DEBUG simple debug message 2026-10-17 18:56:40 INFO simple info message 2026-10-17 18:56:40 ERROR simple error message
NLog file target
With the file target, we write our log messages into a file.
using NLog;
LogManager.Setup().LoadConfiguration(builder =>
{
builder.ForLogger().FilterMinLevel(LogLevel.Info)
.WriteToFile(fileName: "${basedir}/logs/${shortdate}.log");
});
Logger logger = LogManager.GetCurrentClassLogger();
logger.Info("info message");
logger.Error("error message");
LogManager.Shutdown();
The example writes two messages into a file.
.WriteToFile(fileName: "${basedir}/logs/${shortdate}.log");
The file name can contain layout renderers. The ${basedir} is the
directory of the application and the ${shortdate} is the current
date. This gives us a new log file each day. The directory is created
automatically.
Logger logger = LogManager.GetCurrentClassLogger();
The GetCurrentClassLogger method creates a logger named after the
current class. This is the most common way of creating loggers.
LogManager.Shutdown();
The Shutdown method flushes the pending messages and closes the
targets. It should be called before the application exits.
$ dotnet run $ cat bin/Debug/net10.0/logs/2026-10-17.log 2026-10-17 18:56:42.3315|INFO|LogDemo.Program|info message 2026-10-17 18:56:42.3432|ERROR|LogDemo.Program|error message
The project is called LogDemo; the class created for top-level
statements is LogDemo.Program.
NLog structured logging
NLog supports message templates. Instead of building the message with string interpolation, we pass the values as arguments. The values are captured as named properties, which structured targets such as JSON files or log servers can store separately.
using NLog;
LogManager.Setup().LoadConfiguration(builder =>
{
builder.ForLogger().FilterMinLevel(LogLevel.Info).WriteToConsole();
});
Logger logger = LogManager.GetCurrentClassLogger();
var user = "John Doe";
int attempts = 3;
logger.Info("User {User} logged in after {Attempts} attempts", user, attempts);
try
{
int[] vals = [1, 2, 3];
Console.WriteLine(vals[3]);
}
catch (IndexOutOfRangeException e)
{
logger.Error(e, "Failed to read value at index {Index}", 3);
}
LogManager.Shutdown();
We log a message with two properties and an exception.
logger.Info("User {User} logged in after {Attempts} attempts", user, attempts);
The {User} and {Attempts} are named placeholders. The
arguments are matched to them by position. Another advantage over string
interpolation is that the message is not formatted at all if the level is
disabled.
logger.Error(e, "Failed to read value at index {Index}", 3);
The exception is passed as the first argument. The default layout renders it after the message.
$ dotnet run 2026-10-17 18:56:44.3155|INFO|LogDemo.Program|User John Doe logged in after 3 attempts 2026-10-17 18:56:44.3433|ERROR|LogDemo.Program|Failed to read value at index 3|System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.<Main>$(String[] args) in /home/jano/LogDemo/Program.cs:line 18
NLog rules
The logging rules control which messages are written to which targets. A rule matches messages by the logger name and the log level.
using NLog;
using NLog.Config;
using NLog.Targets;
var config = new LoggingConfiguration();
var fileTarget = new FileTarget("file")
{
FileName = "${basedir}/errors.log"
};
var consoleTarget = new ConsoleTarget("console");
config.AddRule(LogLevel.Error, LogLevel.Fatal, fileTarget, "Payments.*");
config.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget, "*");
LogManager.Configuration = config;
Logger logger = LogManager.GetLogger("App");
logger.Info("info message");
logger.Error("error message");
Logger logger2 = LogManager.GetLogger("Payments.Card");
logger2.Info("info message 2");
logger2.Error("error message 2");
LogManager.Shutdown();
We have two targets: a file target and a console target. The two rules define
how messages go to the targets. This time we build the configuration with the
LoggingConfiguration class.
config.AddRule(LogLevel.Error, LogLevel.Fatal, fileTarget, "Payments.*");
The first rule sends error and fatal messages from loggers whose names start
with Payments. to the file target.
config.AddRule(LogLevel.Info, LogLevel.Fatal, consoleTarget, "*");
The second rule has the * name pattern, so it matches all loggers.
It sends messages with the Info level and above to the console.
$ dotnet run 2026-10-17 18:56:46.3192|INFO|App|info message 2026-10-17 18:56:46.3286|ERROR|App|error message 2026-10-17 18:56:46.3286|INFO|Payments.Card|info message 2 2026-10-17 18:56:46.3286|ERROR|Payments.Card|error message 2 $ cat bin/Debug/net10.0/errors.log 2026-10-17 18:56:46.3286|ERROR|Payments.Card|error message 2
All four messages were logged to the console because they satisfied the second rule. Only one message satisfied the first rule, which was bound to the file target.
NLog configuration file
In larger applications, the configuration is usually placed in the
nlog.config XML file. The logging can then be changed without
recompiling the application. NLog loads the file automatically from the
application directory.
<?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">
<targets>
<target name="console" xsi:type="Console"
layout="${time} ${level:uppercase=true} ${message}" />
<target name="file" xsi:type="File"
fileName="${basedir}/app.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message:withexception=true}" />
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="console" />
<logger name="*" minlevel="Warn" writeTo="file" />
</rules>
</nlog>
We define a console and a file target. Messages with the Debug
level and above go to the console; warnings and errors also go to the file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NLog" Version="6.2.2" />
</ItemGroup>
<ItemGroup>
<None Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
The configuration file must be copied to the output directory.
using NLog;
Logger logger = LogManager.GetCurrentClassLogger();
logger.Trace("trace message");
logger.Debug("debug message");
logger.Info("info message");
logger.Warn("warning message");
logger.Error("error message");
LogManager.Shutdown();
The program contains no configuration code.
$ dotnet run 18:57:02.3450 DEBUG debug message 18:57:02.3506 INFO info message 18:57:02.3506 WARN warning message 18:57:02.3569 ERROR error message $ cat bin/Debug/net10.0/app.log 2026-10-17 18:57:02.3506|WARN|LogDemo.Program|warning message 2026-10-17 18:57:02.3569|ERROR|LogDemo.Program|error message
NLog with Microsoft.Extensions.Logging
Modern .NET applications often log through the ILogger interface
from Microsoft.Extensions.Logging. NLog can serve as its provider.
The application code depends only on the ILogger abstraction,
while NLog writes the messages to its targets.
$ dotnet add package NLog.Extensions.Logging
We add the package. It also brings in the
Microsoft.Extensions.Logging package.
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using var factory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Debug);
builder.AddNLog();
});
ILogger<Program> logger = factory.CreateLogger<Program>();
logger.LogDebug("Application started");
logger.LogInformation("Processing {Count} orders", 12);
logger.LogWarning("Disk space is low: {FreeMb} MB left", 512);
NLog.LogManager.Shutdown();
The AddNLog method registers NLog as a logging provider. NLog
reads its configuration from the same nlog.config file as in the
previous example. In ASP.NET Core or Generic Host applications, NLog is
registered with the logging builder of the host in the same way.
$ dotnet run 18:57:23.7594 DEBUG Application started 18:57:23.7695 INFO Processing 12 orders 18:57:23.7695 WARN Disk space is low: 512 MB left
See the C# ILogger article for more about the logging abstraction.
Source
In this article we have done logging in C# with the NLog library.
Author
List all C# tutorials.