相关文章推荐
快乐的麻辣香锅  ·  termux中Permission ...·  1 年前    · 
细心的筷子  ·  语音特征提取: ...·  2 年前    · 
谦和的拐杖  ·  Shell 教程:编写 BASH ...·  2 年前    · 
热心的皮蛋  ·  Teradata ...·  2 年前    · 
完善 .Net Core 项目 — NLog入门 (日志组件)

完善 .Net Core 项目 — NLog入门 (日志组件)

4 年前 · 来自专栏 小白的.Net学习之路

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)
  • 对于 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 支持两种文件格式:

  1. 在标准的 *.exe.config 或 web.config 中嵌入配置
  2. 在单独的文件中进行配置

个人觉得单独的配置文件便于在项目中切换配置或日志库。

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 ),按照优先级只生效当前优先级最高的属性。等级相关属性优先级如下,

  1. level
  2. levels
  3. minLevel maxLevel
  4. 没有设置(所有等级的日志都会被记录)

以下是 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>
  1. 命名空间 Name.Space 下类 Class1 中高于 Debug 级别的日志信息将被写入输出目标 f1
  2. 命名空间 Name.Space 下类 Class1 中级别为 Debug Error 的日志信息将被写入 target:f1
  3. 命名空间 Name.Space 下所有类中的日志信息将被写入 target:f3, f4
  4. 命名空间 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 的配置:

  1. 新建 NLog.Config.LoggingConfiguration 对象;
  2. 新建一个或多个继承自 NLog.Targets.Target 抽象类的具体目标类对象,并通过 NLog.Config.LoggingConfiguration 对象的 AddTarget 方法将输出目标添加到配置对象中;
  3. 设置输出目标的属性;
  4. 新建 NLog.Config.LoggingRule 对象指定路由规则,并通过 NLog.Config.LoggingConfiguration 对象 LoggingRules 属性的 Add 方法将路由规则添加到配置对象中;
  5. 通过将配置对象赋值到 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");