NLog

シンプルさとパフォーマンスで評価される成熟したロギングフレームワーク。log4netよりもクリーンなコードベース設定、合理的なデフォルト設定を提供。高いログ生成負荷でのスロットリング性能に優れ、構造化ログ機能も提供。

ロギングライブラリC#.NETASP.NET Core構造化ログパフォーマンス

ライブラリ

NLog

概要

NLogは.NET向けの高性能で柔軟なロギングライブラリです。軽量ながら豊富な機能を提供し、30以上の出力ターゲット、構造化ログ対応、高度な設定機能を搭載しています。パフォーマンス重視の設計でスロットリングシナリオにも優秀で、ASP.NET CoreでのMicrosoft.Extensions.Loggingとの完全統合により、現代的な.NET開発に最適化されています。軽量性とスループットの高さで評価され、企業レベルからスタートアップまで幅広く採用されています。

詳細

NLog 6.0は2025年現在、.NET開発でパフォーマンスと設定の柔軟性を重視する場面での重要な選択肢として位置づけられています。20年以上の開発実績により、安定性と信頼性を兼ね備え、.NET Framework 2.0から.NET 8まで幅広いプラットフォームをサポート。豊富なターゲット(File、Database、Console、Mail、EventLog等)、条件付きログ処理、非同期ログ、構造化ログ出力により、小規模アプリケーションから大規模エンタープライズシステムまで対応します。特にASP.NET Coreとの深い統合により、モダンなWebアプリケーション開発に最適化されています。

主な特徴

  • 30以上の豊富なターゲット: File、Database、Console、Mail、EventLog等の多様な出力先
  • 高性能非同期ログ: バックグラウンドスレッドによる高速ログ処理とアプリケーション性能向上
  • 柔軟な設定システム: XML、JSON、プログラマティック設定による詳細なカスタマイズ
  • 構造化ログ対応: JSON出力とメッセージテンプレートによる現代的なログ形式
  • ASP.NET Core統合: Microsoft.Extensions.Loggingとの完全統合とDI対応
  • 条件付きロギング: 実行時条件によるログレベルと出力先の動的制御

メリット・デメリット

メリット

  • パフォーマンスと柔軟性の優れたバランスによる幅広いシナリオでの適用
  • 30以上のターゲットにより企業レベルの複雑な出力要件に対応
  • Microsoft.Extensions.Loggingとの完全統合によるASP.NET Coreでの標準的利用
  • 軽量設計による高いスループットとスロットリング耐性
  • 20年以上の開発実績による圧倒的な安定性と信頼性
  • 設定ファイルによる運用時の動的ログレベル変更が可能

デメリット

  • Serilogほど構造化ログに特化していないため、一部の高度な機能が限定的
  • 豊富な機能ゆえに初期学習コストがやや高い
  • XML設定ファイルの可読性と保守性の課題
  • 軽量アプリケーションには機能がオーバースペックとなる可能性
  • 複雑な設定では他のライブラリより冗長になる場合がある
  • 構造化ログではSerilogに比べて機能の豊富さで劣る部分がある

参考ページ

書き方の例

インストールと基本セットアップ

<!-- NuGet Package Manager または .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
// 最もシンプルな使用例
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");
        
        // パラメータ付きログ
        string userName = "john_doe";
        int userId = 12345;
        logger.Info("User {UserName} logged in with ID {UserId}", userName, userId);
    }
}

