完善 .Net Core 项目 — NLog入门 (日志组件)
0. 写在前面
写这个系列文章的主要原因是,本人经常忘记自己之前折腾过的工具怎么使用。因此记录下这些工具的入门使用方法,旨在快速上手这些工具。
个人水平有限,如果有什么不对的地方,希望大家斧正~
1. NLog 简介
NLog 是一个跨平台的 .Net 日志组件。
NLog 遵从 BSD license,即允许商业应用且完全开放源代码。任何人都可以免费使用并对其进行测试,然后通过邮件列表反馈问题以及建议。
NLog 主页:
Home
2. 安装 NLog
2.1 Package Manager
PM> Install-Package NLog
2.2 .Net CLI
~ $ dotnet add package NLog
3. 配置 NLog
3.1 NLog 的配置方法
NLog 可以通过两种方式进行配置:
- 配置文件
- 程序代码
3.2 通过配置文件配置 NLog
3.2.1 NLog 配置文件路径
NLog 启动时会在某些标准路径查找配置文件,进行自动配置:
- 对于 stand-alone *.exe 应用程序,以下路径会被查询:
- 标准的应用程序配置文件(通常是applicationname.exe.config)
- 应用程序目录下的applicationname.exe.nlog 文件
- 应用程序目录下的 NLog.config 文件 (Name sensitive; using docker dotnet core)
- NLog.dll 所在目录下的 NLog.dll.nlog 文件(only if NLog isn't installed in the GAC)
- 对于 http:// ASP.NET 应用程序,以下文件会被查询:
- 标准 web 应用程序文件 web.config
- web.config 所在目录下的 web.nlog 文件
- 应用程序目录下的 NLog.config 文件
- NLog.dll 所在目录下的 NLog.dll.nlog 文件(only if NLog isn't installed in the GAC)
- 对于 .NET Compact Framework 项目,应用程序配置文件(*.exe.config)和环境变量均不能被识别。因此,NLog 仅在以下路径查询:
- 应用程序目录下的 applicationname.exe.nlog 文件
- 应用程序目录下的 NLog.config 文件
- NLog.dll 所在目录下的 NLog.dll.nlog 文件(only if NLog isn't installed in the GAC)
- 对于 Xamarin Android 项目,assert 文件夹中的 NLog.config 文件会被自动加载。如果配置文件名有所不同,可以执行以下命令:
- LogManager.Configuration = new XmlLoggingConfiguration(“assets/someothername.config");
3.2.2 NLog 配置文件格式
NLog 支持两种文件格式:
- 在标准的 *.exe.config 或 web.config 中嵌入配置
- 在单独的文件中进行配置
个人觉得单独的配置文件便于在项目中切换配置或日志库。
NLog 配置文件是一个以
nlog
为根节点的 XML 文件。
nlog
节点可以添加命名空间,以开启 Visual Studio 的 Intellisense 功能。
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
</nlog>
以下元素可以作为
nlog
节点的子节点,前两种元素在所有 NLog 配置文件中都必须存在,剩余元素是可选的。
-
targets
- 定义日志目标/输出 -
rules
- 定义日志路由规则 -
extensions
- 定义要加载的 NLog 扩展项 *.dll 文件 -
includes
- 定义要包含的外部配置文件 -
variables
- 设置配置变量的值
在
nlog
节点中设置属性
autoReload="true"
,NLog 会监视配置文件,并在配置文件发生更改时自动重新载入配置文件而不需要重启应用程序。该功能支持通过
include
包含的子配置文件。示例如下,
<nlog autoReload="true">
</nlog>
(1) Targets
targets
节点中定义了一系列日志输出目标,每一个输出目标是一个
target
元素。对于每一个
target
元素,
name
属性和
type
属性是必须要指定的:
-
name
-target
的名字。路由规则根据该属性将日志信息路由到当前目标。 -
type
-target
的类型。当使用了 xsi 命名空间时,该属性被命名为 xsi:type。目前 NLog 支持的输出目标列表: Targets
不同类型的
target
节点可以接受不同的属性。例如对于
File
目标,
fileName
参数指定了日志文件的文件名;对于
Console
目标,
error
参数指定日志信息是写到标准错误流还是标准输出流。NLog 内置了许多预定义类型,也可以自定义输出目标类型,详见
如何自定义输出目标
。
以下是
targets
节点的例子:
<targets>
<target name="f1" xsi:type="File" fileName="file1.txt"/>
<target name="f2" xsi:type="File" fileName="file2.txt"/>
<target name="n1"
xsi:type="Network" address="tcp://localhost:4001"/>
<target name="ds" xsi:type="OutputDebugString"/>
</targets>
(2) Rules
rules
节点是日志路由规则的集合,由一个或多个
logger
元素组成。每个
logger
元素记录了 logger 的名字、目标输出以及要处理的日志等级。NLog 从路由规则表的第一个
logger
开始处理,如果当前
logger
有效,则日志信息将被输出到指定的
target
。如果某个
logger
被标记为
final
,那么其后的
logger
都会被忽略。
logger
包含下列属性:
-
name
- logger的名字(可以使用通配符*) -
minLevel
- 最小日志等级 -
maxLevel
- 最大日志等级 -
level
- 单一的日志等级 -
levels
- 以逗号分割的日志等级列表 -
writeTo
- 以逗号分割的输出目标列表 -
final
- 标记当前规则为最后一条规则 -
enabled
- 使能当前规则
如果在一条规则中定义了多个日志等级相关的属性(
level
,
levels
,
minLevel
和
maxLevel
),按照优先级只生效当前优先级最高的属性。等级相关属性优先级如下,
-
level
-
levels
-
minLevel
和maxLevel
- 没有设置(所有等级的日志都会被记录)
以下是
rules
节点的
例子
:
<rules>
<logger name="Name.Space.Class1" minlevel="Debug" writeTo="f1" />
<logger name="Name.Space.Class1" levels="Debug,Error" writeTo="f1" />
<logger name="Name.Space.*" writeTo="f3,f4" />
<logger name="Name.Space.*" minlevel="Debug" maxlevel="Error" final="true" />
</rules>
-
命名空间
Name.Space
下类Class1
中高于Debug
级别的日志信息将被写入输出目标f1
-
命名空间
Name.Space
下类Class1
中级别为Debug
和Error
的日志信息将被写入target:f1
-
命名空间
Name.Space
下所有类中的日志信息将被写入target:f3, f4
-
命名空间
Name.Space
下所有类中级别在Debug
和Error
之间 (Debug
,Info
,Warn
和Error
) 的日志信息不被记录(因为没有指定属性writeTo
)。由于标记了属性final
,之后的logger
都会被忽略。
(3) Extensions
extensions
节点可以添加额外的NLog元包或自定义功能,语法为 。
assembly
属性指定的被包含程序集不带后缀
.dll
。示例如下,
<nlog>
<extensions>
<add assembly="MyAssembly" />
</extensions>
<targets>
<target name="a1" type="MyFirst" host="localhost" />
</targets>
<rules>
<logger name="*" minLevel="Info" appendTo="a1" />
</rules>
</nlog>
NLog 4.0 之后,与
NLog.dll
同目录下名如
NLog*.dll
的程序集(如
NLog.CustomTarget.dll
)会被自动加载。
(4) Includes
include
节点指定当前配置文件包含多个子配置文件,语法为 。通过
${}
语法可以使用环境变量,下例展示包含一个名为当前机器名的配置文件。
<nlog>
<include file="${machinename}.config" />
</nlog>
NLog 4.4.2 之后可以使用通配符
*
指定多个文件。例如,
<include file="nlog-*.config" />
(5) Variables
variable
元素定义了配置文件中需要用到的变量,一般用来表示复杂或者重复的表达式(例如文件名)。变量需要先定义后使用,否则配置文件将初始化失败。定义变量的语法如下:
<variable name="var" value="xxx" />
定义变量之后,可以通过
${var}
语法来使用:
<nlog>
<variable name="logDirectory" value="logs/${shortdate}" />
<targets>
<target name="file1" xsi:type="File" fileName="${logDirectory}/file1.txt" />
<target name="file2" xsi:type="File" fileName="${logDirectory}/file2.txt" />
</targets>
</nlog>
3.3 通过程序代码配置 NLog
3.3.1 程序代码配置步骤
通过以下 5 步在代码中完成对 NLog 的配置:
-
新建
NLog.Config.LoggingConfiguration
对象; -
新建一个或多个继承自
NLog.Targets.Target
抽象类的具体目标类对象,并通过NLog.Config.LoggingConfiguration
对象的AddTarget
方法将输出目标添加到配置对象中; - 设置输出目标的属性;
-
新建
NLog.Config.LoggingRule
对象指定路由规则,并通过NLog.Config.LoggingConfiguration
对象LoggingRules
属性的Add
方法将路由规则添加到配置对象中; -
通过将配置对象赋值到
LogManager.Configuration
属性上启用当前配置
3.3.2 程序代码配置案例
// Step 1. Create configuration object
var config = new LoggingConfiguration();
// Step 2. Create targets and add them to the configuration
var consoleTarget = new ColoredConsoleTarget();
config.AddTarget("console", consoleTarget);
var fileTarget = new FileTarget();
config.AddTarget("file", fileTarget);
// Step 3. Set target properties
consoleTarget.Layout = @"${date:format=HH\:mm\:ss} ${logger} ${message}";
fileTarget.FileName = "${basedir}/file.txt";
fileTarget.Layout = "${message}";
// Step 4. Define rules
var rule1 = new LoggingRule("*", LogLevel.Debug, consoleTarget);
config.LoggingRules.Add(rule1);
var rule2 = new LoggingRule("*", LogLevel.Debug, fileTarget);
config.LoggingRules.Add(rule2);
// Step 5. Activate the configuration
LogManager.Configuration = config;
// Example usage
Logger logger = LogManager.GetLogger("Example");
logger.Trace("trace log message");
logger.Debug("debug log message"
);
logger.Info("info log message");
logger.Warn("warn log message");
logger.Error("error log message");
logger.Fatal("fatal log message");
4. 使用 NLog 记录日志
4.1 获取
NLog.Logger
实例
通过
NLog.LogManager.GetCurrentClassLogger
方法或
NLog.LogManager.GetLogger
方法可以获得
NLog.Logger
的实例。
(1)
NLog.LogManager.GetCurrentClassLogger
方法
通过
NLog.LogManager.GetCurrentClassLogger
方法可以创建一个与所在类同名(包括 namespace)的
NLog.Logger
的实例。
var logger = NLog.LogManager.GetCurrentClassLogger();
(2)
NLog.LogManager.GetLogger
方法
通过
NLog.LogManager.GetLogger("MyLogger")
方法可以显式地指定
NLog.Logger
的名称为 MyLogger。
var logger = NLog.LogManager.GetLogger("MyLogger");
4.2 记录日志
4.2.1 NLog 支持的日志级别
-
Trace
- very detailed logs,包含大量的信息,例如 protocol payloads。该级别一般仅在开发环境中启用。 -
Debug
- debugging information, 比Trance
级别稍微粗略,一般在生产环境中不启用。 -
Info
- information messages,一般在生产环境中启用。 -
Warn
- warning messages,一般用于可恢复或临时性错误的非关键问题。 -
Error
- error messages,一般是异常信息。 -
Fatal
- 非常严重的错误!
4.2.2 NLog 日志记录函数方法
在代码中通过以下方法分别进行不同等级的日志记录,
NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
logger.Trace("Sample trace message");
logger.Debug("Sample debug message");
logger.Info("Sample informational message");
logger.Warn("Sample warning message");
logger.Error("Sample error message");
logger.Fatal("Sample fatal error message");
除此之外,
NLog.Logger
还可以通过调用
Log
方法记录日志,
NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
// log level is passed as the parameter.
logger.Log(LogLevel.Trace, "Sample trace message");
logger.Log(LogLevel.Debug, "Sample debug message");
logger.Log(LogLevel.Info, "Sample informational message");
logger.Log(LogLevel.Warn, "Sample warning message");
logger.Log(LogLevel.Error, "Sample error message");
logger.Log(LogLevel.Fatal, "Sample fatal message");
4.2.3 Layouts and Layout Renderers
(1) 模版宏 Layout Renders
Layout 使用模版宏 Layout Renders 自定义日志输出的内容和格式。例如,大多数
target
使用的默认
SimpleLayout
如下,
${longdate}|${level:uppercase=true}|${logger}|${message}
通过
${}
语法可以使用预定义的模版宏 Layout Renders。
Layout Renders 的列表:
Layout Renders
。
(2) 预定义
Layout
-
CsvLayout
- A specialized layout that renders CSV-formatted events.
-
JsonLayout
- A specialized layout that renders to JSON.
-
LayoutWithHeaderAndFooter
- A specialized layout that supports header and footer.
-
Log4JXmlEventLayout
- A specialized layout that renders Log4j-compatible XML events.
-
SimpleLayout
- Represents a string with embedded placeholders that can render contextual information.
-
CompoundLayout
- A layout containing one or more nested layouts.
5. 单元测试
使用
Debug
类型的
target
对 NLog 进行单元测试。步骤如下,
(1) 在配置文件中设置
Debug
类型的输出目标和相应的路由规则。
<nlog>
<targets>
<target name="unitTest" xsi:type="Debug" layout="${level}-${message}"/>
</targets>
<rules>
<logger name="*" writeTo="unitTest"/>
</rules>
</nlog>
(2) 通过
LogManager.Configuration
获取 NLog 当前配置,
var config = LogManager.Configuration;
(3) 通过
NLog.Config.LoggingConfiguration.FindTargetByName
方法获得对应的输出目标,
var target = config.FindTargetByName("targetName") as DebugTarget;
targetName
为配置文件中输出目标
target
的
name
属性。
⚠️注意:若该
target
被
wrapper
包装(例如
AsyncWrapper
),则该输出目标
target
的
name
属性和配置文件中设置的
name
属性有所不同。至今还未找到对应的
name
属性变化规则。
(4) 记录日志后,通过
DebugTarget.LastMessage
属性获得上一次记录的日志信息,并进行测试。
var massage = target.LastMessage;
6. 最佳实践
(1)
NLog.Logger
应为类的静态变量
新建
NLog.Logger
对象消耗一定的开销,例如需要获取锁或者分配对象。因此推荐以下方式创建
NLog.Logger
,
namespace MyNamespace
public class MyClass
private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
(2) 应由
NLog.Logger
格式化日志
尽量避免直接使用字符串进行格式化。例如,
var message = "Hello" + "Earch";
logger.Info(message);
推荐使用
NLog.Logger
对象进行格式化。例如,
logger.Info("Hello {0}", "Earth");
NLog.Logger
对象可以推迟执行格式化操作的时机,从而减少开销。
(3) 应将异常对象传递给
NLog.Logger
避免将异常作为字符串格式化的参数,而是显示的将异常作为参数传递给函数。例如,
try
catch (Exception ex)
logger.Error(ex, "Something bad happened");