如何使用 Microsoft.Extensions.Logging for NLog

  • 4409
  • 0
  • Log
  • 2020-10-29

.NET 提供了標準化的 Log (Microsoft.Extensions.Logging),NLog 4.5 的時候支援了結構化日誌,同時也實作標準日誌,由於程式碼已經依賴了標準,引用 NLog 也只是彈指之間的設定,仍然可以使用舊的 NLog.Config。還不知道如何使用標準化日誌請看 這裡

開發環境

  • VS 2019
  • .NET Framework 4.8
  • .NLog 4.7.5
  • Microsoft.Extensions.Logging 3.1.9
  • Microsoft.Extensions.DependencyInjection 3.1.9

 

.NET Framework 4.8 or .NET Core 3.1 桌面應用程式

NLog  4.5 之後支援了 .NET Standard

開一個 WInFrom App 、.NET Framework 4.8  專案並安裝以下套件

Install-package Microsoft.Extensions.Logging
Install-Package NLog.Extensions.Logging
Install-Package NLog.Config
Install-Package Microsoft.Extensions.Configuration.Json

 

NLog 組態設定

NLog.Config 套件裝完就會產生 NLog.config,修改一下檔案

  • 增加兩個 target,檔案和 Console
  • NLog Provider 本身就內建非同步寫入,<targets async="true">,正好彌補 Microsoft.Extensions.Logging 的 Log 方法沒有支援非同步
<?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"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">

  <!-- optional, add some variables
  https://github.com/nlog/NLog/wiki/Configuration-file#variables
  -->
  <variable name="myvar" value="myvalue"/>
  <variable name="generic" value="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"/>

  <!--
  See https://github.com/nlog/nlog/wiki/Configuration-file
  for information on customizing logging rules and outputs.
   -->
  <targets async="true">

    <!--
    add your targets here
    See https://github.com/nlog/NLog/wiki/Targets for possible targets.
    See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers.
    -->

    <!--
    Write events to a file with the date in the filename.
    <target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
            layout="${longdate} ${uppercase:${level}} ${message}" />
    -->
    <target xsi:type="File" name="logfile" fileName="${basedir}/logs/${shortdate}.log"
            layout="${generic}" />
    <target xsi:type="Console" name="logconsole"
            layout="${generic}" />
  </targets>

  <rules>
    <!-- add your logging rules here -->

    <!--
    Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace)  to "f"
    <logger name="*" minlevel="Debug" writeTo="f" />
    -->
    <logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />

  </rules>
</nlog>

 

LoggerFactory.Create

建立 LoggerFactory,下面代碼可以把它搬到靜態類別再實例化它,我就不實作了。

private static readonly ILogger<Form1> Logger;

static Form1()
{
    var factory = LoggerFactory.Create(builder =>
                                       {
                                           builder.AddFilter("Microsoft", LogLevel.Warning)
                                                  .AddFilter("System",           LogLevel.Warning)
                                                  .AddFilter("WindowsFormsApp1", LogLevel.Debug)
                                                  .AddNLog();
                                           ;
                                       });
    Logger = factory.CreateLogger<Form1>();
}

 

private void button1_Click(object sender, EventArgs e)
{
    var button = (Button) sender;
    var name   = button.Name;
 
    Logger.LogInformation(LogEvent.GenerateItem, "{name} 按鈕被按了", name);
    Logger.LogInformation(LogEvent.UpdateItem,   "執行更新");
    Logger.LogInformation(LogEvent.GenerateItem, "完成");
}

 

除了輸出到 Console 之外,在 bin 資料夾也有 log 檔案

 

DI container

安裝以下套件

Install-package Microsoft.Extensions.DependencyInjection

 

Runner 依賴 ILogger<Runner>

public class Runner
{
    private readonly ILogger<Runner> _logger;
 
    public Runner(ILogger<Runner> logger)
    {
        this._logger = logger;
    }
 
    public void DoAction(string name)
    {
        this._logger.LogInformation(LogEvent.UpdateItem, "Doing hard work! {Action}", name);
    }
}

參考 https://github.com/NLog/NLog/wiki/Getting-started-with-.NET-Core-2---Console-application#31-create-your-runner-class

 

Form1 依賴 ILogger<Runner>、Runner runner

public partial class Form1 : Form
{
    public ILogger<Form1> Logger { get; set; }
 
    public Runner Runner { get; set; }
 
    public Form1()
    {
        this.InitializeComponent();
    }
 
    public Form1(ILogger<Form1> logger, Runner runner)
    {
        this.InitializeComponent();
        this.Logger = logger;
        this.Runner = runner;
    }
 
    private void button1_Click(object sender, EventArgs e)
    {
        var button = (Button) sender;
        var name   = button.Name;
 
        this.Logger.LogInformation(LogEvent.GenerateItem, "{name} 按鈕被按了", name);
        this.Logger.LogInformation(LogEvent.UpdateItem,   "執行更新");
 
        this.Runner.DoAction(name);
 
        this.Logger.LogInformation(LogEvent.GenerateItem, "完成");
    }
}

 

設定組態

private static IConfiguration CreateConfig()
{
    var config = new ConfigurationBuilder()
                 .SetBasePath(System.IO.Directory
                                    .GetCurrentDirectory()) //From NuGet Package Microsoft.Extensions.Configuration.Json
                 .AddJsonFile("appsettings.json", true, true)
                 .Build();
    return config;
}

 

實例化 ServiceProvider,注入 Form1、Runner

