[鐵人賽 Day19] ASP.NET Core 2 系列 - NLog & Log4net

-- Pageviews

[鐵人賽 Day19] ASP.NET Core 2 系列 - NLog & Log4net

ASP.NET Core 提供的 Logging API,不僅可以方便調用 Logger,且支援多種 Log 輸出,也能把 Log 發送到多個地方,並支援第三方的 Logging Framework 套件。
本篇將介紹 ASP.NET Core 的 Logging 搭配第三方 Logging Framework 套件,NLogLog4net 的範例。

iT 邦幫忙 2018 鐵人賽 - Modern Web 組參賽文章:
[Day19] ASP.NET Core 2 系列 - NLog & Log4net

NLog

NLog 是 .NET 的熱門 Logging Framework;而且還是 ASP.NET Core 官方第三方 Logging Framework 推薦名單之一。

安裝套件

ASP.NET Core 使用 NLog 需要安裝 NLogNLog.Web.AspNetCore 套件。
透過 .NET Core CLI 在專案資料夾執行安裝指令:

1
2
dotnet add package NLog -v 4.5.0-rc02
dotnet add package NLog.Web.AspNetCore -v 4.5.0-rc2

.NET Core 的版本目前還是 RC 版。

組態設定檔

新增一個 nlog.config 的檔案如下:

nlog.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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="C:\Logs\MyWebsite\nlog-internal.txt">
<targets>
<!-- write logs to file -->
<target xsi:type="File" name="ALL"
fileName="C:\Logs\MyWebsite\nlog-all_${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId.Id}|${uppercase:${level}}|${logger}|${message} ${exception}" />
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="ALL" />
</rules>
</nlog>

NLog 組態設定可以參考:NLog Configuration file

Program.Main 啟動時載入 NLog 組態設定檔,並在 WebHost Builder 注入 NLog 服務。

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using NLog.Web;

namespace MyWebsite
{
public class Program
{
public static void Main(string[] args)
{
NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseNLog()
.Build();
}
}
}

Log 輸出範例延續前篇[鐵人賽 Day18] ASP.NET Core 2 系列 - Logging
以下 Log 輸出都是以前篇的 Home.Index() 做為範例。

輸出結果如下:

C:\Logs\MyWebsite\nlog-all_2018-01-07.log

1
2
3
4
5
6
7
8
9
10
2018-01-07 00:27:32.6339||INFO|Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager|User profile is available. Using 'C:\Users\john.wu\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. 
2018-01-07 00:27:33.1149||INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request starting HTTP/1.1 GET http://localhost:5000/
2018-01-07 00:27:33.1969||INFO|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executing action method MyWebsite.HomeController.Index (MyWebsite) with arguments ((null)) - ModelState is Valid
2018-01-07 00:27:33.1999||INFO|MyWebsite.HomeController|This information log from Home.Index()
2018-01-07 00:27:33.1999||WARN|MyWebsite.HomeController|This warning log from Home.Index()
2018-01-07 00:27:33.1999||ERROR|MyWebsite.HomeController|This error log from Home.Index()
2018-01-07 00:27:33.1999||FATAL|MyWebsite.HomeController|This critical log from Home.Index()
2018-01-07 00:27:33.2219||INFO|Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor|Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
2018-01-07 00:27:33.2459||INFO|Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker|Executed action MyWebsite.HomeController.Index (MyWebsite) in 56.8935ms
2018-01-07 00:27:33.2519||INFO|Microsoft.AspNetCore.Hosting.Internal.WebHost|Request finished in 137.5115ms 200 text/plain; charset=utf-8

安裝完套件後,只要加一個設定檔及兩行程式碼就完成,可說是非常的友善使用。

Log4net

從網路上各方訊息看來,Log4net 應該是 .NET 最熱門的 Logging Framework,我個人也是習慣用 Log4net。

安裝套件

.NET Core 使用 Log4net 需要安裝 log4net 套件。
透過 .NET Core CLI 在專案資料夾執行安裝指令:

1
dotnet add package log4net

組態設定檔

新增一個 log4net.config 的檔案如下:

log4net.config

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<log4net>
<appender name="All" type="log4net.Appender.RollingFileAppender">
<file value="C:\Logs\MyWebsite\log4net-all" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<datePattern value="_yyyy-MM-dd.lo\g" />
<maximumFileSize value="5MB" />
<maxSizeRollBackups value="15" />
<staticLogFileName value="false" />
<PreserveLogFileNameExtension value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %level %logger - %message%newline" />
</layout>
</appender>
<root>
<appender-ref ref="All" />
</root>
</log4net>
</configuration>

Log4net 組態設定可以參考:Apache log4net Manual - Configuration

Program.Main 啟動時載入 Log4net 組態設定檔。

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using System.IO;
using System.Reflection;
using log4net;
using log4net.Config;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;

namespace MyWebsite
{
public class Program
{
private readonly static ILog _log = LogManager.GetLogger(typeof(Program));

public static void Main(string[] args)
{
LoadLog4netConfig();
_log.Info("Application Start");
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}

private static void LoadLog4netConfig()
{
var repository = LogManager.CreateRepository(
Assembly.GetEntryAssembly(),
typeof(log4net.Repository.Hierarchy.Hierarchy)
);
XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));
}
}
}

