1. 程式人生 > >ASP.NET Core 原始碼學習之 Logging[1]:Introduction

ASP.NET Core 原始碼學習之 Logging[1]:Introduction

在ASP.NET 4.X中,我們通常使用 log4net, NLog 等來記錄日誌,但是當我們引用的一些第三方類庫使用不同的日誌框架時,就比較混亂了。而在 ASP.Net Core 中內建了日誌系統,並提供了一個統一的日誌介面,ASP.Net Core 系統以及其它第三方類庫等都使用這個日誌介面來記錄日誌,而不關注日誌的具體實現,這樣便可以在我們的應用程式中進行統一的配置,並能很好的與第三方日誌框架整合。

註冊日誌服務

ASP.NET Core 全部使用依賴注入,更好的規範我們的程式碼。想要使用日誌系統,首先要進行註冊和配置:

public void ConfigureServices(IServiceCollection services)  
{
    services.AddLogging(builder =>
    {
        builder
            .AddConfiguration(loggingConfiguration.GetSection("Logging"))
            .AddFilter("Microsoft", LogLevel.Warning)
            .AddConsole();
    });
}

如上,通過 AddLogging ,將日誌系統註冊到了 DI 系統中,而 AddConfiguration 是對日誌系統的全域性配置, AddFilter 則是對日誌過濾器的一些配置,最後 AddConsole 添加了一個 Console 的日誌提供者(將日誌輸出到控制檯)。

記錄日誌

在我們需要記錄日誌的時候,只需要通過建構函式注入ILogger<T>就可以了:

public class TodoController : Controller
{
    private readonly ITodoRepository _todoRepository;
    private readonly ILogger _logger;

    public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
    {
        _todoRepository = todoRepository;
        _logger = logger;
    }

    [HttpGet]
    public IActionResult GetById(string id)
    {
        _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
        var item = _todoRepository.Find(id);
        if (item == null)
        {
            _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
            return NotFound();
        }
        return new ObjectResult(item);
    }  
}

ILogger<T> 中的 T 表示日記的類別,在我們檢視日誌時,非常有用,在本文後面會講。

日誌輸出示例

使用上面的示例程式碼,當我們通過控制檯來執行時,訪問 http://localhost:5000/api/todo/0 將會看到如下的日誌輸出:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
      Request starting HTTP/1.1 GET http://localhost:5000/api/todo/invalidid
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
      Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (invalidid) - ModelState is Valid
info: TodoApi.Controllers.TodoController[1002]
      Getting item invalidid
warn: TodoApi.Controllers.TodoController[4000]
      GetById(invalidid) NOT FOUND
info: Microsoft.AspNetCore.Mvc.StatusCodeResult[1]
      Executing HttpStatusCodeResult, setting HTTP status code 404
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
      Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 243.2636ms
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
      Request finished in 628.9188ms 404

如果我們訪問 http://localhost:55070/api/todo/0 ,將會看到:

Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/1.1 GET http://localhost:55070/api/todo/invalidid
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method TodoApi.Controllers.TodoController.GetById (TodoApi) with arguments (invalidid) - ModelState is Valid
TodoApi.Controllers.TodoController:Information: Getting item invalidid
TodoApi.Controllers.TodoController:Warning: GetById(invalidid) NOT FOUND
Microsoft.AspNetCore.Mvc.StatusCodeResult:Information: Executing HttpStatusCodeResult, setting HTTP status code 404
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action TodoApi.Controllers.TodoController.GetById (TodoApi) in 12.5003ms
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 19.0913ms 404

通過這個示例,可以看到我們記錄到了 ASP.NET Core 框架自身的日誌,這也是統一的日誌框架才能實現的功能。

日誌類別

我們建立的每一個日誌器都指定了一個類別。它可以是任意的字串,但是約定使用寫入類的完整限定名,如:“TodoApi.Controllers.TodoController”。如果要顯式的指定日誌的種類,則可以使用 ILoggerFactory 中的 CreateLogger 方法:

public class TodoController : Controller
{
    private readonly ILogger _logger;

    public TodoController(ILoggerFactory logger)
    {
        _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
    }
}

不過,大多數時候,我們還是使用 ILogger<T>,更加方便:

public class TodoController : Controller
{
    private readonly ILogger _logger;

