日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性。我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger、LoggerFactory和LoggerProvider这三个核心对象组成。我们可以通过简单的配置实现对LoggerFactory的定制,以及对LoggerProvider添加。 [ 本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、 配置LoggerFactory
二、以当前请求作为日志范围
三、记录异常日志

一、 配置LoggerFactory

我们在上面一节演示了一个展示ASP.NET Core默认注册服务的实例,细心的读者一定会看到显示的列表中就包含了针对LoggerFactory的服务。如果这个默认的LoggerFactory服务不能满足我们的需求,我们完全可以配置任何一个需要的LoggerFactory,针对LoggerFactory的设置可以直接调用WebHostBuilder的UseLoggerFactory方法来实现。

   1: public interface IWebHostBuilder

   2: {

   3:     IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory);

   4:     IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging);

   5:     ...

   6: }

不过针对日志的配置更多地还是体现在针对某种LoggerProvider的添加,而这可以通过调用WebHostBuilder的ConfigureLogging方法来完成。我们在上面演示的实例中就曾经采用如下的方式将一个ConsoleLoggerProvider注册到LoggerFactory之上,这样我们可以直接在宿主应用的扩展台上看到记录的日志信息。

   1: new WebHostBuilder()

   2:     .ConfigureLogging(factory=>factory.AddConsole())

   3:     ...

既然LoggerFactory已经作为一个服务进行了注册,那么我们完全按照依赖注入的来获取这个对象,并利用它创建对应的Logger对象来写日志。如果我们需要在一个定义的中间件中写入某种类型的日志,就可以按照如下的方式在Invoke方法中定义ILoggerFactory类型的参数注入这个LoggerFactory。

   1: public class FoobarMiddleware

   2: {

   3:     private RequestDelegate _next;

   4:  

   5:     public FoobarMiddleware(RequestDelegate next)

   6:     {

   7:         _next = next;

   8:     }

   9:  

  10:     public async Task Invoke(HttpContext context, ILoggerFactory loggerFactory)

  11:     {

  12:         ILogger<FoobarMiddleware> logger = loggerFactory.CreateLogger<FoobarMiddleware>();

  13:         logger.LogInformation("...");

  14:         await _next(context);

  15:     }

  16: }

不仅仅我们开发的应用或者中间件可以利用注册的LoggerFactory来创建进行日志记录的Logger对象,ASP.NET Core管道本身也会在处理请求过程中采用相同的方式记录一些日志。比如管道每次处理请求的开始和结束时候分别会写入两条Information等级的日志,我们现在就来通过一个简单的实例看看这两条日志信息具有怎样的内容。

   1: public class Program

   2: {

   3:     public static void Main()

   4:     {

   5:         new WebHostBuilder()

   6:             .ConfigureLogging(factory=>factory.AddConsole())

   7:             .UseKestrel()

   8:             .UseStartup<Startup>()                

   9:             .Build()

  10:             .Run();

  11:     }

  12: }

  13:  

  14: public class Startup

  15: {

  16:     public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)

  17:     {

  18:         app.Run(async context =>

  19:         {

  20:             loggerFactory.CreateLogger("App").LogInformation("Log entry for test...");

  21:             await context.Response.WriteAsync("Hello world!");

  22:         });

  23:     }

  24: }

如上所示的代码有两处与日志有关,第一个地方是调用WebHostBuilder的ConfigureLogging方法通过调用扩展方法AddConsole将一个ConsoleProvider添加到当前LoggerFactory之上,另一个地方就是启动类的Configure方法注册的中间件在执行过程中会利用注入的LoggerFactory创建一个Logger对象,我们利用后者写入了一条Information等级的日志。我们运行程序之后利用浏览器访问目标地址后,宿主控制台上会出现如下图所示的三条日志。除了第二条日志是由我们自己编写的代码写入的之外,其余两条都是ASP.NET Core框架自己写入的。第一条日志包含不仅仅包含请求的目标地址,还包括请求采用的协议(HTTP/1.1)和HTTP方法(GET),第三条则反映了整个请求处理过程所花的时间。

