在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();
});
}

如上,通过 AddLogging ,将日志系统注册到了 DI 系统中,而 AddConfiguration 是对日志系统的全局配置, AddFilter 则是对日志过滤器的一些配置,最后 AddConsole 添加了一个 Console 的日志提供者(将日志输出到控制台)。

记录日志

在我们需要记录日志的时候,只需要通过构造函数注入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 系统提供了六个日志级别,通过增加重要性或严重程度排序如下:

  • Trace 用于记录最详细的日志消息,通常仅用于开发阶段调试问题。这些消息可能包含敏感的应用程序数据,因此不应该用于生产环境。默认应禁用。

  • Debug 这种消息在开发阶段短期内比较有用。它们包含一些可能会对调试有所助益、但没有长期价值的信息。默认情况下这是最详细的日志。

  • Information 这种消息被用于跟踪应用程序的一般流程。与 Verbose 级别的消息相反,这些日志应该有一定的长期价值。

  • Warning 当应用程序出现错误或其它不会导致程序停止的流程异常或意外事件时使用警告级别,以供日后调查。在一个通用的地方处理警告级别的异常。

  • Error 当应用程序由于某些故障停止工作则需要记录错误日志。这些消息应该指明当前活动或操作(比如当前的 HTTP 请求),而不是应用程序范围的故障。

  • Critical 当应用程序或系统崩溃、遇到灾难性故障,需要立即被关注时,应当记录关键级别的日志。如数据丢失、磁盘空间不够等。

日志事件ID

每次写日志的时候,我们可以指定一个 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 都可以有 IDRequestTime 属性,这将简化对日志数据的查询,你可以查找指定 RequestTime 范围内的所有日志,而不必花费解析文本消息的开销。

过滤器

过滤器可以让你根据日志的级别和类别来选择是输出,还是忽略。我们可以为不同的日志提供者指定不同的过滤器,如下代码所示,让 Console 提供者忽略低于 warning 级别的日志,而 Debug 提供者则忽略 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。

使用ILgger.BeginScope<TState> 方法创建一个 Scope,并返回一个 IDisposable 类型,当我们 Dispose的时候,这个 Scope 也就结束了,非常适合于使用 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 提供了统一的日志框架,能方便地通过 Startup 类进行配置,灵活的集成第三方日志框架,并使用依赖注入的方式在应用程序中使用。本文整体的概述了一下 Logging 系统,在下一章中,会来分析一下 Logging 中配置的源码。

参考微软官方文档:Introduction to Logging in ASP.NET Core

ASP.NET Core 源码学习之 Logging[1]:Introduction的更多相关文章

  1. 【ASP.NET Core 】ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

  2. ASP.NET Core 源码学习之 Logging[2]:Configure

    在上一章中,我们对 ASP.NET Logging 系统做了一个整体的介绍,而在本章中则开始从最基本的配置开始,逐步深入到源码当中去. 默认配置 在 ASP.NET Core 2.0 中,对默认配置做 ...

  3. ASP.NET Core 源码学习之 Logging[3]:Logger

    上一章,我们介绍了日志的配置,在熟悉了配置之后,自然是要了解一下在应用程序中如何使用,而本章则从最基本的使用开始,逐步去了解去源码. LoggerFactory 我们可以在构造函数中注入 ILogge ...

  4. ASP.NET Core 源码学习之 Logging[4]:FileProvider

    前面几章介绍了 ASP.NET Core Logging 系统的配置和使用,而对于 Provider ,微软也提供了 Console, Debug, EventSource, TraceSource ...

  5. ASP.NET Core源码学习(一)Hosting

    ASP.NET Core源码的学习,我们从Hosting开始, Hosting的GitHub地址为:https://github.com/aspnet/Hosting.git 朋友们可以从以上链接克隆 ...

  6. ASP.NET Core 源码学习之 Options[1]:Configure

    配置的本质就是字符串的键值对,但是对于面向对象语言来说,能使用强类型的配置是何等的爽哉! 目录 ASP.NET Core 配置系统 强类型的 Options Configure 方法 源码解析 ASP ...

  7. ASP.NET Core 源码学习之 Options[4]:IOptionsMonitor

    前面我们讲到 IOptions 和 IOptionsSnapshot,他们两个最大的区别便是前者注册的是单例模式,后者注册的是 Scope 模式.而 IOptionsMonitor 则要求配置源必须是 ...

  8. asp.net core源码飘香:Logging组件

    简介: 作为基础组件,日志组件被其他组件和中间件所使用,它提供了一个统一的编程模型,即不需要知道日志最终记录到哪里去,只需要调用它即可. 使用方法很简单,通过依赖注入ILogFactory(Creat ...

  9. ASP.NET Core 源码学习之 Options[2]:IOptions

    在上一篇中,介绍了一下Options的注册,而使用时只需要注入IOption即可: public ValuesController(IOptions<MyOptions> options) ...

随机推荐

  1. 关于String的问题

    String是在代码中非常常见的一种数据类型.它能直接像基本类型一样直接赋值(String str = "test"),也能像引用类型一样创建一个实例(String str = n ...

  2. Mysql 忘记root密码后修改root密码

    1.修改my.cnf: 在mysqld进程配置文件中添加skip-grant-tables,添加完成后记住保存. 2.重新启动MYSQL数据库: service mysqld restart 2.修改 ...

  3. JDFS:一款分布式文件管理实用程序第一篇(线程池、epoll、上传、下载)

    一 前言 截止目前,笔者在博客园上面已经发表了3篇关于网络下载的文章,这三篇博客实现了基于socket的http多线程远程断点下载实用程序.笔者打算在此基础上开发出一款分布式文件管理实用程序,截止目前 ...

  4. 【Javascript语言精粹】笔记摘要

    现在大部分编译语言中都流行要求强类型.其原理在于强类型允许编译器在编译时检测错误.我们能越早检测和修复错误,付出的代价越小.Javascript是一门弱类型的语言,所以Javascript编译器不能检 ...

  5. opencv探索之路(十二):感兴趣区域ROI和logo添加技术

    在图像处理领域,有一个非常重要的名词ROI. 什么是ROI? 它的英文全称是Region Of Interest,对应的中文解释就是感兴趣区域. 感兴趣区域,就是我们从图像中选择一个图像区域,这个区域 ...

  6. Linux环境下配置JDK,java环境

    1.查看Linux自带的JDK是否已安装 # java -version 2. 查看JDK信息 # rpm -qa | grep java 显示: java-x.x.x-gcj-compat-x.x. ...

  7. PATH menu

    先上效果图 主界面布局文件 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" ...

  8. mysql之 日志体系(错误日志、查询日志、二进制日志、事务日志、中继日志)

    一. mysql错误日志:错误日志记录的事件:a).服务器启动关闭过程中的信息b).服务器运行过程中的错误信息c).事件调试器运行一个事件时间生的信息d).在从服务器上启动从服务器进程时产生的信息lo ...

  9. 六一儿童节PHP宝宝又被围剿了,迅速围观!

    原文链接 据说这是2020年某国际多语言幼儿园的官方讨论会现场外泄图片,程序员宝宝们,你们同意会议结果吗? 据说这是2020年某国际多语言幼儿园的官方讨论会现场外泄图片,程序员宝宝们,你们同意会议结果 ...

  10. Zepto源码分析-form模块

    源码注释 // Zepto.js // (c) 2010-2015 Thomas Fuchs // Zepto.js may be freely distributed under the MIT l ...