    public TodoController(ILogger<TodoController> logger)
    {
        _logger = logger;
    }
}

這等效於使用 T 型別的完整限定名來呼叫 CreateLogger 方法。

日誌級別

在我們記錄日誌時,需要指定日誌的級別,這對我們過濾日誌非常有用,比如在測試環境中,我們希望提供非常的詳細的日誌資訊,包括一些敏感資訊等,但是在生產環境中,我們希望只記錄嚴重的錯誤,這時候只需要簡單的通過 AddFilter 對日誌的過濾級別配置一下就行了。

ASP.NET Core Logging 系統提供了六個日誌級別,通過增加重要性或嚴重程度排序如下:

  • Trace 用於記錄最詳細的日誌訊息,通常僅用於開發階段除錯問題。這些訊息可能包含敏感的應用程式資料,因此不應該用於生產環境。預設應禁用。

  • Debug 這種訊息在開發階段短期內比較有用。它們包含一些可能會對除錯有所助益、但沒有長期價值的資訊。預設情況下這是最詳細的日誌。

  • Information 這種訊息被用於跟蹤應用程式的一般流程。與 Verbose 級別的訊息相反,這些日誌應該有一定的長期價值。

  • Warning 當應用程式出現錯誤或其它不會導致程式停止的流程異常或意外事件時使用警告級別,以供日後調查。在一個通用的地方處理警告級別的異常。

  • Error 當應用程式由於某些故障停止工作則需要記錄錯誤日誌。這些訊息應該指明當前活動或操作(比如當前的 HTTP 請求),而不是應用程式範圍的故障。

  • Critical 當應用程式或系統崩潰、遇到災難性故障,需要立即被關注時,應當記錄關鍵級別的日誌。如資料丟失、磁碟空間不夠等。

日誌事件ID

每次寫日誌的時候,我們可以指定一個 event ID


public class LoggingEvents
{
    public const int GET_ITEM = 1002;
    public const int GET_ITEM_NOTFOUND = 4000;
}

public IActionResult GetById(string id)
{
    _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
    var item = _todoRepository.Find(id);
    if (item == null)
    {
        _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
        return NotFound();
    }
    return new ObjectResult(item);
}

event ID 是一個整數,它可以將一組日誌事件關聯到一起。與日誌類別類似,但是更加細化。而它的輸出取決於日誌提供者,Console 提供者輸出格式如下,在日誌類別後面,並用一對中括號包裹著:

info: TodoApi.Controllers.TodoController[1002]
      Getting item invalidid
warn: TodoApi.Controllers.TodoController[4000]
      GetById(invalidid) NOT FOUND

日誌格式化字串

每次記錄日誌時,都會提供一條文字訊息,而在這個訊息字串中,我們可以使用命名佔位符:

public IActionResult GetById(string id)
{
    _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
    var item = _todoRepository.Find(id);
    if (item == null)
    {
        _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
        return NotFound();
    }
    return new ObjectResult(item);
}

但是佔位符的順序決定了使用哪個引數,而不是它的名字,如下示例:

string p1 = "parm1";
string p2 = "parm2";
_logger.LogInformation("Parameter values: {p2}, {p1}", p1, p2);

輸出結果為:

Parameter values: parm1, parm2

那這樣做有什麼意義呢?

日誌框架使用這種訊息格式化方式,使日誌提供者能夠實現 語義化日誌,也稱結構化日誌。因為引數本身被傳遞到日誌系統中,而不僅僅是格式化的字串,因此日誌提供者可以將引數的值作為欄位儲存單獨的儲存。比如:如果使用 Azure Table Storage,我們可以使用如下方法來記錄日誌:

_logger.LogInformation("Getting item {ID} at {RequestTime}", id, DateTime.Now);

每一個 Azure Table 都可以有 IDRequestTime 屬性,這將簡化對日誌資料的查詢,你可以查詢指定 RequestTime 範圍內的所有日誌,而不必花費解析文字訊息的開銷。

過濾器

過濾器可以讓你根據日誌的級別和類別來選擇是輸出,還是忽略。我們可以為不同的日誌提供者指定不同的過濾器,如下程式碼所示,讓 Console 提供者忽略低於 warning 級別的日誌,而 Debug 提供者則忽略 TodoApi 類別的日誌。

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    loggerFactory
        .AddConsole(LogLevel.Warning)
        .AddDebug((category, logLevel) => (category.Contains("TodoApi") && logLevel >= LogLevel.Trace));
}