由于ASP.NET Core管道对请求的处理总是在一个由HttpApplication创建的执行上下文中进行,所以上下文的创建和回收释放可以视为 整个请求处理流程开始和结束的标识。对于上述的这两条分别在处理请求开始和结束时写入的日志,实际上是在HostingApplication的CreateContext和DisposeContext方法分别被调用的时候被记录下来的。之所以在结束的时候能够计算出整个请求处理过程所花的时间,是因为创建的这个上下文对象保存了开始处理请求的时间戳,该时间戳对应着Context结构的StartTimestamp属性。

   1: public class HostingApplication : IHttpApplication<HostingApplication.Context>

   2: {

   3:     public struct Context

   4:     {

   5:         public HttpContext      HttpContext { get; set; }

   6:         public IDisposable      Scope { get; set; }

   7:         public long          StartTimestamp { get; set; }

   8:     }

   9: }

二、以当前请求作为日志范围

我们知道日志系统有一个叫做“日志范围”的概念,它的目的在于为多次相关的日志记录创建一个上下文范围,并为这个范围提供一个唯一标识,这个标识会作为日志内容的一部分被写入。当我们在进行日志分析的时候,可以根据日志范围标识将一组原本独立的日志关联起来。这个概念对于Web应用尤为重要,因为很多情况下我们所做的日志分析都是针对某一个请求,这就要求我们必须明确地分辨出被记录下来的日志隶属于哪一个请求,只有这样才能将针对同一请求的所有日志提取出来做综合的分析以得出一个准确的结论。

从上个实例最终写入的三条日志来看,它们并不携带当前请求的标识信息。但是这不是ASP.NET Core的问题,而是我们在调用LoggerFactory的扩展方法AddConsole注册ConsoleLoggerProvider的时候并未显式开启针对日志范围的支持。为了让注册的ConsoleLoggerProvider创建的Logger能够支持日志范围,我们只需按照如下的方式在调用AddConsole方法的时候添加一个额外的参数(true)即可。

   1: new WebHostBuilder()

   2:     .ConfigureLogging(factory=>factory.AddConsole(true))

   3:     .UseKestrel()

   4:     .UseStartup<Startup>()                

   5:     .Build()

   6:     .Run();

我们再次请求应用并利用浏览器对目标地址发送两次请求,六条写入的日志将会以如下图所示的形式输出到控制台上。不同于上面的输出结果,本次输出的日志包含请求的ID(Request Id),在同一个请求下被记录下来的日志具有相同的ID。除了请求ID,记录的日志还携带了请求的路径(Request Path)。

日志范围携带的用于唯一标识当前请求的ID,同时也可以视为当前HttpContext的唯一标识,它对应着HttpContext的TranceIdentifier属性。对于DefaultHttpContext来说,针对这个属性的读写是借助一个名为HttpRequestIdentifierFeature的特性实现的,下面的代码提供了该对象对应的接口IHttpRequestIdentifierFeature和默认实现类HttpRequestIdentifierFeature的定义。

   1: public abstract class HttpContext

   2: {

   3:     //省略其他成员

   4:     public abstract string TraceIdentifier { get; set; }

   5: }

   6:  

   7: public interface IHttpRequestIdentifierFeature

   8: {

   9:     string TraceIdentifier { get; set; }

  10: }

  11:  

  12: public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature

  13: {

  14:     private string _id;

  15:     private static long _requestId = DateTime.UtcNow.Ticks;

  16:     private static unsafe string GenerateRequestId(long id);

  17:     public string TraceIdentifier

  18:     {

  19:         get

  20:         {

  21:             return _id??(id= GenerateRequestId(Interlocked.Increment(ref _requestId)));

  22:         }

  23:         set

  24:         {

  25:             this._id = value;

  26:         }

  27:     }

  28: }

