问题由来

在一次将Asp.net Core默认日志换成NLog时,发现NLog配置文件中的设置不生效?具体的来说就是在NLog文件中设置的路由以及对应的日志级别只有在Info或者以上时才生效,而DebugTrace级别则不会有日志输出。比如我的NLog配置:

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
<?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" throwConfigExceptions="true"
internalLogLevel="Warn" internalLogFile="${basedir}/logs/internal.log">

<variable name="logDirectory" value="${basedir}/logs"/>

<extensions>
<add assembly="NLog.Web.AspNetCore"/>
</extensions>

<targets>
<default-wrapper xsi:type="BufferingWrapper" bufferSize="100"/>
<target xsi:type="File" name="file" fileName="${logDirectory}/${shortdate}-${level}.log" encoding="utf-8"
layout="${longdate}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" />
</targets>
<targets>
<default-wrapper xsi:type="AsyncWrapper">
<wrapper-target xsi:type="RetryingWrapper"/>
</default-wrapper>
<target xsi:type="ColoredConsole" name="console" detectConsoleAvailable="true"
layout="${longdate}|${uppercase:${level}}|${logger}|${message}"/>
</targets>

<rules>
<logger name="Microsoft.*" minlevel="Debug" writeTo="console"/>
<logger name="WebApplication.*" minlevel="Trace" writeTo="console"/>
<logger name="*" minlevel="Info" writeTo="file" final="true"/>
</rules>

</nlog>

按照rules中的设置,Microsoft命名空间下Debug级别的日志应该都可以输出在控制台中的,但是启动后控制台的输出只有:

1
2
3
4
5
2020-06-16 20:41:34.9142|INFO|Microsoft.Hosting.Lifetime|Now listening on: https://localhost:5001
2020-06-16 20:41:34.9353|INFO|Microsoft.Hosting.Lifetime|Now listening on: http://localhost:5000
2020-06-16 20:41:34.9353|INFO|Microsoft.Hosting.Lifetime|Application started. Press Ctrl+C to shut down.
2020-06-16 20:41:34.9353|INFO|Microsoft.Hosting.Lifetime|Hosting environment: Development
2020-06-16 20:41:34.9353|INFO|Microsoft.Hosting.Lifetime|Content root path: D:\workspace\csharp\WebApplication\WebApplication

注意这里的格式已经应用了NLog中设置的布局,这说明配置文件被加载了并没有问题,但为什么日志级别的设置不生效呢?

原因所在

其实是因为appsettings.{env}.json中日志级别的设置覆盖了Nlog中的设置。在我们创建一个Asp.Net Core项目时,一般会帮我们创建好两个appsettings.json文件:

appsettings.json
1
2
3
4
5
6
7
8
9
10
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
appsettings.Development.json
1
2
3
4
5
6
7
8
9
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

注意这里使用.net core3.1创建的模板,2.1创建的项目命名空间以及对应日志级别会有所不同。

从这两个文件很容易发现默认的日志级别为Information并且Microsoft命名空间下的日志级别被设置成了Warning,当然也就覆盖了NLog中的设置,为什么会覆盖呢?可以思考一下。并且这里的设置能够影响到NLog中的设置还有一个前提:将NLog添加到容器,并且是以DI的方式来获取ILogger

1
2
3
4
5
6
7
8
9
10
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureLogging(logBuilder =>
{
logBuilder.ClearProviders()
.AddNLog("NLog.config");
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}

如果你是这样添加NLog,然后使用DI在需要记录日志的类中注入ILogger的话,那很可能就中招了。

1
private static readonly NLog.Logger _logger = NLog.LogManager.GetCurrentClassLogger();

如果你不使用DI来注入而是使用上面这个方式来获取logger,那么就不存在这个问题。但是我们使用容器主要的目的不就是容器可以更方便的管理对象之间的依赖吗?更具体地说那就是DI。那这个问题该怎样解决呢?

解决方法

解决的方法其实很简单:

首先删除appsettings.{env}.json中日志相关的设置,然后在注册NLog时设置最低日志级别为你所用到的最低级别(因为就算去掉了这些设置,默认的日志级别也是Information),这样一来,就相当于把日志级别控制和输出都交给NLog来管理。比如我上面的文件中给自己项目的命名空间设置了Trace并且已是最低级别,那么在删除appsettings.{env}.json中日志相关的设置后可以这样来添加NLog

1
2
3
4
5
6
7
8
9
10
11
private static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureLogging(logBuilder =>
{
logBuilder.ClearProviders()
.SetMinimumLevel(LogLevel.Trace) // 最低为Trace
.AddNLog(_configFileRelativePath);
})
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}

或者你也可以使用AddFilter来细分使得日志输出可以“交到”NLog手中。当然,如果你不想删除原有的设置并且不想通过SetMinimumLevel(LogLevel)方法来设置最低级别,你也可以在appsettings.{env}.json文件中设置命名空间和相应的日志级别来保证NLog中的设置不会被覆盖(因为这个文件在启动时会自动被加载)。总之,只需要保证NLog中日志级别的设置不会因为该文件或者默认的Information级别所覆盖即可。如果不使用DI来获取的话那就当我没说。(逃