而我們還可以指定全域性過濾器,作用於所有的日誌提供者,如下示例,我們對於以 "Microsoft" 和 "System" 開頭的日誌類別忽略掉低於 Warning 級別的日誌:

public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
    loggerFactory
        .AddFilter("Microsoft", LogLevel.Warning)
        .AddFilter("System", LogLevel.Warning)
        .AddFilter("SampleApp.Program", LogLevel.Debug)
        .AddDebug();
}

作用域

我們可以將一組邏輯操作放在一個有序的 Scope 中,將 Scope 的標識附加到範圍內的所有日誌中。例如,我們可以在處理事務的時候,使事務內的每一個操作日誌都包含這個事務的ID。

使用ILgger.BeginScope<TState> 方法建立一個 Scope,並返回一個 IDisposable 型別,當我們 Dispose的時候,這個 Scope 也就結束了,非常適合於使用 using 的方式:

public IActionResult GetById(string id)
{
    TodoItem item;
    using (_logger.BeginScope("Message attached to logs created in the using block"))
    {
        _logger.LogInformation(LoggingEvents.GET_ITEM, "Getting item {ID}", id);
        item = _todoRepository.Find(id);
        if (item == null)
        {
            _logger.LogWarning(LoggingEvents.GET_ITEM_NOTFOUND, "GetById({ID}) NOT FOUND", id);
            return NotFound();
        }
    }
    return new ObjectResult(item);
}

每一個日誌將包括 Scope 的資訊:

info: TodoApi.Controllers.TodoController[1002]
      => RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
      Getting item 0
warn: TodoApi.Controllers.TodoController[4000]
      => RequestId:0HKV9C49II9CK RequestPath:/api/todo/0 => TodoApi.Controllers.TodoController.GetById (TodoApi) => Message attached to logs created in the using block
      GetById(0) NOT FOUND

總結

ASP.NET Core 提供了統一的日誌框架,能方便地通過 Startup 類進行配置,靈活的整合第三方日誌框架,並使用依賴注入的方式在應用程式中使用。本文整體的概述了一下 Logging 系統,在下一章中,會來分析一下 Logging 中配置的原始碼。

相關推薦

ASP.NET Core 原始碼學習 Logging[1]:Introduction

在ASP.NET 4.X中,我們通常使用 log4net, NLog 等來記錄日誌,但是當我們引用的一些第三方類庫使用不同的日誌框架時,就比較混亂了。而在 ASP.Net Core 中內建了日誌系統,並提供了一個統一的日誌介面,ASP.Net Core 系統以及其它第三方類庫等都使用這個日誌介面來記錄日誌,而

ASP.NET Core 2.1 原始碼學習 Options[1]:Configure

配置的本質就是字串的鍵值對,但是對於面嚮物件語言來說,能使用強型別的配置是何等的爽哉! 目錄 ASP.NET Core 配置系統 強型別的 Options Configure 方法 ConfigureNamedOptions ASP.NET Core 配置系統 在ASP.NET 4.X中,通常將配置儲

asp.net core identity學習1

ASP.NET Identity 學習 建立一個Asp.net core mvc專案 新增Nuget包: Microsoft.EntityFrameworkCore.SqlServer 3.1.3 Microsoft.EntityFrameworkCore.Tools 3.1.3 Microsoft.A

ASP.NET Core 運行原理剖析1:初始化WebApp模版並運行

正式版 功能 option urn server ack reference 修改 tin ASP.NET Core 運行原理剖析1:初始化WebApp模版並運行 核心框架 ASP.NET Core APP 創建與運行 總結 之前兩篇文章簡析.NET Core

ASP.NET Core 2 學習筆記(十)視圖

部分 合成 cati 分享 col script text var AC ASP.NET Core MVC中的Views是負責網頁顯示,將數據一並渲染至UI包含HTML、CSS等。並能痛過Razor語法在*.cshtml中寫渲染畫面的程序邏輯。本篇將介紹ASP.NET Co

ASP.NET Core 2 學習筆記(十二)REST-Like API