HttpRequestIdentifierFeature生成TraceIdentifier的逻辑很简单。如上面的代码片断所示,它具有一个静态长整型字段_requestId,其初始值为当前时间戳。对于某个具体的HttpRequestIdentifierFeature对象来说,它的TraceIdentifier属性的默认值返回的是这个字段_requestId加1之后转换的字符串。具体的转换逻辑定义在GenerateRequestId方法中,它会采用相应的算法 将指定的整数转换一个长度为13的字符串。

和开始请求处理的时间戳一样,被创建出来的日志范围实际被保存在HostingApplication的上下文对象中,它对应着Context结构的Scope属性。当HostingApplication创建这个Context对象的时候,它会从当前HttpContext中提取出请求的ID和路径,创建出这个日志范围并赋值给这个属性。整个请求的处理其实就在这个日志范围中进行,请求处理结束,当前日志范文也被回收释放。

   1: public class HostingApplication : IHttpApplication<HostingApplication.Context>

   2: {

   3:     public struct Context

   4:     {

   5:         public HttpContext     HttpContext { get; set; }

   6:         public IDisposable     Scope { get; set; }

   7:         public long            StartTimestamp { get; set; }

   8:     }

   9: }

三、记录异常日志

由于ASP.NET Core在处理请求过程中导致的异常并不会导致应用终止,考虑到安全,抛出的异常的详细信息也不应该直接返回到客户端。所以在很多情况下我们根本感知不到应用发生了异常,即使感知到了,也不知道导致异常的根源在何处。在这种情况下,我们就需要使用记录的日志进行差错和纠错,因为ASP.NET Core在处理请求遇到的异常都会记录到日志中。

比如针对如下这段程序,毫无疑问它针对任何一个请求的处理都会抛出一个DivideByZeroException的异常。如果我们利用浏览器来访问站点地址,它只会得到一个状态为500的响应,并简单的提示服务端出现错误。对于宿主程序来说,我们根本就是感知不到任何异常发生。

   1: new WebHostBuilder()

   2:     .UseKestrel()

   3:     .Configure(app=>app.Run(async context=> {

   4:         int x = 1;

   5:         int y = 0;

   6:         await context.Response.WriteAsync((x / y).ToString());

   7:     }))

   8:     .Build()

   9: .Run();

在这种情况下我们可以通过查看日志得到异常的详细信息,不过在这之前必须为LoggerFactory注册相应的LoggerProvider。如果我们采用控制台应用作为宿主,在开发或者调试的时候最简单的莫过于按照如下的方式注册一个ConsoleLoggerProvider让日志可以直接写入宿主程序的控制台。

   1: new WebHostBuilder()

   2:     .ConfigureLogging(factory=>factory.AddConsole (true))

   3:     .UseKestrel()

   4:     .Configure(app=>app.Run(async context=> {

   5:         int x = 1;

   6:         int y = 0;

   7:         await context.Response.WriteAsync((x / y).ToString());

   8:     }))

   9:     .Build()

  10:     .Run();

一旦为LoggerFactory注册了这么一个ConsoleLoggerProvider,对于服务端出现的未处理的任何异常,我们都可以直接在宿主控制台上看到错误的详细信息,下图就是上面这个例子抛出的DivideByZeroException异常的详细信息。

