在 ASP.NET 4.X 中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了。而在 ASP.Net Core 中内置了日志系统,并提供了一个统一的日志接口,ASP.Net Core 系统以及其它第三方类库等都使用这个日志接口来记录日志,而不关注日志的具体实现,这样便可以在我们的应用程序中进行统一的配置,并能很好的与第三方日志框架集成。
ASP.NET Core 全部使用依赖注入,更好的规范我们的代码。想要使用日志系统,首先要进行注册和配置:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddLogging(builder =>
- {
- builder
- .AddConfiguration(loggingConfiguration.GetSection("Logging"))
- .AddFilter("Microsoft", LogLevel.Warning)
- .AddConsole();
- });
- }
如上,通过
,将日志系统注册到了 DI 系统中,而
- AddLogging
是对日志系统的全局配置,
- AddConfiguration
则是对日志过滤器的一些配置,最后
- AddFilter
添加了一个 Console 的日志提供者(将日志输出到控制台)。
- AddConsole
在我们需要记录日志的时候,只需要通过构造函数注入
就可以了:
- ILogger<T>
- public class TodoController : Controller
- {
- private readonly ITodoRepository _todoRepository;
- private readonly ILogger _logger;
- public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
- {
- _todoRepository = todoRepository;
- _logger = logger;
- }
- [HttpGet]
- public IActionResult GetById(string id)
- {
- _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
- var item = _todoRepository.Find(id);
- if (item == null)
- {
- _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
- return NotFound();
- }
- return new ObjectResult(item);
- }
- }
中的
- ILogger<T>
表示日记的类别,在我们查看日志时,非常有用,在本文后面会讲。
- T
使用上面的示例代码,当我们通过控制台来运行时,访问
将会看到如下的日志输出:
- http://localhost:5000/api/todo/0
- info: Microsoft.AspNetCore.Hosting.Internal.webHost[1]
- Request starting HTTP/1.1 GET http://localhost:5000/api/todo/invalidid
- info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
- Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (invalidid) - ModelState is Valid
- info: TodoApi.Controllers.TodoController[1002]
- Getting item invalidid
- warn: TodoApi.Controllers.TodoController[4000]
- GetById(invalidid) NOT FOUND
- info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
- Executing HttpStatusCodeResult, setting HTTP status code 404
- info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
- Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 243.2636ms
- info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
- Request finished in 628.9188ms 404
如果我们访问
,将会看到:
- http://localhost:55070/api/todo/0
- Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:55070/api/todo/invalidid
- Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (invalidid) - ModelState is Valid
- TodoApi.Controllers.TodoController:Information: Getting item invalidid
- TodoApi.Controllers.TodoController:Warning: GetById(invalidid) NOT FOUND
- Microsoft.AspNetCore.Mvc.StatusCodeResult:Information: Executing HttpStatusCodeResult, setting HTTP status code 404
- Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 12.5003ms
- Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 19.0913ms 404
通过这个示例,可以看到我们记录到了 ASP.NET Core 框架自身的日志,这也是统一的日志框架才能实现的功能。
我们创建的每一个日志器都指定了一个类别。它可以是任意的字符串,但是约定使用写入类的完整限定名,如:"TodoApi.Controllers.TodoController"。如果要显式的指定日志的种类,则可以使用
中的
- ILoggerFactory
方法:
- CreateLogger
- public class TodoController : Controller
- {
- private readonly ILogger _logger;
- public TodoController(ILoggerFactory logger)
- {
- _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
- }
- }
不过,大多数时候,我们还是使用
,更加方便:
- ILogger<T>
- public class TodoController : Controller
- {
- private readonly ILogger _logger;
- public TodoController(ILogger<TodoController> logger)
- {
- _logger = logger;
- }
- }
这等效于使用
类型的完整限定名来调用
- T
方法。
- CreateLogger
在我们记录日志时,需要指定日志的级别,这对我们过滤日志非常有用,比如在测试环境中,我们希望提供非常的详细的日志信息,包括一些敏感信息等,但是在生产环境中,我们希望只记录严重的错误,这时候只需要简单的通过
对日志的过滤级别配置一下就行了。
- AddFilter
ASP.NET Core Logging 系统提供了六个日志级别,通过增加重要性或严重程度排序如下:
每次写日志的时候,我们可以指定一个 event ID:
- public class LoggingEvents
- {
- public const int GET_ITEM = 1002;
- public const int GET_ITEM_NOTFOUND = 4000;
- }
- public IActionResult GetById(string id)
- {
- _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
- var item = _todoRepository.Find(id);
- if (item == null)
- {
- _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
- return NotFound();
- }
- return new ObjectResult(item);
- }
event ID 是一个整数,它可以将一组日志事件关联到一起。与日志类别类似,但是更加细化。而它的输出取决于日志提供者,Console 提供者输出格式如下,在日志类别后面,并用一对中括号包裹着:
- info: TodoApi.Controllers.TodoController[1002]
- Getting item invalidid
- warn: TodoApi.Controllers.TodoController[4000]
- GetById(invalidid) NOT FOUND
每次记录日志时,都会提供一条文本消息,而在这个消息字符串中,我们可以使用命名占位符:
- public IActionResult GetById(string id)
- {
- _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
- var item = _todoRepository.Find(id);
- if (item == null)
- {
- _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
- return NotFound();
- }
- return new ObjectResult(item);
- }
但是占位符的顺序决定了使用哪个参数,而不是它的名字,如下示例:
- string p1 = "parm1";
- string p2 = "parm2";
- _logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);
输出结果为:
- Parameter values: parm1,
- parm2
那这样做有什么意义呢?
日志框架使用这种消息格式化方式,使日志提供者能够实现 语义化日志,也称结构化日志。因为参数本身被传递到日志系统中,而不仅仅是格式化的字符串,因此日志提供者可以将参数的值作为字段存储单独的存储。比如:如果使用 Azure Table Storage,我们可以使用如下方法来记录日志:
- _logger.LogInformation("Getting item {ID} at {RequestTime}", id, DateTime.Now);
每一个 Azure Table 都可以有
和
- ID
属性,这将简化对日志数据的查询,你可以查找指定
- RequestTime
范围内的所有日志,而不必花费解析文本消息的开销。
- RequestTime
过滤器可以让你根据日志的级别和类别来选择是输出,还是忽略。我们可以为不同的日志提供者指定不同的过滤器,如下代码所示,让 Console 提供者忽略低于
级别的日志,而 Debug 提供者则忽略
- warning
类别的日志。
- TodoApi
- public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
- {
- loggerFactory
- .AddConsole(LogLevel.Warning)
- .AddDebug((category, logLevel) => (category.Contains("TodoApi") && logLevel >= LogLevel.Trace));
- }
而我们还可以指定全局过滤器,作用于所有的日志提供者,如下示例,我们对于以 "Microsoft" 和 "System" 开头的日志类别忽略掉低于
级别的日志:
- Warning
- public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
- {
- loggerFactory
- .AddFilter("Microsoft", LogLevel.Warning)
- .AddFilter("System", LogLevel.Warning)
- .AddFilter("SampleApp.Program", LogLevel.Debug)
- .AddDebug();
- }
我们可以将一组逻辑操作放在一个有序的 Scope 中,将 Scope 的标识附加到范围内的所有日志中。例如,我们可以在处理事务的时候,使事务内的每一个操作日志都包含这个事务的 ID。
使用
方法创建一个 Scope,并返回一个
- ILgger.BeginScope<TState>
类型,当我们
- IDisposable
的时候,这个 Scope 也就结束了,非常适合于使用
- Dispose
的方式:
- using
- public IActionResult GetById(string id)
- {
- TodoItem item;
- using (_logger.BeginScope("Message attached to logs created in the using block"))
- {
- _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
- item = _todoRepository.Find(id);
- if (item == null)
- {
- _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
- return NotFound();
- }
- }
- return new ObjectResult(item);
- }
每一个日志将包括 Scope 的信息:
- info: TodoApi.Controllers.TodoController[1002]
- => RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
- Getting item 0
- warn: TodoApi.Controllers.TodoController[4000]
- => RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
- GetById(0) NOT FOUND
ASP.NET Core 提供了统一的日志框架,能方便地通过
类进行配置,灵活的集成第三方日志框架,并使用依赖注入的方式在应用程序中使用。本文整体的概述了一下 Logging 系统,在下一章中,会来分析一下 Logging 中配置的源码。
- Startup
参考微软官方文档:Introduction to Logging in ASP.NET Core
来源: http://www.cnblogs.com/RainingNight/p/asp-net-core-logging-introduction.html