private static ServiceProvider CreateServiceProvider(IConfiguration config)
{
    var serviceCollection = new ServiceCollection();
    return serviceCollection.AddTransient<Form1>() // Runner is the custom class
                            .AddTransient<Runner>()
                            .AddLogging(loggingBuilder =>
                                        {
                                            // configure Logging with NLog
                                            loggingBuilder.ClearProviders();
                                            loggingBuilder.SetMinimumLevel(LogLevel.Trace);
                                            loggingBuilder.AddNLog(config);
                                        })
                            .BuildServiceProvider();
}

 

建立 ServiceProvider 後,取出 Form1,丟給 Application.Run

[STAThread]
private static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var config = CreateConfig();
    using (var serviceProvider = CreateServiceProvider(config))
    {
        var form = serviceProvider.GetService(typeof(Form1)) as Form;
        Application.Run(form);
    }
}

 

執行結果如下:

除了輸出到 Console 之外,在 bin 資料夾也有 log 檔案

 

參考

https://github.com/NLog/NLog/wiki/Getting-started-with-.NET-Core-2---Console-application

ASP.NET Core 3.1

新增一個 ASP.NET Core Web Application -> API 專案,安裝以下套件

Install-package NLog.Web.AspNetCore

 

手動加入 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"
      xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
      autoReload="true"
      throwExceptions="false"
      internalLogLevel="Off" internalLogFile="c:\temp\nlog-internal.log">

  <!-- enable asp.net core layout renderers -->
  <extensions>
    <add assembly="NLog.Web.AspNetCore"/>
  </extensions>

  <!-- optional, add some variables
  https://github.com/nlog/NLog/wiki/Configuration-file#variables
  -->
  <variable name="myvar" value="myvalue"/>
  <variable name="generic" value="${longdate}|${event-properties:item=EventId_Id}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}"/>
  <!--<variable name="generic" value="${longdate}|${level}|${message} |${all-event-properties} ${exception:format=tostring}"/>-->

  <!--
  See https://github.com/nlog/nlog/wiki/Configuration-file
  for information on customizing logging rules and outputs.
   -->
  <targets async="true">

    <!--
    add your targets here
    See https://github.com/nlog/NLog/wiki/Targets for possible targets.
    See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers.
    -->

    <!--
    Write events to a file with the date in the filename.
    <target xsi:type="File" name="f" fileName="${basedir}/logs/${shortdate}.log"
            layout="${longdate} ${uppercase:${level}} ${message}" />
    -->
    <target xsi:type="File" name="logfile" fileName="${basedir}/logs/${shortdate}.log"
            layout="${generic}" />
    <target xsi:type="Console" name="logconsole"
            layout="${generic}" />

  </targets>

  <rules>
    <!-- add your logging rules here -->

    <!--
    Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace)  to "f"
    <logger name="*" minlevel="Debug" writeTo="f" />
    -->
    <logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />

  </rules>
</nlog>

 

將檔案複製到輸出位置

 

清掉預設的 Log Provider,換成 NLog .UseNLog

public class Program
{
    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        return Host.CreateDefaultBuilder(args)
                   .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
                   .ConfigureLogging(logging =>
                                     {
                                         logging.ClearProviders();
                                         logging.SetMinimumLevel(LogLevel.Trace);
                                     })
                   .UseNLog();
    }
 
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }
}

 

Controller 建構函數依賴 ILogger<WeatherForecastController>

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;
 
    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        this._logger = logger;
    }
}

 

在 Action 裡面就可以使用 ILogger<WeatherForecastController> 的實例寫入日誌this._logger.LogInformation

[HttpGet]
public IActionResult Get()
{
    //using (this._logger.BeginScope($"Scope Id:{Guid.NewGuid()}"))
    using (this._logger.BeginScope("Scope Id:{id}", Guid.NewGuid()))
    {
        this._logger.LogInformation(LogEvent.GenerateItem, "開始,訪問 WeatherForecast api");
        this._logger.LogInformation(LogEvent.TestItem,     "執行流程");
 
        var random = new Random();
        var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
                               {
                                   Date         = DateTime.Now.AddDays(index),
                                   TemperatureC = random.Next(-20, 55),
                                   Summary      = Summaries[random.Next(Summaries.Length)]
                               })
                               .ToArray();
 
        this._logger.LogInformation(LogEvent.GenerateItem, "結束流程");
 
        return this.Ok(result);
    }
}

 

執行結果

經實驗,NLog 沒有支援 BeginScope

 

NLog 設定相關資源

targets

輸出目標,NLog 官方實作了很多的目標,官方如果找不到可以嘗試到 Nuget 找,比如 NLog.SlackNLog.Telegram

https://nlog-project.org/config/?tab=targets

layout-renderers

日誌呈現內容,NLog 提供許多的方法讓你不需要寫扣就能取得相關資訊,比如 ${hostname} 取得電腦名稱

https://nlog-project.org/config/?tab=layout-renderers

rule

根據條件寫入目標,例如

logger name 等於 * 任意字串,且 log level 最小等於 Trace,則寫入 logfile,logconsole 兩個 target

<logger name="*" minlevel="Trace" writeTo="logfile,logconsole" />

 

Fluent API

NLog 還可以用 Fluent API 語法操作,由於這不是 Microsoft.Extensions.Logging 的標準 ,所以沒有支援

using NLog.Fluent;

_logger.Info()
       .Message("This is a test fluent message '{0}'.", DateTime.Now.Ticks)
       .Property("Test", "InfoWrite")
       .Write();

 

NLog configuration with appsettings.json

如果不喜歡 nlog.config 也可以用 appsettings.json,請參考以下連結:
https://github.com/NLog/NLog.Extensions.Logging/wiki/NLog-configuration-with-appsettings.json

 

相關文章

通過標準化的 Microsoft.Extensions.Logging 實現日誌紀錄

專案位置

https://github.com/yaochangyu/sample.dotblog/tree/master/Log/Lab.MsLogging.ForNLog

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo