Log 是系統不可或缺的角色,有利於我們開發(偵錯)、維運,.NET Core 發展出了標準化的 Log 抽象 Microsoft.Extensions.Logging.Abstractions,未來可使用這個標準來實現 Log
開發環境
- VS 2019
- .NET Framework 4.8
- ASP.NET 3.1
- Microsoft.Extensions.Logging 3.1.9
三個介面
ILogger:紀錄日誌
ILoggerProvider:產生 ILogger 實例
ILoggerFactory:使用那些 ILoggerProvider
由 Microsoft.Extensions.Logging.Abstractions 和 Microsoft.Extensions.Logging 所提供Install-Package Microsoft.Extensions.Logging
Install-Package Microsoft.Extensions.Logging.Abstractions
實例化範例
開始之前先來看個範例,以下的範例,
要先安裝Install-Package Microsoft.Extensions.Logging.Console
.NET Framework
- 在 .NET Framework 沒有像 .NET Core 內建 DI Container,可以呼叫 LoggerFactory.Create 建立實例。
- builder.AddConsole():使用 Console LoggerProvider。
- factory.CreateLogger():Logger 實例。
- logger.LogInformation:寫入 Log,等級為 Information
var factory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});
var logger = factory.CreateLogger<Form1>();
logger.LogInformation("Example log message");
ASP.NET Core 3.1
- 可以使用 DI Container 的方式注入
- builder.AddConsole():使用 Console LoggerProvider
public class Program
{
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.ConfigureLogging((context, builder) =>
{
builder.AddConsole();
});
}
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
}
- 在 Controller 就可以拿到 ILogger 實例
- logger.LogInformation:寫入 Log,等級為 Information
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
this._logger = logger;
}
[HttpGet]
public IActionResult Get()
{
...
this._logger.LogInformation(LogEvent.GenerateItem,"開始,訪問 WeatherForecast api");
}
}
6個層級
LogLevel 定義以及建議的使用方式如下:
LogLevel | 值 | 擴充方法 | 描述 |
---|---|---|---|
Trace | 0 | LogTrace | 追蹤:包含最詳細的訊息。 這些訊息可能包含敏感性應用程式資料。 這些訊息預設為停用,且不應在生產環境中啟用。 |
Debug | 1 | LogDebug | 偵錯:用於偵錯工具和開發。 請在生產環境中小心使用,因為有大量的數量。 |
Information | 2 | LogInformation | 資訊:追蹤應用程式的一般流程。 可能具有長期值。 |
Warning | 3 | LogWarning | 警告:針對異常或非預期的事件。 通常會包含不會導致應用程式失敗的錯誤或狀況。 |
Error | 4 | LogError | 錯誤:發生無法處理的錯誤和例外狀況。 這些訊息表示目前的作業或要求失敗,而不是整個應用程式的失敗。 |
Critical | 5 | LogCritical | 嚴重:發生需要立即注意的失敗。 範例:資料遺失情況、磁碟空間不足。 |
None | 6 | 指定記錄類別不應寫入任何訊息。 |
- 如果未設定預設記錄層級,則預設的記錄層級值為 Information 。
ASP.NET Core 範例如下:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
參考
https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1#log-level
ILoggerFactory
以下的範例,要先安裝Install-Package Microsoft.Extensions.Logging.Console
- 在 .NET Framework 沒有像 ASP .NET Core 內建 DI Container,可以呼叫 LoggerFactory.Create 建立實例
var factory = LoggerFactory.Create(builder =>
{
builder.AddConsole()
;
});
var logger = factory.CreateLogger<Form1>();
logger.LogInformation("Example log message");
- ASP.NET Core 3.1 可以使用 DI Container 的方式注入
Host.CreateDefaultBuilder 會注入下列 LoggerProvider
主控台
偵錯
EventSource
EventLog:僅限 Windows
為了演示,我仍然注入了 Console LoggerProvider
public class Program
{
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.ConfigureLogging((context, builder) =>
{
builder.AddConsole()
;
});
}
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
}
在 Controller 即可取出 ILogger 實例
private readonly ILogger<WeatherForecastController> _logger;
public WeatherForecastController(ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
this._logger.LogInformation("訪問 /WeatherForecast");
///....
}
除了,預設 Controller 注入,我們也可以針對其他物件注入 ILogger
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddRazorPages();
services.AddSingleton<IMyService>((container) =>
{
var logger = container.GetRequiredService<ILogger<MyService>>();
return new MyService() { Logger = logger };
});
}
ILoggerProvider
內建提供以下 LoggerProvider
Console:Microsoft.Extensions.Logging.Console
Debug:Microsoft.Extensions.Logging.Debug
EventLog:Microsoft.Extensions.Logging.EventLog
EventSource:Microsoft.Extensions.Logging.EventSource
TraceSource:Microsoft.Extensions.Logging.TraceSource
AzureAppServices:Microsoft.Extensions.Logging.AzureAppServices
3rd LoggerProvider
支援 ASP.NET Core 的 Log 協力廠商
elmah.io
Gelf
JSNLog
KissLog.net
Log4Net
Loggr
NLog
PLogger
Sentry
Serilog
Stackdriver
ILogger
只需要一行程式,即可依照 LoggerProvider,寫入不同的目標
如下範例,注入了多個 LoggerProvider
public static IHostBuilder CreateHostBuilder(
string[] args) => Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureLogging(logging =>
{
// clear default logging providers
logging.ClearProviders();
// add built-in providers manually, as needed
logging.AddConsole();
logging.AddDebug();
logging.AddEventLog();
logging.AddEventSourceLogger();
logging.AddTraceSource(sourceSwitchName);
});
Log Message
- Log() 方法要傳入 Level 參數
- 擴充方法,方法名稱已經帶有 Level
// Log() 方法要帶有 Level
ILogger.Log(LogLevel.Information, "some text");
// 擴充方法,方法名稱已經帶有
ILogger.LogInformation("some text");
Log 分類
ILogger 建立物件時,需要指定分類,分類的字串是任意的,慣例是使用類別名稱。
LoggerFactory.Create 使用範例如下:
var factory = LoggerFactory.Create(builder =>
{
builder.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("WindowsFormsApp1", LogLevel.Debug)
.AddConsole()
;
});
var logger = factory.CreateLogger<Form1>();
logger.LogInformation("Example log message");
例如:ASP.NET Core 可以使用注入,最後,在建構函數改變自訂的分類名稱
public class ContactModel : PageModel
{
private readonly ILogger _logger;
public ContactModel(ILoggerFactory logger)
{
_logger = logger.CreateLogger("MyCategory");
}
public void OnGet()
{
_logger.LogInformation("GET Pages.ContactModel called.");
}
}
參考
https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1#log-category
Log 範本
- 訊息範本可以包含預留變數,變數使用名稱而非數字
- 傳遞參數,可讓 LoggerProvider 執行語意結構化紀錄
_logger.LogInformation("Getting item {Id} at {RequestTime}", id, DateTime.Now);
Scope
在非同步的框架下,同一個流程,日誌內容會被散落在四處,這時後利用 BeginScope 可以讓我們的日誌比較好除錯
內建支援的 LoggerProvider:
Console
AzureAppServicesFile 和 AzureAppServicesBlob
使用方式
在 BeginScope 調用關閉(IDisposable)前都屬於都一個範圍,可用 Using 包起來
[HttpGet]
public IActionResult Get()
{
using (this._logger.BeginScope($"Scope Id:{Guid.NewGuid()}"))
//using (this._logger.BeginScope("Scope Id:{id}", Guid.NewGuid().ToString())
{
this._logger.LogInformation("開始,訪問 WeatherForecast api");
this._logger.LogInformation("執行流程");
var random = new Random();
var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = random.Next(-20, 55),
Summary = Summaries[random.Next(Summaries.Length)]
})
.ToArray();
this._logger.LogInformation("結束流程");
return this.Ok(result);
}
}
最後再設定檔的 Console LoggerProvider 加入 “IncludeScopes”: true
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
},
"Console": {
"IncludeScopes": true,// <= 這裡
"LogLevel": {
"Default": "Trace",
"Microsoft": "Warning"
}
}
},
"AllowedHosts": "*"
}
執行結果:
這讓每一筆 Log 都添加 Guid
參考
https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1#log-scopes
事件識別碼
識別碼用來建立日誌的關聯性,有些 LoggerProvider 有提供識別碼篩選
先建立識別碼。如下:
public class LogEvent
{
public const int GenerateItem = 1000;
public const int ListItem = 1001;
public const int GetItem = 1002;
public const int InsertItem = 1003;
public const int UpdateItem = 1004;
public const int DeleteItem = 1005;
public const int TestItem = 3000;
public const int GetItemNotFound = 4000;
public const int UpdateItemNotFound = 4001;
}
[HttpGet]
public IActionResult Get()
{
//using (this._logger.BeginScope($"Scope Id:{Guid.NewGuid()}"))
using (this._logger.BeginScope("Scope Id:{id}", Guid.NewGuid()))
{
this._logger.LogInformation(LogEvent.GenerateItem,"開始,訪問 WeatherForecast api");
this._logger.LogInformation(LogEvent.TestItem,"執行流程");
var random = new Random();
var result = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = random.Next(-20, 55),
Summary = Summaries[random.Next(Summaries.Length)]
})
.ToArray();
this._logger.LogInformation(LogEvent.GenerateItem,"結束流程");
return this.Ok(result);
}
}
執行結果:
Console LoggerProvider 就會在類別後面加上 [識別碼] WebApiNetCore31.Controllers.WeatherForecastController[1000]
參考
https://docs.microsoft.com/zh-tw/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1#log-event-id
非同步
官方 文件寫著沒有 Logger.Log 提供非同步的方法,原因是紀錄日誌應該是很快,如果很慢的話應該先將記錄檔寫在快速儲存區(記憶體),再把它們移到慢速儲存區(I/O),比如 SQL Server,幸好 LoggerProvider 有實作非同步寫入日誌,比如:Consloe、NLog
Config
ASP.NET Core 的預設專案範本一建立就會產生 appsettings.json、appsettings.Development.json 內容如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
上述設定,LoggerProvider 會依照 Namsespace 和 LogLevel 過濾出需要記錄的日誌,當指定某一 Level 時,代表該等級以上的 Log 都會被記錄下來
- Logging:所有的 LoggerProvider,套用此設定
- Default:預設,套用 Information Level
- Microsoft:“Microsoft” 開頭的命名空間,套用 Warning Level
- Microsoft.Hosting.Lifetime:比類別更明確 “Microsoft”,所以 “Microsoft.Hosting.Lifetime” 的等級為 Information
- 特定分類覆蓋 Default 分類
- Level 高的覆蓋低的
- 若要隱藏所有的日誌則指定 None
ASP.NET Core 範例如下:
public class Program
{
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.ConfigureLogging((context, builder) =>
{
builder.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("WindowsFormsApp1", LogLevel.Debug)
.AddConfiguration(context.Configuration.GetSection("Logging"))
.AddConsole()
;
});
}
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
}
Filer 函數
當 Config 之外,透過 Filter 做更多的設定
ASP.NET Core 範例如下:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.AddFilter((provider, category, logLevel) =>
{
if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Controller")
&& logLevel >= LogLevel.Information)
{
return true;
}
else if (provider.Contains("ConsoleLoggerProvider")
&& category.Contains("Microsoft")
&& logLevel >= LogLevel.Information)
{
return true;
}
else
{
return false;
}
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
一般來說,記錄層級應指定于設定中,而不是程式碼。
專案位置
https://github.com/yaochangyu/sample.dotblog/tree/master/Log/Lab.MsLogging
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET