NLog
Mature logging framework valued for simplicity and performance. Provides cleaner code-based configuration than log4net with sensible default settings. Excellent throttling performance under high log generation loads, also provides structured logging features.
Library
NLog
Overview
NLog is a high-performance and flexible logging library for .NET providing rich features while remaining lightweight. It includes 30+ output targets, structured logging support, and advanced configuration capabilities. Performance-focused design excels in throttling scenarios, and complete integration with Microsoft.Extensions.Logging provides optimization for modern .NET development. Valued for lightweight nature and high throughput, it's widely adopted from enterprise-level to startup environments.
Details
NLog 6.0 is positioned as an important choice for .NET development scenarios prioritizing performance and configuration flexibility in 2025. With over 20 years of development experience, it combines stability and reliability while supporting a wide range of platforms from .NET Framework 2.0 to .NET 8. Rich targets (File, Database, Console, Mail, EventLog, etc.), conditional log processing, asynchronous logging, and structured log output handle everything from small-scale applications to large enterprise systems. Deep integration with ASP.NET Core provides optimization for modern web application development.
Key Features
- 30+ Rich Targets: Diverse output destinations including File, Database, Console, Mail, EventLog, etc.
- High-Performance Async Logging: Fast log processing through background threads and application performance improvement
- Flexible Configuration System: Detailed customization through XML, JSON, and programmatic configuration
- Structured Logging Support: Modern log formats through JSON output and message templates
- ASP.NET Core Integration: Complete integration with Microsoft.Extensions.Logging and DI support
- Conditional Logging: Dynamic control of log levels and output destinations through runtime conditions
Pros and Cons
Pros
- Excellent balance of performance and flexibility for wide range of scenarios
- Support for enterprise-level complex output requirements through 30+ targets
- Standard usage in ASP.NET Core through complete integration with Microsoft.Extensions.Logging
- High throughput and throttling resistance through lightweight design
- Overwhelming stability and reliability through 20+ years of development experience
- Dynamic log level changes during operation through configuration files
Cons
- Limited advanced features compared to Serilog's specialized structured logging focus
- Slightly high initial learning cost due to rich features
- Readability and maintainability challenges with XML configuration files
- Features may be overkill for lightweight applications
- Complex configurations may be more verbose than other libraries
- Some functionality richness falls short compared to Serilog in structured logging
References
Code Examples
Installation and Basic Setup
<!-- NuGet Package Manager or .csproj -->
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.8" />
<!-- ASP.NET Core Integration -->
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
# Package Manager Console
Install-Package NLog
Install-Package NLog.Web.AspNetCore
# .NET CLI
dotnet add package NLog
dotnet add package NLog.Web.AspNetCore
// Simplest usage example
using NLog;
public class SimpleExample
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public static void Main()
{
logger.Trace("Trace message");
logger.Debug("Debug message");
logger.Info("Info message");
logger.Warn("Warning message");
logger.Error("Error message");
logger.Fatal("Fatal message");
// Parameterized logging
string userName = "john_doe";
int userId = 12345;
logger.Info("User {UserName} logged in with ID {UserId}", userName, userId);
}
}
Basic Logging Operations (Levels, Formatting)
using NLog;
using System;
public class BasicLoggingExample
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly Logger serviceLogger = LogManager.GetLogger("Service");
private static readonly Logger dbLogger = LogManager.GetLogger("Database");
public static void Main()
{
// Basic log output
logger.Trace("Detailed debug information");
logger.Debug("Debug information");
logger.Info("General information message");
logger.Warn("Warning message");
logger.Error("Error message");
logger.Fatal("Fatal error message");
// Structured logging (recommended)
logger.Info("User operation: Operation={Operation}, Duration={Duration}ms",
"UserLogin", 150);
// Conditional logging
if (logger.IsDebugEnabled)
{
logger.Debug("Expensive debug information: {Data}", GetExpensiveDebugData());
}
// Exception logging
try
{
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
logger.Error(ex, "Division by zero error occurred");
}
// Category-specific loggers
serviceLogger.Info("Service layer processing");
dbLogger.Debug("Database connection established");
// Scoped logging
using (ScopeContext.PushProperty("CorrelationId", Guid.NewGuid()))
{
logger.Info("Processing within scope");
ProcessUserRequest();
}
}
private static void ProcessUserRequest()
{
logger.Info("Processing user request");
}
private static string GetExpensiveDebugData()
{
return "Expensive debug data generation result";
}
}
Advanced Configuration and Customization (XML Configuration, Targets)
<!-- NLog.config - Advanced configuration example -->
<?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"
internalLogLevel="Info"
internalLogFile="logs/internal-nlog.txt">
<!-- Variable definitions -->
<variable name="logDirectory" value="logs"/>
<variable name="applicationName" value="MyApp"/>
<!-- Target definitions -->
<targets>
<!-- Console output -->
<target xsi:type="Console" name="console"
layout="${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=tostring}" />
<!-- Colored console -->
<target xsi:type="ColoredConsole" name="coloredConsole"
layout="${longdate} ${level:uppercase=true:padding=-5} ${logger:shortName=true} ${message} ${exception:format=tostring}">
<highlight-row condition="level == LogLevel.Error" foregroundColor="Red" />
<highlight-row condition="level == LogLevel.Warn" foregroundColor="Yellow" />
<highlight-row condition="level == LogLevel.Info" foregroundColor="Green" />
</target>
<!-- File output -->
<target xsi:type="File" name="fileTarget"
fileName="${logDirectory}/${applicationName}.log"
layout="${longdate} ${level:uppercase=true} [${threadid}] ${logger} ${message} ${exception:format=tostring}"
archiveFileName="${logDirectory}/archive/${applicationName}.{#}.log"
archiveEvery="Day"
archiveNumbering="Rolling"
maxArchiveFiles="30"
concurrentWrites="true"
keepFileOpen="false" />
<!-- Error-specific file -->
<target xsi:type="File" name="errorFileTarget"
fileName="${logDirectory}/error.log"
layout="${longdate} ${level:uppercase=true} [${threadid}] ${logger} ${message} ${newline}${exception:format=detailed}"
archiveFileName="${logDirectory}/archive/error.{#}.log"
archiveEvery="Week"
maxArchiveFiles="12" />
<!-- Structured logging (JSON) -->
<target xsi:type="File" name="jsonFileTarget"
fileName="${logDirectory}/structured.log">
<layout xsi:type="JsonLayout" includeAllProperties="true">
<attribute name="timestamp" layout="${longdate}" />
<attribute name="level" layout="${level:upperCase=true}" />
<attribute name="logger" layout="${logger}" />
<attribute name="message" layout="${message}" />
<attribute name="threadId" layout="${threadid}" />
<attribute name="exception" layout="${exception:format=@}" encode="false" />
<attribute name="properties" encode="false">
<layout xsi:type="JsonLayout" includeAllProperties="true" maxRecursionLimit="2" />
</attribute>
</layout>
</target>
<!-- Asynchronous file output -->
<target xsi:type="AsyncWrapper" name="asyncFile" queueLimit="1000" overflowAction="Discard">
<target-ref name="fileTarget" />
</target>
<!-- Database output -->
<target xsi:type="Database" name="database"
connectionString="Data Source=localhost;Initial Catalog=LogDB;Integrated Security=true;">
<commandText>
INSERT INTO Logs(TimeStamp, Level, Logger, Message, Exception, MachineName, UserName)
VALUES(@TimeStamp, @Level, @Logger, @Message, @Exception, @MachineName, @UserName)
</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:tostring}" />
<parameter name="@MachineName" layout="${machinename}" />
<parameter name="@UserName" layout="${windows-identity:domain=false}" />
</target>
<!-- Email notification -->
<target xsi:type="Mail" name="mailTarget"
smtpServer="smtp.company.com"
smtpPort="587"
smtpAuthentication="Basic"
smtpUserName="[email protected]"
smtpPassword="password"
enableSsl="true"
from="[email protected]"
to="[email protected]"
subject="Critical Error in ${applicationName}"
body="${longdate} ${level:uppercase=true} ${logger} ${newline}${message} ${newline}${exception:format=detailed}"
html="false" />
</targets>
<!-- Rules definition -->
<rules>
<!-- Email notification for errors and above -->
<logger name="*" minlevel="Error" writeTo="mailTarget" />
<!-- All levels to file output (async) -->
<logger name="*" minlevel="Trace" writeTo="asyncFile" />
<!-- Errors and above to dedicated file -->
<logger name="*" minlevel="Error" writeTo="errorFileTarget" />
<!-- INFO and above to structured log -->
<logger name="*" minlevel="Info" writeTo="jsonFileTarget" />
<!-- Development environment console output -->
<logger name="*" minlevel="Debug" writeTo="coloredConsole">
<filters>
<when condition="'${environment:ASPNETCORE_ENVIRONMENT}' != 'Development'" action="Ignore" />
</filters>
</logger>
<!-- Microsoft library log level adjustment -->
<logger name="Microsoft.*" maxlevel="Info" final="true" />
<logger name="System.Net.Http.*" maxlevel="Info" final="true" />
<!-- Database-related logs to dedicated target -->
<logger name="Database*" minlevel="Debug" writeTo="database" />
</rules>
</nlog>
Structured Logging and Modern Observability Support
using NLog;
using Microsoft.Extensions.Logging;
using System.Text.Json;
public class StructuredLoggingExample
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
public void ProcessUserOrder(string userId, string orderId, decimal amount)
{
// Setting scope information
using (ScopeContext.PushProperty("UserId", userId))
using (ScopeContext.PushProperty("OrderId", orderId))
using (ScopeContext.PushProperty("CorrelationId", Guid.NewGuid()))
{
try
{
logger.Info("Order processing started: Amount={Amount}, Currency={Currency}",
amount, "USD");
// Business logic processing
var result = ProcessPayment(amount);
logger.Info("Order processing completed: Status={Status}, ProcessingTime={ProcessingTime}ms",
"Success", result.ProcessingTime);
// Metrics information
logger.Info("BusinessMetric: MetricName={MetricName}, Value={Value}, Labels={@Labels}",
"order_completed_total", 1, new {
payment_method = "credit_card",
customer_type = "premium"
});
}
catch (Exception ex)
{
logger.Error(ex, "Order processing failed: Status={Status}, Amount={Amount}",
"Failed", amount);
throw;
}
}
}
// OpenTelemetry-style logging
public void LogWithTracing(string traceId, string spanId)
{
using (ScopeContext.PushProperty("TraceId", traceId))
using (ScopeContext.PushProperty("SpanId", spanId))
{
logger.Info("Distributed tracing enabled log: Operation={Operation}", "UserAuthentication");
}
}
// Prometheus-style metrics
public void LogMetrics(string metricType, string metricName, double value, object labels = null)
{
logger.Info("Metric: Type={MetricType}, Name={MetricName}, Value={Value}, Labels={@Labels}",
metricType, metricName, value, labels ?? new { });
}
// Security events
public void LogSecurityEvent(string eventType, string ipAddress, string userAgent)
{
logger.Warn("SecurityEvent: Type={EventType}, IP={IPAddress}, UserAgent={UserAgent}, Severity={Severity}",
eventType, ipAddress, userAgent, "Medium");
}
private PaymentResult ProcessPayment(decimal amount)
{
var startTime = DateTime.UtcNow;
// Payment processing simulation
System.Threading.Thread.Sleep(100);
return new PaymentResult
{
Success = true,
ProcessingTime = (DateTime.UtcNow - startTime).TotalMilliseconds
};
}
}
public class PaymentResult
{
public bool Success { get; set; }
public double ProcessingTime { get; set; }
}
Error Handling and Performance Optimization
using NLog;
using System.Diagnostics;
public class PerformanceOptimizedService
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private static readonly Logger performanceLogger = LogManager.GetLogger("Performance");
// High-performance logging pattern
public async Task<T> ExecuteWithLogging<T>(string operationName, Func<Task<T>> operation)
{
var stopwatch = Stopwatch.StartNew();
var operationId = Guid.NewGuid().ToString("N")[..8];
using (ScopeContext.PushProperty("OperationId", operationId))
using (ScopeContext.PushProperty("OperationName", operationName))
{
try
{
logger.Debug("Operation started");
var result = await operation();
stopwatch.Stop();
performanceLogger.Info("Operation completed: Duration={Duration}ms, Success={Success}",
stopwatch.ElapsedMilliseconds, true);
// Performance threshold check
if (stopwatch.ElapsedMilliseconds > 1000)
{
logger.Warn("Operation took longer than expected: Duration={Duration}ms",
stopwatch.ElapsedMilliseconds);
}
return result;
}
catch (Exception ex)
{
stopwatch.Stop();
logger.Error(ex, "Operation failed: Duration={Duration}ms, Success={Success}",
stopwatch.ElapsedMilliseconds, false);
// Critical error determination
if (IsCriticalError(ex))
{
logger.Fatal("CRITICAL: Immediate attention required: {ErrorType}", ex.GetType().Name);
}
throw;
}
}
}
// Performance optimization through conditional logging
public void LogExpensiveOperation()
{
// Avoid expensive processing with IsEnabled check
if (logger.IsDebugEnabled)
{
var expensiveData = GenerateExpensiveDebugData();
logger.Debug("Detailed debug information: {@Data}", expensiveData);
}
// Utilizing lazy evaluation
logger.Info("User information: {UserData}", () => GetUserDataForLogging());
}
// Bulk log processing
public void LogBulkOperations(IEnumerable<string> operations)
{
var totalCount = 0;
var errorCount = 0;
foreach (var operation in operations)
{
try
{
ProcessOperation(operation);
totalCount++;
}
catch (Exception ex)
{
errorCount++;
logger.Error(ex, "Error in bulk processing: Operation={Operation}", operation);
}
}
logger.Info("Bulk processing completed: Total={Total}, Errors={Errors}, SuccessRate={SuccessRate:P}",
totalCount, errorCount, (double)(totalCount - errorCount) / totalCount);
}
private bool IsCriticalError(Exception ex)
{
return ex is OutOfMemoryException ||
ex is StackOverflowException ||
ex.Message.Contains("database", StringComparison.OrdinalIgnoreCase);
}
private object GenerateExpensiveDebugData()
{
return new { Timestamp = DateTime.UtcNow, Memory = GC.GetTotalMemory(false) };
}
private string GetUserDataForLogging()
{
return "user_data_for_logging";
}
private void ProcessOperation(string operation)
{
// Operation processing
}
}
Framework Integration and Practical Examples
// Program.cs - ASP.NET Core integration
using NLog;
using NLog.Web;
var builder = WebApplication.CreateBuilder(args);
// Integrate NLog with ASP.NET Core
builder.Logging.ClearProviders();
builder.Host.UseNLog();
builder.Services.AddControllers();
var app = builder.Build();
// Configure request logging
app.UseNLogWeb();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.Run();
// Controller with NLog
using Microsoft.AspNetCore.Mvc;
using NLog;
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private readonly IUserService userService;
public UsersController(IUserService userService)
{
this.userService = userService;
}
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(string id)
{
using (ScopeContext.PushProperty("UserId", id))
using (ScopeContext.PushProperty("RequestId", HttpContext.TraceIdentifier))
{
try
{
logger.Info("User information retrieval started");
var user = await userService.GetByIdAsync(id);
if (user == null)
{
logger.Warn("User not found");
return NotFound();
}
logger.Info("User information retrieval successful");
return Ok(user);
}
catch (Exception ex)
{
logger.Error(ex, "Error occurred during user information retrieval");
return StatusCode(500, "Internal Server Error");
}
}
}
}
// appsettings.json NLog configuration
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"NLog": {
"autoReload": true,
"throwConfigExceptions": true,
"internalLogLevel": "Info",
"internalLogFile": "logs/internal-nlog.txt",
"extensions": [
{ "assembly": "NLog.Web.AspNetCore" }
],
"targets": {
"fileTarget": {
"type": "File",
"fileName": "logs/nlog-${shortdate}.log",
"layout": "${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=tostring}"
},
"jsonFile": {
"type": "File",
"fileName": "logs/nlog-json-${shortdate}.log",
"layout": {
"type": "JsonLayout",
"Attributes": [
{ "name": "timestamp", "layout": "${longdate}" },
{ "name": "level", "layout": "${level:upperCase=true}" },
{ "name": "logger", "layout": "${logger}" },
{ "name": "message", "layout": "${message}" },
{ "name": "requestId", "layout": "${aspnet-TraceIdentifier}" },
{ "name": "userId", "layout": "${aspnet-user-identity}" }
]
}
}
},
"rules": [
{
"logger": "*",
"minLevel": "Info",
"writeTo": "fileTarget"
},
{
"logger": "*",
"minLevel": "Info",
"writeTo": "jsonFile"
}
]
}
}
// DI Container configuration example
using NLog;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register NLog with DI container
services.AddSingleton<ILoggerFactory, LoggerFactory>();
services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
// Custom service using NLog
services.AddScoped<IUserService, UserService>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Configure logging middleware
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseNLogWeb();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}