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 ...
随机推荐
- php报错 ----> Call to undefined function imagecreatetruecolor()
刚才在写验证码的时候,发现报错,然后排查分析了一下,原来是所用的php版本(PHP/5.3.13)没有开启此扩展功能. 进入php.ini 找到extension=php_gd2.dll ,将其前面的 ...
- 破解SQLServer for Linux预览版的3.5GB内存限制 (RHEL篇)
微软发布了SQLServer for Linux,但是安装竟然需要3.5GB内存,这让大部分云主机用户都没办法尝试这个新东西 这篇我将讲解如何破解这个内存限制 要看关键的可以直接跳到第6步,只需要替换 ...
- redis成长之路——(一)
为什么使用redis Redis适合所有数据in-momory的场景,虽然Redis也提供持久化功能,但实际更多的是一个disk-backed的功能,跟传统意义上的持久化有比较大的差别,那么可能大家就 ...
- ASP.NET MVC学习之母版页和自定义控件的使用
一.母板页_Layout.cshtml类似于传统WebForm中的.master文件,起到页面整体框架重用的目地1.母板页代码预览 <!DOCTYPE html> <html> ...
- C++整数转字符串的一种方法
#include <sstream> //ostringstream, ostringstream::str() ostringstream stream; stream << ...
- CommandPattern
/** * 命令模式 * @author TMAC-J * 将调用者和接受者分离 * 可以将一组命令组合在一起,适合很多命令的时候 */ public class CommandPattern { i ...
- 【从零开始学BPM,Day3】自定义表单开发
[课程主题] 主题:5天,一起从零开始学习BPM [课程形式] 1.为期5天的短任务学习 2.每天观看一个视频,视频学习时间自由安排. [第三天课程] 1.课程概要 Step 1 软件下载:H3 BP ...
- iOS之延时执行(睡眠)的几种方法
1. 最直接的方法: [self performSelector:@selector(deleyMethod) withObject:nil afterDelay:1.0]; 此方式要求必须在主线程中 ...
- Android之解析XML
1.XML:可扩展标记语言. 可扩展标记语言是一种很像超文本标记语言的标记语言. 它的设计宗旨是传输数据,而不是显示数据. 它的标记没有被预定义.需要自行定义标签. 它被设计为具有自我描述性. 是W3 ...
- 浅谈SQL注入风险 - 一个Login拿下Server
前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...