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.

Logging LibraryC#.NETASP.NET CoreStructured LoggingPerformance

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());
    }
}