使用日志
分类: Asp.net Core入门 ◆ 标签: #Asp.Net core基础 #基础 #Web ◆ 发布于: 2023-06-04 20:31:11

在Asp.net core
中除了Console
以外,其他的日志提供者都存储日志,例如Azure Application Insights
将日志存储到该服务中,框架已经向用户提供了不少日志提供者,我们还是从最基本的模板来看一下默认已经启用了哪些日志提供者。
public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
我们需要注意的是方法CreateDefaultBuilder
, 在这个方法里,完成了:
- 创建
Generic Host
- 调用方法
CreateDefaultBuilder
:- Console
- Debug
- EventSource
- EventLog: (Windows Only)
以上是默认的日志提供者,如果用户需要自己定制日志,那么可以使用如下的方法:
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureLogging(logging => { logging.ClearProviders(); logging.AddConsole(); }) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
注意扩展方法ConfigureLogging
。
默认配置和基本用法
我们来看一下日志提供者的基本用法,DI在整个框架中都是需要用到的,因此日志也不例外,为了创建日志,我们需要从DI里通过构造函数注入:ILoger<TCategoryName>
, 这里有一个重要的概念就是日志的分类(Category), 分类是一个字符串,类似于一个域名的层级形式,除了在日志里记录这个Category
之外,我们还在配置文件里使用该分类,来定义某个分类的日志等级,以过滤日志。
从构造函数里注入,并使用:
public class AboutModel : PageModel { private readonly ILogger _logger; public AboutModel(ILogger<AboutModel> logger) { _logger = logger; } public string Message { get; set; } public void OnGet() { Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}"; _logger.LogInformation(Message); } }
这里有两个非常重要的概念,就是日志的等级,以及日志的分类,关于分类我们上面已经学习过了。
日志配置
我们使用配置Logging
来配置日志,同时这个项下面还有很多个子项,例如:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } } }
从这个配置上可以看出:
Default
,Microsoft
,Microsoft.Hosting.Lifetime
都是表示日志的分类。- 分类
Microsoft
分类应用所有以Microsoft
开头的分类。 - 同时每个分类的日志等级可以定位不一样的。
- 缺省的还是由
default
来定义。 - 如果某一个提供者没有指定相应的配置,那么就会使用
Default
再看一个例子:
{ "Logging": { "LogLevel": { // All providers, LogLevel applies to all the enabled providers. "Default": "Error", // Default logging, Error and higher. "Microsoft": "Warning" // All Microsoft* categories, Warning and higher. }, "Debug": { // Debug provider. "LogLevel": { "Default": "Information", // Overrides preceding LogLevel:Default setting. "Microsoft.Hosting": "Trace" // Debug:Microsoft.Hosting category. } }, "EventSource": { // EventSource provider "LogLevel": { "Default": "Warning" // All categories of EventSource provider. } } } }
这里的Debug
, EventSource
就是专门针对日志提供者设定的配置。
上述也可以以这种形式指定:Logging:Default:LogLevel:Default:Information
也可以通过环境变量,例如: set Logging__LogLevel__Microsoft=Information
简单的描述一下日志过滤的规则算法:
- 使用提供者或者提供者的别名,选择所有符合条件的日志,如果没有符合的,则选择空提供者。
- 从上一步的结果中,选择符合指定分类名的日志,如果没有符合的分类,则选择所有没有指定分类的日志
- 如果由多条日志被选择,最少用一条
- 如果全部没选中,则使用
MinimumLevel
Log Category
日志分类主要的作用是用于日志的过滤,例如在配置里设定某个分类的等级,在日志显示的时候也显示出具体的分类,按照约定一般情况推荐使用类名来表示分类。
基本的用法可以使用泛型版的ILogger: ILogger
public class PrivacyModel : PageModel { private readonly ILogger<PrivacyModel> _logger; public PrivacyModel(ILogger<PrivacyModel> logger) { _logger = logger; } public void OnGet() { _logger.LogInformation("GET Pages.PrivacyModel called."); } }
也可以明确的指定分类名称,这个时候需要使用方法ILoggerFactory.CreateLogger
:
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."); }
Log Level
日志等级这个很好理解,一般情况分成这几类:
- Trace: 0
- Debug: 1
- Information: 2
- Warning: 3
- Error: 4
- Critical: 5
- None: 6
日志级别从小到大,显示的信息也会越来越窄,Trace(0), 显示最多的信息。注意日志的方法LogTrace
, 类似这样的。
Log Event Id
每个日志都可以指定一个事件的ID, 这里的ID是用户自行定义的,非常方便用自己的系统中,用于快速的判断问题。
public class MyLogEvents { public const int GenerateItems = 1000; public const int ListItems = 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; }
使用事件ID
[HttpGet("{id}")] public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id) { _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id); var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id); return NotFound(); } return ItemToDTO(todoItem); }
可以看到日志显示的方法是: LogInformation('事件ID', '消息模板', '替换变量')
Log Message Template
日志消息中使用命名模板,如下代码所示:
[HttpGet("{id}")] public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id) { _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id); var todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id); return NotFound(); } return ItemToDTO(todoItem); } string apples = 1; string pears = 2; string bananas = 3; _logger.LogInformation("Parameters: {pears}, {bananas}, {apples}", apples, pears, bananas);
Log Exception
日志的方法有重载可以直接显示或者存储异常的,如下代码所示:
[HttpGet("{id}")] public IActionResult TestExp(int id) { var routeInfo = ControllerContext.ToCtxString(id); _logger.LogInformation(MyLogEvents.TestItem, routeInfo); try { if (id == 3) { throw new Exception("Test exception"); } } catch (Exception ex) { _logger.LogWarning(MyLogEvents.GetItemNotFound, ex, "TestExp({Id})", id); return NotFound(); } return ControllerContext.MyDisplayRouteInfo(); }
Log Scope
Log Scope可以将一组逻辑的操作组织到一起,这个逻辑组可以附加相同的数据到每个日志上,例如:
[HttpGet("{id}")] public async Task<ActionResult<TodoItemDTO>> GetTodoItem(long id) { TodoItem todoItem; using (_logger.BeginScope("using block message")) { _logger.LogInformation(MyLogEvents.GetItem, "Getting item {Id}", id); todoItem = await _context.TodoItems.FindAsync(id); if (todoItem == null) { _logger.LogWarning(MyLogEvents.GetItemNotFound, "Get({Id}) NOT FOUND", id); return NotFound(); } } return ItemToDTO(todoItem); }
Service中使用Log
在Service中使用构造函数注入日志的实例,可以直接使用的。
高性能日志处理
这个部分的原因我们之前有一个一篇介绍过,具体的做法实际上是用LoggerMessage.Define
来定义,例如:
先定义一个委托:
private static readonly Action<ILogger, Exception> _indexPageRequested;
然后将这个委托指向LoggerMessage.Define
指定的委托:
_indexPageRequested = LoggerMessage.Define( LogLevel.Information, new EventId(1, nameof(IndexPageRequested)), "GET request for Index page");
最后使用一个扩展方法来准备调用它:
public static void IndexPageRequested(this ILogger logger) { _indexPageRequested(logger, null); }
在日常使用中使用该扩展方法:
public async Task OnGetAsync() { _logger.IndexPageRequested(); Quotes = await _db.Quotes.AsNoTracking().ToListAsync(); }
日志这个部分可以先介绍到这里了。