ASP.NET Core应用中如何记录和查看日志
日志记录不仅对于我们开发的应用,还是对于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应用中如何记录和查看日志的更多相关文章
- NET Core应用中如何记录和查看日志
NET Core应用中如何记录和查看日志 日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日 ...
- ASP.NET Core 1.0 开发记录
官方资料: https://github.com/dotnet/core https://docs.microsoft.com/en-us/aspnet/core https://docs.micro ...
- ASP.NET Core 集成测试中通过 Serilog 向控制台输出日志
日志是程序员的雷达,不仅在生产环境中需要,在集成测试环境中也需要,可以在持续集成失败后帮助定位问题.与生产环境不同,在集成测试环境中使用控制台输出日志更方便,这样可以通过持续集成 runner 执行 ...
- 观看杨老师(杨旭)Asp.Net Core MVC入门教程记录
观看杨老师(杨旭)Asp.Net Core MVC入门教程记录 ASP.NET Core MVC入门 Asp.Net Core启动和配置 Program类,Main方法 Startup类 依赖注入,I ...
- Serilog 是 ASP.NET Core 的一个插件,可以简化日志记录
[翻译] ASP.NET Core 利用 Docker.ElasticSearch.Kibana 来记录日志 原文: Logging with ElasticSearch, Kibana, ASP.N ...
- 在 ASP.NET Core 项目中使用 MediatR 实现中介者模式
一.前言 最近有在看 DDD 的相关资料以及微软的 eShopOnContainers 这个项目中基于 DDD 的架构设计,在 Ordering 这个示例服务中,可以看到各层之间的代码调用与我们之前 ...
- 在 ASP.NET Core 项目中使用 npm 管理你的前端组件包
一.前言 在项目的前端开发中,对于绝大多数的小伙伴来说,当然,也包括我,不可避免的需要在项目中使用到一些第三方的组件包.这时,团队中的小伙伴是选择直接去组件的官网上下载,还是图省事直接在网上搜索,然后 ...
- 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?
在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...
- ASP.NET Core MVC 中的 [Controller] 和 [NonController]
前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...
随机推荐
- Android Studio配置 AndroidAnnotations——Hi_博客 Android App 开发笔记
以前用Eclicps 用习惯了现在 想学学 用Android Studio 两天的钻研终于 在我电脑上装了一个Android Studio 并完成了AndroidAnnotations 的配置. An ...
- 实现代理设置proxy
用户在哪些情况下是需要设置网络代理呢? 1. 内网上不了外网,需要连接能上外网的内网电脑做代理,就能上外网:多个电脑共享上外网,就要用代理: 2.有些网页被封,通过国外的代理就能看到这被封的网站:3. ...
- SQL Server-聚焦查询计划Stream Aggregate VS Hash Match Aggregate(二十)
前言 之前系列中在查询计划中一直出现Stream Aggregate,当时也只是做了基本了解,对于查询计划中出现的操作,我们都需要去详细研究下,只有这样才能对查询计划执行的每一步操作都了如指掌,所以才 ...
- SELECT INTO 和 INSERT INTO SELECT 两种表复制语句
Insert是T-sql中常用语句,Insert INTO table(field1,field2,...) values(value1,value2,...)这种形式的在应用程序开发中必不可少.但我 ...
- mybatis_映射查询
一.一对一映射查询: 第一种方式(手动映射):借助resultType属性,定义专门的pojo类作为输出类型,其中该po类中封装了查询结果集中所有的字段.此方法较为简单,企业中使用普遍. <!- ...
- 如何优化coding
如何优化coding 前言 最近一直在做修改bug工作,修改bug花费时间最多的不是如何解决问题而是怎样快速读懂代码.如果代码写的好的,不用debug就可以一眼看出来哪里出了问题.实际上,我都要deb ...
- IL异常处理
异常处理在程序中也算是比较重要的一部分了,IL异常处理在C#里面实现会用到一些新的方法 1.BeginExceptionBlock:异常块代码开始,相当于try,但是感觉又不太像 2.EndExcep ...
- GJM : C#设计模式(1)——单例模式
感谢您的阅读.喜欢的.有用的就请大哥大嫂们高抬贵手"推荐一下"吧!你的精神支持是博主强大的写作动力以及转载收藏动力.欢迎转载! 版权声明:本文原创发表于 [请点击连接前往] ,未经 ...
- 拦截UIViewController的popViewController事件
实现拦截UIViewController的pop操作有两种方式: 自定义实现返回按钮,即设置UIBarButtonItem来实现自定义的返回操作. 创建UINavigatonController的Ca ...
- jQuery radio的取值与赋值
取值: $("input[name='radioName']:checked").val(); 赋值: $("input[name='radioName'][value= ...