載入 Log4net 組態設定後,就可以直接操作 _log 物件寫 Log,用法就跟過去 .NET Framework 一樣。
但 Log4net 沒有實作 ASP.NET Core 的 Logging API,所以沒辦法透過注入 ILogger 寫 Log4net 的 Log。

難怪 ASP.NET Core 官方不推 Log4net…

ILogger

既然 Log4net 沒有實作 ILogger,就自己做吧!
建立一個 Log4netLogger.cs,內容如下:

Log4netLogger.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
using System;
using System.IO;
using System.Reflection;
using log4net;
using log4net.Config;
using Microsoft.Extensions.Logging;

namespace MyWebsite
{
public class Log4netLogger : ILogger
{
private readonly ILog _log;

public Log4netLogger(string name, FileInfo fileInfo)
{
var repository = LogManager.CreateRepository(
Assembly.GetEntryAssembly(),
typeof(log4net.Repository.Hierarchy.Hierarchy)
);
XmlConfigurator.Configure(repository, fileInfo);
_log = LogManager.GetLogger(repository.Name, name);
}

public IDisposable BeginScope<TState>(TState state)
{
return null;
}

public bool IsEnabled(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Critical: return _log.IsFatalEnabled;
case LogLevel.Debug:
case LogLevel.Trace: return _log.IsDebugEnabled;
case LogLevel.Error: return _log.IsErrorEnabled;
case LogLevel.Information: return _log.IsInfoEnabled;
case LogLevel.Warning: return _log.IsWarnEnabled;
default:
throw new ArgumentOutOfRangeException(nameof(logLevel));
}
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
}
if (formatter == null)
{
throw new ArgumentNullException(nameof(formatter));
}
string message = null;
if (null != formatter)
{
message = formatter(state, exception);
}
if (!string.IsNullOrEmpty(message) || exception != null)
{
switch (logLevel)
{
case LogLevel.Critical: _log.Fatal(message); break;
case LogLevel.Debug:
case LogLevel.Trace: _log.Debug(message); break;
case LogLevel.Error: _log.Error(message); break;
case LogLevel.Information: _log.Info(message); break;
case LogLevel.Warning: _log.Warn(message); break;
default:
_log.Warn($"Unknown log level {logLevel}.\r\n{message}");
break;
}
}
}
}
}

由於 Log4net 的 Log Level 跟 ASP.NET Core Logger API 的級別不一致,所以要將 Log Level 的事件做相對的對應。

ILoggerProvider。

ILogger 主要是透過 Logger Provider 產生,所以需要實作 ILoggerProvider
建立一個 Log4netProvider.cs,內容如下:

Log4netProvider.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System.IO;
using Microsoft.Extensions.Logging;

namespace MyWebsite
{
public class Log4netProvider : ILoggerProvider
{
private readonly FileInfo _fileInfo;

public Log4netProvider(string log4netConfigFile)
{
_fileInfo = new FileInfo(log4netConfigFile);
}

public ILogger CreateLogger(string name)
{
return new Log4netLogger(name, _fileInfo);
}

public void Dispose()
{
}
}
}

Log4netProvider 註冊到 WebHost 的 ConfigureLogging 中。

Program.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;

namespace MyWebsite
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(string[] args)
{
return WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(logging => {
logging.AddProvider(new Log4netProvider("log4net.config"));
})
.UseStartup<Startup>()
.Build();
}
}
}

如此一來,也能透過 ASP.NET Core 的 Logger API 寫出 Log4net 的 Log 了。

輸出結果如下:

C:\Logs\MyWebsite\log4net-all_2018-01-07.log

1
2
3
4
5
6
7
8
9
10
2018-01-07 00:56:46,673 [1] INFO Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager - User profile is available. Using 'C:\Users\john.wu\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
2018-01-07 00:56:47,167 [17] INFO Microsoft.AspNetCore.Hosting.Internal.WebHost - Request starting HTTP/1.1 GET http://localhost:5000/
2018-01-07 00:56:47,261 [17] INFO Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker - Executing action method MyWebsite.HomeController.Index (MyWebsite) with arguments ((null)) - ModelState is Valid
2018-01-07 00:56:47,265 [17] INFO MyWebsite.HomeController - This information log from Home.Index()
2018-01-07 00:56:47,266 [17] WARN MyWebsite.HomeController - This warning log from Home.Index()
2018-01-07 00:56:47,268 [17] ERROR MyWebsite.HomeController - This error log from Home.Index()
2018-01-07 00:56:47,269 [17] FATAL MyWebsite.HomeController - This critical log from Home.Index()
2018-01-07 00:56:47,278 [17] INFO Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor - Executing ObjectResult, writing value Microsoft.AspNetCore.Mvc.ControllerContext.
2018-01-07 00:56:47,303 [17] INFO Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker - Executed action MyWebsite.HomeController.Index (MyWebsite) in 52.7449ms
2018-01-07 00:56:47,305 [17] INFO Microsoft.AspNetCore.Hosting.Internal.WebHost - Request finished in 141.4295ms 200 text/plain; charset=utf-8

參考

Introduction to Logging in ASP.NET Core
NLog - Getting started with ASP.NET Core 2
How to use Log4Net with ASP.NET Core for logging