基本的なロギング操作(レベル、フォーマット)

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()
    {
        // 基本的なログ出力
        logger.Trace("詳細なデバッグ情報");
        logger.Debug("デバッグ情報");
        logger.Info("一般的な情報メッセージ");
        logger.Warn("警告メッセージ");
        logger.Error("エラーメッセージ");
        logger.Fatal("致命的エラーメッセージ");
        
        // 構造化ログ(推奨)
        logger.Info("ユーザー操作: Operation={Operation}, Duration={Duration}ms", 
                   "UserLogin", 150);
        
        // 条件付きログ
        if (logger.IsDebugEnabled)
        {
            logger.Debug("重い処理のデバッグ情報: {Data}", GetExpensiveDebugData());
        }
        
        // 例外ログ
        try
        {
            int result = 10 / 0;
        }
        catch (DivideByZeroException ex)
        {
            logger.Error(ex, "除算エラーが発生しました");
        }
        
        // 分類別ロガー
        serviceLogger.Info("サービス層の処理");
        dbLogger.Debug("データベース接続が確立されました");
        
        // スコープ情報付きログ
        using (ScopeContext.PushProperty("CorrelationId", Guid.NewGuid()))
        {
            logger.Info("スコープ内での処理");
            ProcessUserRequest();
        }
    }
    
    private static void ProcessUserRequest()
    {
        logger.Info("ユーザーリクエスト処理中");
    }
    
    private static string GetExpensiveDebugData()
    {
        return "重いデバッグデータの生成結果";
    }
}

高度な設定とカスタマイズ(XML設定、ターゲット等)

<!-- NLog.config - 高度な設定例 -->
<?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 name="logDirectory" value="logs"/>
  <variable name="applicationName" value="MyApp"/>
  
  <!-- ターゲット定義 -->
  <targets>
    <!-- コンソール出力 -->
    <target xsi:type="Console" name="console"
            layout="${longdate} ${level:uppercase=true} ${logger} ${message} ${exception:format=tostring}" />
    
    <!-- カラー付きコンソール -->
    <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>
    
    <!-- ファイル出力 -->
    <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" />
    
    <!-- エラー専用ファイル -->
    <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" />
    
    <!-- 構造化ログ(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>
    
    <!-- 非同期ファイル出力 -->
    <target xsi:type="AsyncWrapper" name="asyncFile" queueLimit="1000" overflowAction="Discard">
      <target-ref name="fileTarget" />
    </target>
    
    <!-- データベース出力 -->
    <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>
    
    <!-- メール通知 -->
    <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>
    <!-- エラー以上をメール通知 -->
    <logger name="*" minlevel="Error" writeTo="mailTarget" />
    
    <!-- 全レベルをファイル出力(非同期) -->
    <logger name="*" minlevel="Trace" writeTo="asyncFile" />
    
    <!-- エラー以上を専用ファイル -->
    <logger name="*" minlevel="Error" writeTo="errorFileTarget" />
    
    <!-- INFO以上を構造化ログ -->
    <logger name="*" minlevel="Info" writeTo="jsonFileTarget" />
    
    <!-- 開発環境用コンソール出力 -->
    <logger name="*" minlevel="Debug" writeTo="coloredConsole">
      <filters>
        <when condition="'${environment:ASPNETCORE_ENVIRONMENT}' != 'Development'" action="Ignore" />
      </filters>
    </logger>
    
    <!-- Microsoftライブラリのログレベル調整 -->
    <logger name="Microsoft.*" maxlevel="Info" final="true" />
    <logger name="System.Net.Http.*" maxlevel="Info" final="true" />
    
    <!-- データベース関連ログを専用ターゲット -->
    <logger name="Database*" minlevel="Debug" writeTo="database" />
  </rules>
</nlog>

構造化ログと現代的な可観測性対応

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)
    {
        // スコープ情報の設定
        using (ScopeContext.PushProperty("UserId", userId))
        using (ScopeContext.PushProperty("OrderId", orderId))
        using (ScopeContext.PushProperty("CorrelationId", Guid.NewGuid()))
        {
            try
            {
                logger.Info("注文処理開始: Amount={Amount}, Currency={Currency}", 
                           amount, "JPY");
                
                // ビジネスロジック処理
                var result = ProcessPayment(amount);
                
                logger.Info("注文処理完了: Status={Status}, ProcessingTime={ProcessingTime}ms", 
                           "Success", result.ProcessingTime);
                
                // メトリクス情報
                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, "注文処理失敗: Status={Status}, Amount={Amount}", 
                            "Failed", amount);
                throw;
            }
        }
    }
    
    // OpenTelemetryスタイルのログ
    public void LogWithTracing(string traceId, string spanId)
    {
        using (ScopeContext.PushProperty("TraceId", traceId))
        using (ScopeContext.PushProperty("SpanId", spanId))
        {
            logger.Info("分散トレース対応ログ: Operation={Operation}", "UserAuthentication");
        }
    }
    
    // Prometheus風メトリクス
    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 { });
    }
    
    // セキュリティイベント
    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;
        
        // 支払い処理のシミュレーション
        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; }
}

エラーハンドリングとパフォーマンス最適化

using NLog;
using System.Diagnostics;

public class PerformanceOptimizedService
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger();
    private static readonly Logger performanceLogger = LogManager.GetLogger("Performance");
    
    // 高性能なログパターン
    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("操作開始");
                
                var result = await operation();
                
                stopwatch.Stop();
                performanceLogger.Info("操作完了: Duration={Duration}ms, Success={Success}", 
                                     stopwatch.ElapsedMilliseconds, true);
                
                // パフォーマンス閾値チェック
                if (stopwatch.ElapsedMilliseconds > 1000)
                {
                    logger.Warn("操作が予想より時間がかかりました: Duration={Duration}ms", 
                               stopwatch.ElapsedMilliseconds);
                }
                
                return result;
            }
            catch (Exception ex)
            {
                stopwatch.Stop();
                
                logger.Error(ex, "操作失敗: Duration={Duration}ms, Success={Success}", 
                            stopwatch.ElapsedMilliseconds, false);
                
                // 重要なエラーの判定
                if (IsCriticalError(ex))
                {
                    logger.Fatal("CRITICAL: 即座に対応が必要: {ErrorType}", ex.GetType().Name);
                }
                
                throw;
            }
        }
    }
    
    // 条件付きログによる性能最適化
    public void LogExpensiveOperation()
    {
        // IsEnabledチェックで重い処理を回避
        if (logger.IsDebugEnabled)
        {
            var expensiveData = GenerateExpensiveDebugData();
            logger.Debug("詳細デバッグ情報: {@Data}", expensiveData);
        }
        
        // LazyEvaluationの活用
        logger.Info("ユーザー情報: {UserData}", () => GetUserDataForLogging());
    }
    
    // バルクログ処理
    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, "バルク処理でエラー: Operation={Operation}", operation);
            }
        }
        
        logger.Info("バルク処理完了: 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)
    {
        // 操作の処理
    }
}

フレームワーク統合と実用例

// Program.cs - ASP.NET Core統合
using NLog;
using NLog.Web;

var builder = WebApplication.CreateBuilder(args);

// NLogをASP.NET Coreに統合
builder.Logging.ClearProviders();
builder.Host.UseNLog();

builder.Services.AddControllers();

var app = builder.Build();

// リクエストログの設定
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("ユーザー情報取得開始");
                
                var user = await userService.GetByIdAsync(id);
                
                if (user == null)
                {
                    logger.Warn("ユーザーが見つかりません");
                    return NotFound();
                }
                
                logger.Info("ユーザー情報取得成功");
                return Ok(user);
            }
            catch (Exception ex)
            {
                logger.Error(ex, "ユーザー情報取得中にエラーが発生");
                return StatusCode(500, "Internal Server Error");
            }
        }
    }
}
// appsettings.json でのNLog設定
{
  "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設定例
using NLog;
using Microsoft.Extensions.DependencyInjection;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // NLogをDIコンテナに登録
        services.AddSingleton<ILoggerFactory, LoggerFactory>();
        services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
        
        // カスタムサービスでNLog使用
        services.AddScoped<IUserService, UserService>();
        
        services.AddControllers();
    }
    
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ログミドルウェアの設定
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        
        app.UseNLogWeb();
        app.UseRouting();
        app.UseEndpoints(endpoints => endpoints.MapControllers());
    }
}