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异常的详细信息。

作者:蒋金楠 
微信公众账号:大内老A

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

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

    日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger.Logger ...

  2. 如何在ABAP Netweaver和CloudFoundry里记录并查看日志

    Netweaver 要记录日志需要有一个checkpoint group,可以自行创建也可以使用标准的.这里我重用标准的group:DEMO_CHECKPOINT_GROUP. tcode SAAB, ...

  3. 【Azure 应用服务】App Service For Linux 部署Java Spring Boot应用后,查看日志文件时的疑惑

    编写Java Spring Boot应用,通过配置logging.path路径把日志输出在指定的文件夹中. 第一步:通过VS Code创建一个空的Spring Boot项目 第二步:在applicat ...

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

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

  5. Linux 从core信息中找到TLS信息

    背景 我们在查core问题时,有时候须要查看某个TLS变量的值.可是GDB没有提供直接的命令,或者我不知道.这篇文字的目的.就是想办法从core文件里找出某个线程存放TLS变量的内容. 依据 Linu ...

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

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

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

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

  8. Docker入门 .Net Core 使用Docker全程记录

    https://www.cnblogs.com/flame7/p/9210986.html Docker入门 第一课 --.Net Core 使用Docker全程记录   微服务架构无疑是当前最火热的 ...

  9. .net core中的那些常用的日志框架(NLog篇)

    前言 咱们上回讲到,.net core中内置的Logging日志框架的使用,以及浅显的讲解,接下来,给大家介绍一个第三方日志框架(NLog). NLog简介 NLog是适用于各种.NET平台(包括.N ...

随机推荐

  1. mvn使用记录

    1. mvn dependency:copy-dependencies 会导出到targed/dependency 下面 2. mvn dependency:copy-dependencies -Do ...

  2. 2015推荐的Android框架

    一.Guava Google的基于java1.6的类库集合的扩展项目,包括collections, caching, primitives support, concurrency libraries ...

  3. 【Lintcode】112.Remove Duplicates from Sorted List

    题目: Given a sorted linked list, delete all duplicates such that each element appear only once. Examp ...

  4. css 中visibility:hidden和display:none有什么区别呢

    <div style="width:100px;height:100px;background:red;visibility:hidden"></div>/ ...

  5. [原创]Devexpress XtraReports 系列 1 创建静态报表

    在各种管理系统中,报表是必不可少的.报表是数据最直观体现的方式之一 以前,屌丝我是微软的忠实粉丝,所以报表工具就是微软自带的RDLC... 不可否认的是RDLC的功能是非常强大的.能够满足绝大多数的要 ...

  6. CF-822B

    B. Crossword solving time limit per test 1 second memory limit per test 256 megabytes input standard ...

  7. Regex Golf 练习记录

    正则表达式的练习网站:https://alf.nu/RegexGolf 共17道题:只能说从第10题开始就很变态了,就是看看答案好了 .Warmup: foo .Anchors: k$ 或 ick$ ...

  8. Linux下使用doxygen+vim生成c语言源程序文档的方法

    1.安装 doxygen 有两种获得 doxygen 的方法.可以下载预编译的可执行文件,也可以从 SVN 存储库下载源代码并自己编译.清单 1 演示的是后一种方法. 清单 1. 安装和构建 doxy ...

  9. 第3章 编写ROS程序-3

    1.订阅者程序 我们继续使用 turtlesim 作为测试平台,订阅 turtlesim_node发布的/turtle1/pose 话题. 这一话题的消息描述了海龟的位姿 (位置和朝向) .尽管目前你 ...

  10. linux下 C编程改变输出字体颜色

    格式: echo "\033[字背景颜色;字体颜色m字符串\033[0m"例如:echo "\033[41;36m something here \033[0m" ...