我们都知道,在netcore中存在三种生命周期,分别是Singleton、Scoped、Transient。三种生命周期分别适用于不同的场景,相应的,如果我们要对各种各样的请求进行日志记录,是否也可以考虑日志的作用域呢?除了netcore的日志框架外,是否有第三方的类似log4net之类的可多样化记录日志的方式呢?你可以先思考一下,我们下面可以一块来看一下。
日志作用域:解决不同请求之间的日志干扰
作用域场景
- 一个事务包含多条操作时
- 复杂流程的日志关联时
- 调用链追踪与请求处理过程对应时
下面,我们通过示例来看一下实际效果
首先是settings.json配置文件
{ "logging": { "loglevel": { }, "Console": { "IncludeScopes": true, "loglevel": { "default": "information", "_12_LoggingScopeDemo.Program": "Trace" } } } }
我们通过该配置来实例化日志记录器
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder(); configurationBuilder.AddJsonFile("settings.json"); IConfigurationRoot configurationRoot = configurationBuilder.Build(); IServiceCollection serviceDescriptors = new ServiceCollection(); serviceDescriptors.AddLogging(configure => { configure.AddConfiguration(configurationRoot.GetSection("logging")); configure.AddConsole(); configure.AddDebug(); }); serviceDescriptors.AddSingleton<IConfiguration>(p => configurationRoot); IServiceProvider serviceProvider = serviceDescriptors.BuildServiceProvider(); ILogger logger = serviceProvider.GetService<ILogger<Program>>();
然后用beginscope方法来创建日志作用域
using (logger.BeginScope("ScopedId:{scopedId}", Guid.NewGuid())) { logger.LogDebug("this is debug"); logger.LogInformation("this is information"); logger.LogWarning("this is warning"); } Console.WriteLine("====================");
当settings.json中的配置"IncludeScopes"为true时,即可开启日志作用域,此时运行程序结果如下
dbug: _12_LoggingScopeDemo.Program[0] => ScopedId:3aac90bf-ece2-4c44-9ef1-e50a789e32b2 this is debug info: _12_LoggingScopeDemo.Program[0] => ScopedId:3aac90bf-ece2-4c44-9ef1-e50a789e32b2 this is information warn: _12_LoggingScopeDemo.Program[0] => ScopedId:3aac90bf-ece2-4c44-9ef1-e50a789e32b2 this is warning
这里,我们可以看到,程序打印出了本次的ScopedId,当IncludeScoped为false时,是不会打印该Id的,如果用while来多次输出,那么每次的id将是不同的,但一次请求内的id相同。
dbug: _12_LoggingScopeDemo.Program[0] => ScopedId:3aac90bf-ece2-4c44-9ef1-e50a789e32b2 this is debug info: _12_LoggingScopeDemo.Program[0] => ScopedId:3aac90bf-ece2-4c44-9ef1-e50a789e32b2 this is information warn: _12_LoggingScopeDemo.Program[0] => ScopedId:3aac90bf-ece2-4c44-9ef1-e50a789e32b2 this is warning ==================== dbug: _12_LoggingScopeDemo.Program[0] => ScopedId:cebf0aad-9b9b-4efe-bbc6-dda375bb818a this is debug info: _12_LoggingScopeDemo.Program[0] => ScopedId:cebf0aad-9b9b-4efe-bbc6-dda375bb818a this is information warn: _12_LoggingScopeDemo.Program[0] => ScopedId:cebf0aad-9b9b-4efe-bbc6-dda375bb818a this is warning
我们再来看一下在aspnetcore中如何使用日志作用域,还是相同的配置文件,这次使用默认的aspnetcore程序来演示,只不过在默认Get方法里加以下语句
_logger.LogInformation("开始Get了"); await Task.Delay(1000); _logger.LogInformation("Get睡醒了");
运行后,输出结果如下
info: LoggingDemo.Controllers.WeatherForecastController[0] => RequestPath:/weatherforecast RequestId:0HM1P8F34S8NM:00000001, SpanId:|64c643bc-4a794f2ef9a39a1a., TraceId:64c643bc-4a794f2ef9a39a1a, ParentId: => LoggingDemo.Controllers.WeatherForecastController.Get (LoggingDemo) 开始Get了 info: LoggingDemo.Controllers.WeatherForecastController[0] => RequestPath:/weatherforecast RequestId:0HM1P8F34S8NM:00000001, SpanId:|64c643bc-4a794f2ef9a39a1a., TraceId:64c643bc-4a794f2ef9a39a1a, ParentId: => LoggingDemo.Controllers.WeatherForecastController.Get (LoggingDemo) Get睡醒了
从结果可以看出,在日志打印时,通过作用域打印了本次请求相关的路径、Id、TraceId等信息,这样的话,我们就可以在不同的请求内进行日志打印,从而通过这些这些信息来完成调用链追踪等。
这样,我们就可以在以上提到的场景使用日志作用域来更好的记录日志。
结构化日志组件Serilog:记录对查询分析友好的日志
说完日志作用域,我们再来看一下结构化日志组件Serilog,那为什么要使用结构化日志呢?
结构化日志的好处
- 易于检索
- 易于分析检索
那结构化日志能应用于何种场景呢?
- 实现日志告警系统
- 实现上下文追踪
- 实现与追踪系统的集成
使用Serilog时,我们要先添加引用包
接着,像使用Autofac框架时一样,我们要先对框架进行注册
Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .UseSerilog(dispose: true);
Serilog通过Log.Logger来定义日志对象,该实体可选择从配置文件来生成相应配置,并通过相应的方法设置日志等级、输出方式及输出间隔等。
我们可以先来定义一个配置
public static IConfiguration Configuration { get; } = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")??"Production"}.json", optional: false, reloadOnChange: true) .AddEnvironmentVariables() .Build();
通过以上配置来生成日志对象
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration) .MinimumLevel.Debug()//设置最低等级level .Enrich.FromLogContext() .WriteTo.Console(new RenderedCompactJsonFormatter()) .WriteTo.File(formatter: new CompactJsonFormatter(), "logs\\myapp.txt", rollingInterval: RollingInterval.Day) //.WriteTo.Debug() .CreateLogger();
然后在Get方法里添加一句日志输出,此时日志对象会自动被Serilog接管
_logger.LogInformation("Get 随机创建数据");
运行程序,会发现打印如下消息
{"@t":"2020-08-05T14:00:52.9976857Z","@m":"Starting web host","@i":"4872fd06"} {"@t":"2020-08-05T14:00:55.5823856Z","@m":"Get 随机创建数据","@i":"6936e72c","SourceContext":"LoggingSerilogDemo.Controllers.WeatherForecastController","ActionId":"e012839b-6d3e-4cde-b066-6a5d2a4bb330","ActionName":"LoggingSerilogDemo.Controllers.WeatherForecastController.Get (LoggingSerilogDemo)","RequestId":"0HM1P8VS8PS5U:00000001","RequestPath":"/weatherforecast","SpanId":"|91406155-479d2c07b1fc69aa.","TraceId":"91406155-479d2c07b1fc69aa","ParentId":""}
由于我们在创建日志对象的时候选择了输出到控制台以及文件,因此,以上日志内容除了输出到控制台外,还会在配置的路径下生成一个文件夹,文件夹里的日志文件是根据配置的时间间隔来生成日志文件,可以根据需要选择时分或日月等为间隔。这样可方便的对日志进行记录并可在需要时很方便的进行检索,另外,像刚刚输出的一样,日志框架会打印出本次请求相关的上下文对象以便对请求进行追踪等。
以上便是本节的所有内容,而且到这里,日志部分也要告一段落了,对于日志部分,文章只是提到了一些基本操作,实际上还有很多其他的操作,特别是在结构化日志Serilog这里,有很多的操作方法以及日志实例化方式,大家可以通过实践慢慢摸索。
从下一节开始,我们将开始中间件的学习。
详细代码请参阅
https://github.com/IronMarmot/Samples/tree/master/CoreSamples