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

   2023-02-09 学习力0
核心提示:日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性。我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger、LoggerFactory和LoggerProvider这三个核心对象组成。我们可以通过简单的配置实现对Logge

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

目录
一、 配置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>();
  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:         {
  22:         });
  23:     }
  24: }

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

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

由于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)。

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

日志范围携带的用于唯一标识当前请求的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应用中如何记录和查看日志

 
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • 使用WebClient自动填写并提交ASP.NET页面表单的源代码
    使用WebClient自动填写并提交ASP.NET页面表单的
    转自:http://www.cnblogs.com/anjou/archive/2007/03/07/667253.html 在.NET中通过程序填写和提交表单还是比较简单。比如,要提交一个如下图所示的登录表单:           填写和提交以上表单的代码如下:       // 要提交表单的URI字符串
    02-09
  • asp.net mvc多条件+分页查询解决方案
    


            
asp.net mvc多条件+分页查询解决方案
    asp.net mvc多条件+分页查询解决方案
    http://www.cnblogs.com/nickppa/p/3232535.html开发环境vs2010css:bootstrapjs:jquery    bootstrap paginator原先只是想做个mvc的分页,但是一般的数据展现都需要检索条件,而且是多个条件,所以就变成了MVC多条件+分页查询因为美工不是很好,所以用的是
    02-09
  • ASP.NET操作Cookies的问题(Bug or Not)
    以下存和取都是在不同的页面中,如果是在同一个页面也没必要用cookies了。 Test1: 给Cookies赋值: const string AAA="aaa"; Response.Cookies[AAA].Value = "111;222;333"; 取值: string value = Request.Cookies[AAA].Value; // value为111 Test2: 给Cooki
    02-09
  • Asp.Net Core 自定义验证属性
      很多时候,在模型上的验证需要自己定义一些特定于我们需求的验证属性。所以这一篇我们就来介绍一下怎么自定义验证属性。  我们来实现一个验证邮箱域名的自定义验证属性,当然,最重要的是需要定义一个继承自ValidationAttribute的类,然后在实现其IsVal
    02-09
  • Asp.Net 之 枚举类型的下拉列表绑定
    有这样一个学科枚举类型:/// 学科 /// /summary public enum Subject {None = 0,[Description("语文")]Chinese = 1,[Description("数学")]Mathematics = 2,[Description("英语")]English = 3,[Description("政治")]Politics = 4,[Description("物理&qu
    02-09
  • [ASP.NET笔记] 1.Web基础知识
         1:http协议:     2:web服务器:     3:静态网页的概念     4:动态网页的概念       http协议:http(hypertext transfer protocol) 即超文本传输协议,这个协议是在internet上进行信息传送的协议任何网页之间要相互沟通,必须要尊循
    02-09
  • ASP.NET邮件发送 .net 发送邮件
      今天做了个ASP.NET做发送邮件功能,发现QQ邮箱好奇怪,当你用QQ邮箱做服务器的时候什么邮件都发送不出去(QQ邮箱除外)。而且爆出这样的错误:"邮箱不可用。 服务器响应为: Error: content rejected.http://mail.qq.com/zh_CN/help/content/rejectedmail.ht
    02-09
  • 由ASP.NET Core根据路径下载文件异常引发的探究
    前言    最近在开发新的项目,使用的是ASP.NET Core6.0版本的框架。由于项目中存在文件下载功能,没有使用类似MinIO或OSS之类的分布式文件系统,而是下载本地文件,也就是根据本地文件路径进行下载。这其中遇到了一个问题,是关于如何提供文件路径的,通
    02-09
  • ASP.NET的运行原理与运行机制 ASP.NET的开发模式包括
    ASP.NET的运行原理与运行机制 ASP.NET的开发模
    在Asp.net4和4.5中,新增了WebPages Framework,编写页面代码使用了新的Razor语法,代码更加的简洁和符合Web标准,编写方式更接近于PHP和以前的Asp,和使用WebForms这种模仿Windows Form编程方式有了很大不同,不再有大量控件和控件生成的大量不够灵活的代码
    02-09
  • ASP.NET 后台接收前台POST过来的json数据方法
     ASP.NET前后台交互之JSON数据 https://www.cnblogs.com/ensleep/p/3319756.html
    02-09
点击排行