ASP.NET Core应用中如何记录和查看日志的更多相关文章

  1. NET Core应用中如何记录和查看日志

    NET Core应用中如何记录和查看日志 日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日 ...

  2. ASP.NET Core 1.0 开发记录

    官方资料: https://github.com/dotnet/core https://docs.microsoft.com/en-us/aspnet/core https://docs.micro ...

  3. ASP.NET Core 集成测试中通过 Serilog 向控制台输出日志

    日志是程序员的雷达,不仅在生产环境中需要,在集成测试环境中也需要,可以在持续集成失败后帮助定位问题.与生产环境不同,在集成测试环境中使用控制台输出日志更方便,这样可以通过持续集成 runner 执行 ...

  4. 观看杨老师(杨旭)Asp.Net Core MVC入门教程记录

    观看杨老师(杨旭)Asp.Net Core MVC入门教程记录 ASP.NET Core MVC入门 Asp.Net Core启动和配置 Program类,Main方法 Startup类 依赖注入,I ...

  5. Serilog 是 ASP.NET Core 的一个插件,可以简化日志记录

    [翻译] ASP.NET Core 利用 Docker.ElasticSearch.Kibana 来记录日志 原文: Logging with ElasticSearch, Kibana, ASP.N ...

  6. 在 ASP.NET Core 项目中使用 MediatR 实现中介者模式

    一.前言  最近有在看 DDD 的相关资料以及微软的 eShopOnContainers 这个项目中基于 DDD 的架构设计,在 Ordering 这个示例服务中,可以看到各层之间的代码调用与我们之前 ...

  7. 在 ASP.NET Core 项目中使用 npm 管理你的前端组件包

    一.前言 在项目的前端开发中,对于绝大多数的小伙伴来说,当然,也包括我,不可避免的需要在项目中使用到一些第三方的组件包.这时,团队中的小伙伴是选择直接去组件的官网上下载,还是图省事直接在网上搜索,然后 ...

  8. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

    在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...

  9. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

随机推荐

  1. Taurus.MVC 2.0 开源发布:WebAPI开发教程

    背景: 有用户反映,Tausus.MVC 能写WebAPI么? 能! 教程呢? 嗯,木有! 好吧,刚好2.0出来,就带上WEBAPI教程了! 开源地址: https://github.com/cyq1 ...

  2. Git Bash的一些命令和配置

    查看git版本号: git --version 如果是第一次使用Git,你需要设置署名和邮箱: $ git config --global user.name "用户名" $ gi ...

  3. 使用CSS3实现一个3D相册

    CSS3系列我已经写过两篇文章,感兴趣的同学可以先看一下CSS3初体验之奇技淫巧,CSS3 3D立方体效果-transform也不过如此 第一篇主要列出了一些常用或经典的CSS3技巧和方法:第二篇是一 ...

  4. 我为NET狂官方面试题-数据库篇

    求结果:select "1"? 查找包含"objs"的表?查找包含"o"的数据库? 求今天距离2002年有多少年,多少天? 请用一句SQL获 ...

  5. Centos 下 mysql root 密码重置

    重置mysql密码的方法有很多,官网也提供了很方便的快捷操作办法,可参考资料 resetting permissions .本文重置密码的具体步骤如下: 一.停止MySQL(如果处于运行状态) #se ...

  6. IE的F12开发人员工具不显示问题

    按下F12之后,开发人员工具在桌面上看不到,但是任务栏里有显示.将鼠标放在任务栏的开发人员工具上,出现一片透明的区域,选中之后却出不来.将鼠标移动到开发人员工具的缩略图上,右键-最大化,工具就全屏出现 ...

  7. .net windows Kafka 安装与使用入门(入门笔记)

    完整解决方案请参考: Setting Up and Running Apache Kafka on Windows OS   在环境搭建过程中遇到两个问题,在这里先列出来,以方便查询: 1. \Jav ...

  8. PHP中PDO事务的使用方法

    事务 (Transaction) 是操作数据库中很重要的一个功能, 它可以让你预定一条, 或者一系列 SQL 语句, 然后一起执行. 在执行的过程中, 如果其中的某条执行失败, 可以回滚所有已更改的操 ...

  9. ASP.NET SignaiR 实现消息的即时推送,并使用Push.js实现通知

    一.使用背景 1. SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...

  10. 后缀数组的倍增算法(Prefix Doubling)

    后缀数组的倍增算法(Prefix Doubling) 文本内容除特殊注明外,均在知识共享署名-非商业性使用-相同方式共享 3.0协议下提供,附加条款亦可能应用. 最近在自学习BWT算法(Burrows ...