light namespace strong postman space 新增 html move engine Restful幾乎已算是API設計的標準,通過HTTP Method區分新增(Create)、查詢(Read)、修改(Update)和刪除(Delete),簡稱

ASP.NET Core 2 學習筆記(十四)Filters

span ans 黃色 返回 lec red addm spn using 原文:ASP.NET Core 2 學習筆記(十四)FiltersFilter是延續ASP.NET MVC的產物,同樣保留了五種的Filter,分別是Authorization Filter、Res

ASP.NET Core 2 學習筆記(四)依賴註入

pub framework 三次 DDM order 包裝 差異 限制 cto 原文:ASP.NET Core 2 學習筆記(四)依賴註入ASP.NET Core使用了大量的依賴註入(Dependency Injection, DI),把控制反轉(Inversion Of

ASP.NET Core 2 學習筆記(二)生命周期

RF Go 使用 HR runt block top 最大的 env 原文:ASP.NET Core 2 學習筆記(二)生命周期要了解程序的運行原理,就要先知道程序的進入點及生命周期。以往ASP.NET MVC的啟動方式,是繼承 HttpApplication 作為網站開始

ASP.NET Core 2 學習筆記(七)路由

local quest urn AD term 執行 自動 routes code 原文:ASP.NET Core 2 學習筆記(七)路由ASP.NET Core通過路由(Routing)設定,將定義的URL規則找到相對應行為;當使用者Request的URL滿足特定規則條件

ASP.NET Core 2 學習筆記(十一)Cookies & Session

自動 asp Go 內存 rtu serialize .html call names 原文:ASP.NET Core 2 學習筆記(十一)Cookies & Session基本上HTTP是沒有記錄狀態的協定,但可以通過Cookies將Request來源區分出來,並

ASP.NET Core 2 學習筆記(六)MVC

方便 web redirect AR return his 架構模式 PE ofo 原文:ASP.NET Core 2 學習筆記(六)MVC ASP.NET Core MVC跟ASP.NET MVC觀念是一致的,使用上也沒有什麽太大的變化。之前的ASP.NET MVC把MV

asp.net core入門學習

前言 .net core 已經更新到2.0以上的版本了,今天才開始正式接觸,深為程式設計師,丟臉了,作為無所不能的IT人,我著手摺騰一下這個跨平臺的開發框架。 (轉載自百度百科).NET Core 是 .NET Framework的新一代版本,是微軟開發的第一個官方版本,具有跨平臺 (

Visual Studio 2017中使用SourceLink除錯ASP.NET Core原始碼

背景 當我們在學習ASP.NET Core或者除錯ASP.NET Core程式的時候,有時候需要除錯底層程式碼,但是當我們在Visual Studio中除錯程式的時候,由於一些基礎庫或者第三方庫缺少pdb檔案,所以除錯這些庫的程式碼的時候,會出現斷點不能進去的情況。 例如,在如下程式碼中,我們希望除錯

asp.net core mcroservices 機構 分散式日誌(一)

      一 簡介                     

asp.net core mcroservices 架構 分散式日誌(二)自定義日誌開發

一   netcore日誌原理                                    &nbs

ASP.NET Core應用的錯誤處理[1]:三種呈現錯誤頁面的方式

由於ASP.NET Core應用是一個同時處理多個請求的伺服器應用,所以在處理某個請求過程中丟擲的異常並不會導致整個應用的終止。出於安全方面的考量,為了避免敏感資訊的外洩,客戶端在預設的情況下並不會得到詳細的出錯資訊,這無疑會在開發環境下增加查錯糾錯的難度。對於生產環境來說,我們也希望終端使用者能夠根據具體的

Blog with ASP.NET Core and React/Redux. Part 1: Authentication

Back-endThe goal for the back-end part is to make REST API connected to the database. This API should have two endpoints so we can log in and register.Blog

asp.net core mcroservices 架構 分散式日誌(三):整合kafka

    一 kafka介紹                      

asp.net core引數保護自定義要保護的引數型別

asp.net core引數保護之自定義要保護的引數型別 Intro 為了實現 asp.net core 下的引數保護,擴充套件了asp.net core 中 DataProtection,可以自動化的保護某些敏感引數,上次推出之後有一些小夥伴反饋希望能保護 JsonResult 返回的引數,本文主要以