在ASP.NET 4.X中,我們通常使用 log4net, NLog 等來記錄日志,但是當(dāng)我們引用的一些第三方類庫使用不同的日志框架時(shí),就比較混亂了。而在 ASP.Net Core 中內(nèi)置了日志系統(tǒng),并提供了一個(gè)統(tǒng)一的日志接口,ASP.Net Core 系統(tǒng)以及其它第三方類庫等都使用這個(gè)日志接口來記錄日志,而不關(guān)注日志的具體實(shí)現(xiàn),這樣便可以在我們的應(yīng)用程序中進(jìn)行統(tǒng)一的配置,并能很好的與第三方日志框架集成。

注冊日志服務(wù)

ASP.NET Core 全部使用依賴注入,更好的規(guī)范我們的代碼。想要使用日志系統(tǒng),首先要進(jìn)行注冊和配置:

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

如上,通過 AddLogging ,將日志系統(tǒng)注冊到了 DI 系統(tǒng)中,而 AddConfiguration 是對日志系統(tǒng)的全局配置, AddFilter 則是對日志過濾器的一些配置,最后 AddConsole 添加了一個(gè) Console 的日志提供者(將日志輸出到控制臺(tái))。

記錄日志

在我們需要記錄日志的時(shí)候,只需要通過構(gòu)造函數(shù)注入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 表示日記的類別,在我們查看日志時(shí),非常有用,在本文后面會(huì)講。

日志輸出示例

使用上面的示例代碼,當(dāng)我們通過控制臺(tái)來運(yùn)行時(shí),訪問 http://localhost:5000/api/todo/0 將會(huì)看到如下的日志輸出:

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

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

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 404Microsoft.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

通過這個(gè)示例,可以看到我們記錄到了 ASP.NET Core 框架自身的日志,這也是統(tǒng)一的日志框架才能實(shí)現(xiàn)的功能。

日志類別

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

public class TodoController : Controller{    private readonly ILogger _logger;    public TodoController(ILoggerFactory logger)    {
        _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
    }
}

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

public class TodoController : Controller{    private readonly ILogger _logger;    public TodoController(ILogger<TodoController> logger)    {
        _logger = logger;
    }
}

這等效于使用 T 類型的完整限定名來調(diào)用 CreateLogger 方法。

日志級(jí)別

在我們記錄日志時(shí),需要指定日志的級(jí)別,這對我們過濾日志非常有用,比如在測試環(huán)境中,我們希望提供非常的詳細(xì)的日志信息,包括一些敏感信息等,但是在生產(chǎn)環(huán)境中,我們希望只記錄嚴(yán)重的錯(cuò)誤,這時(shí)候只需要簡單的通過 AddFilter 對日志的過濾級(jí)別配置一下就行了。

ASP.NET Core Logging 系統(tǒng)提供了六個(gè)日志級(jí)別,通過增加重要性或嚴(yán)重程度排序如下:

  • Trace 用于記錄最詳細(xì)的日志消息,通常僅用于開發(fā)階段調(diào)試問題。這些消息可能包含敏感的應(yīng)用程序數(shù)據(jù),因此不應(yīng)該用于生產(chǎn)環(huán)境。默認(rèn)應(yīng)禁用。

  • Debug 這種消息在開發(fā)階段短期內(nèi)比較有用。它們包含一些可能會(huì)對調(diào)試有所助益、但沒有長期價(jià)值的信息。默認(rèn)情況下這是最詳細(xì)的日志。

  • Information 這種消息被用于跟蹤應(yīng)用程序的一般流程。與 Verbose 級(jí)別的消息相反,這些日志應(yīng)該有一定的長期價(jià)值。

  • Warning 當(dāng)應(yīng)用程序出現(xiàn)錯(cuò)誤或其它不會(huì)導(dǎo)致程序停止的流程異?;蛞馔馐录r(shí)使用警告級(jí)別,以供日后調(diào)查。在一個(gè)通用的地方處理警告級(jí)別的異常。

  • Error 當(dāng)應(yīng)用程序由于某些故障停止工作則需要記錄錯(cuò)誤日志。這些消息應(yīng)該指明當(dāng)前活動(dòng)或操作(比如當(dāng)前的 HTTP 請求),而不是應(yīng)用程序范圍的故障。

  • Critical 當(dāng)應(yīng)用程序或系統(tǒng)崩潰、遇到災(zāi)難性故障,需要立即被關(guān)注時(shí),應(yīng)當(dāng)記錄關(guān)鍵級(jí)別的日志。如數(shù)據(jù)丟失、磁盤空間不夠等。

日志事件ID

每次寫日志的時(shí)候,我們可以指定一個(gè) 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 是一個(gè)整數(shù),它可以將一組日志事件關(guān)聯(lián)到一起。與日志類別類似,但是更加細(xì)化。而它的輸出取決于日志提供者,Console 提供者輸出格式如下,在日志類別后面,并用一對中括號(hào)包裹著:

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

日志格式化字符串

每次記錄日志時(shí),都會(huì)提供一條文本消息,而在這個(gè)消息字符串中,我們可以使用命名占位符:

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);
}

但是占位符的順序決定了使用哪個(gè)參數(shù),而不是它的名字,如下示例:

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

輸出結(jié)果為:

Parameter values: parm1, parm2

那這樣做有什么意義呢?

日志框架使用這種消息格式化方式,使日志提供者能夠?qū)崿F(xiàn) 語義化日志,也稱結(jié)構(gòu)化日志。因?yàn)閰?shù)本身被傳遞到日志系統(tǒng)中,而不僅僅是格式化的字符串,因此日志提供者可以將參數(shù)的值作為字段存儲(chǔ)單獨(dú)的存儲(chǔ)。比如:如果使用 Azure Table Storage,我們可以使用如下方法來記錄日志:

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

每一個(gè) Azure Table 都可以有 ID 和 RequestTime 屬性,這將簡化對日志數(shù)據(jù)的查詢,你可以查找指定 RequestTime 范圍內(nèi)的所有日志,而不必花費(fèi)解析文本消息的開銷。

過濾器

過濾器可以讓你根據(jù)日志的級(jí)別和類別來選擇是輸出,還是忽略。我們可以為不同的日志提供者指定不同的過濾器,如下代碼所示,讓 Console 提供者忽略低于 warning 級(jí)別的日志,而 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級(jí)別的日志:

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

作用域

我們可以將一組邏輯操作放在一個(gè)有序的 Scope 中,將 Scope 的標(biāo)識(shí)附加到范圍內(nèi)的所有日志中。例如,我們可以在處理事務(wù)的時(shí)候,使事務(wù)內(nèi)的每一個(gè)操作日志都包含這個(gè)事務(wù)的ID。

使用ILgger.BeginScope<TState> 方法創(chuàng)建一個(gè) Scope,并返回一個(gè) IDisposable 類型,當(dāng)我們 Dispose的時(shí)候,這個(gè) Scope 也就結(jié)束了,非常適合于使用 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);
}

每一個(gè)日志將包括 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

總結(jié)

ASP.NET Core 提供了統(tǒng)一的日志框架,能方便地通過 Startup 類進(jìn)行配置,靈活的集成第三方日志框架,并使用依賴注入的方式在應(yīng)用程序中使用。本文整體的概述了一下 Logging 系統(tǒng),在下一章中,會(huì)來分析一下 Logging 中配置的源碼。

http://www.cnblogs.com/RainingNight/p/asp-net-core-logging-introduction.html