文章

.NET 日志框架

我们平时的开发离不开记录日志,.net core框架也内置了强大的日志记录功能。

简单示例

创建一个控制台应用,在 appsettings.json 中加入如下配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    },
    "Console": {
      "IncludeScopes": true,
      "LogLevel": {
        "Default": "Debug",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        "alog": "Debug"
      }
    }
}

引用包

1
2
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Console

  注入 Logging 到容器中

1
2
3
4
5
6
7
8
9
10
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.json", false, false);
var configuration = configurationBuilder.Build();
var services = new ServiceCollection();
services.AddLogging(builder =>
{
        builder.AddConfiguration(iconfigurationRoot.GetSection("Logging"));
        builder.AddConsole();
});
var serviceProvider = services.BuildServiceProvider();

下面是 AddLogging 的源码,其中主要的部分就是将 ILoggerFactoryILogger<> 注入到容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
{
    if (services == null)
    {
        throw new ArgumentNullException("services");
    }
    services.AddOptions();
    services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
    services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
    services.TryAddEnumerable(ServiceDescriptor.Singleton((IConfigureOptions<LoggerFilterOptions>)new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
    configure(new LoggingBuilder(services));
    return services;
}

我们可以通过两种方式创建ILogger对象

1
2
3
4
5
6
7
var logFactory = serviceProvider.GetService<ILoggerFactory>();
var logger = logFactory.CreateLogger("Default");
//或
var logger = serviceProvider.GetService<ILogger<Program>>();
logger.LogInformation("aa");
logger.LogError("bb");
logger.LogDebug("cc");

这样我们就完成了打印日志的简单示例。

日志级别

日志级别分为如下7种,从上往下日志级别由低到高。如在配置文件中设置日志级别为 Error,那么 Warning 等低级别的日志都不会被打印。 

1
2
3
4
5
6
7
8
9
10
public enum LogLevel
{
    Trace,
    Debug,
    Information,
    Warning,
    Error,
    Critical,
    None
}

打印日志时推荐使用字符串模板的方式

1
logger.LogDebug("时间:{date}",DateTime.Now);

不推荐下面这种方式,因为我们调试时可能记录大量 Debug 级别日志,当项目投入生产后,我们往往会关闭 Debug 级别日志,字符串模板方式在记录日志时才会进行字符串拼接,所以字符串模板的方式可以防止 Dubug 级别日志的字符串拼接影响性能。

1
logger.LogDebug($"时间:{DateTime.Now}");

日志域

使用日志域方法,可以让同一作用域下的日志带上相同的 scopeId,这样可以帮助我们更好的做日志追踪和排查问题。

Console 配置文件中添加如下配置

1
"IncludeScopes": true

创建日志域并记录日志

1
2
3
4
5
6
using (logger.BeginScope("scopeId:{scopeId}", Guid.NewGuid()))
{
    logger.LogInformation($"时间:{DateTime.Now}");
    logger.LogError("bb");
    logger.LogDebug("cc");
}

Serilog框架记录结构化日志

结构化日志相比与文本日志易于检索,易于分析统计,可以用于日志告警,日志关联,以及与追踪系统的集成。下面演示 Serilog 的简单使用实例。

创建一个 .net core web 应用项目,并引用下面的nuget包

1
Serilog.AspNetCore

appsettings.json 中添加如下配置

1
2
3
4
5
6
7
8
9
"Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    }
}

Program.cs 中添加如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Program
{
    public static void Main(string[] args)
    {
        var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json", false, true).Build();
        Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration)
            .WriteTo.Console(new RenderedCompactJsonFormatter())
            .Enrich.FromLogContext()
            .CreateLogger();

        Log.Logger.Information("程序启动 {date}",DateTime.Now);
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        }).UseSerilog(dispose:true);
}

在控制器中引用 ILogger<>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;
    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public string Get()
    {
        _logger.LogInformation("hello serilog ");
        return "OK";
    }
}

这样我们打印的日志就都是以Json结构记录的了。

上面的日志记录的较多,我们可以先将无用的日志关闭,在配置文件中将 Microsoft.Hosting.Lifetime 的日志级别设置成 Fatal,这样我们的日志就清晰多了。

Serilog 日志框架还支持多种的 Sinks 接收器,可以通过引用 Serilog.Sinks.HttpSerilog.Sinks.Elasticsearch 将日志记录到 ELK 中。

本文由作者按照 CC BY 4.0 进行授权