注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录

快速上手

添加日志提供程序

在文章主机(Host)中,讲到Host.CreateDefaultBuilder方法,默认通过调用ConfigureLogging方法添加了ConsoleDebugEventSourceEventLog(仅Windows)共四种日志记录提供程序(Logger Provider),然后在主机Build过程中,通过AddLogging()注册了日志相关的服务。

  1. .ConfigureLogging((hostingContext, logging) =>
  2. {
  3. bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
  4. if (isWindows)
  5. {
  6. logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
  7. }
  8. // 添加 Logging 配置
  9. logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
  10. // ConsoleLoggerProvider
  11. logging.AddConsole();
  12. // DebugLoggerProvider
  13. logging.AddDebug();
  14. // EventSourceLoggerProvider
  15. logging.AddEventSourceLogger();
  16. if (isWindows)
  17. {
  18. // 在Windows平台上,添加 EventLogLoggerProvider
  19. logging.AddEventLog();
  20. }
  21. logging.Configure(options =>
  22. {
  23. options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
  24. | ActivityTrackingOptions.TraceId
  25. | ActivityTrackingOptions.ParentId;
  26. });
  27. })
  28. public class HostBuilder : IHostBuilder
  29. {
  30. private void CreateServiceProvider()
  31. {
  32. var services = new ServiceCollection();
  33. // ...
  34. services.AddLogging();
  35. // ...
  36. }
  37. }

如果不想使用默认添加的日志提供程序,我们可以通过ClearProviders清除所有已添加的日志记录提供程序,然后添加自己想要的,如Console

  1. public static IHostBuilder CreateHostBuilder(string[] args) =>
  2. Host.CreateDefaultBuilder(args)
  3. .ConfigureLogging(logging =>
  4. {
  5. logging.ClearProviders()
  6. .AddConsole();
  7. })
  8. .ConfigureWebHostDefaults(webBuilder =>
  9. {
  10. webBuilder.UseStartup<Startup>();
  11. });

记录日志

日志记录提供程序均实现了接口ILoggerProvider,该接口可以创建ILogger实例。

通过注入服务ILogger<TCategoryName>,就可以非常方便的进行日志记录了。

该服务需要指定日志的类别,可以是任意字符串,但是我们约定使用所属类的名称,通过泛型体现。例如,在控制器ValuesController中,日志类别就是ValuesController类的完全限定类型名。

  1. public class ValuesController : ControllerBase
  2. {
  3. private readonly ILogger<ValuesController> _logger;
  4. public ValuesController(ILogger<ValuesController> logger)
  5. {
  6. _logger = logger;
  7. }
  8. [HttpGet]
  9. public string Get()
  10. {
  11. _logger.LogInformation("ValuesController.Get");
  12. return "Ok";
  13. }
  14. }

当请求Get方法后,你就可以在控制台中看到看到输出的“ValuesController.Get”

如果你想要显式指定日志类别,则可以使用ILoggerFactory.CreateLogger方法:

  1. public class ValuesController : ControllerBase
  2. {
  3. private readonly ILogger _logger1;
  4. public ValuesController(ILoggerFactory loggerFactory)
  5. {
  6. _logger1 = loggerFactory.CreateLogger("MyCategory");
  7. }
  8. }

配置日志

默认模板中,日志的配置如下(在appsettings.{Environment}.json文件中):

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. }
  8. }
  9. }

针对所有日志记录提供程序进行配置

LogLevel,顾名思义,就是指要记录的日志的最低级别(即要记录大于等于该级别的日志),想必大家都不陌生。下方会详细介绍日志级别。

LogLevel中的字段,如上面示例中的“Default”、“Microsoft”等,表示日志的类别,也就是咱们上面注入ILogger时指定的泛型参数。可以为每种类别设置记录的最小日志级别,也就是这些类别所对应的值。

下面详细解释一下示例中的三种日志类别。

Default

默认情况下,如果分类没有进行特别配置(即没有在LogLevel中配置),则应用Default的配置。

Microsoft

所有分类以Microsoft开头的日志均应用Microsoft的配置。例如,Microsoft.AspNetCore.Routing.EndpointMiddleware类别的日志就会应用该配置。

Microsoft.Hosting.Lifetime

所有分类以Microsoft.Hosting.Lifetime开头的日志均应用Microsoft.Hosting.Lifetime的配置。例如,分类Microsoft.Hosting.Lifetime就会应用该配置,而不会应用Microsoft,因为Microsoft.Hosting.LifetimeMicrosoft更具体。

OK,以上三种日志类别就说这些了。

回到示例,你可能没有注意到,这里面没有针对某个日志记录提供程序进行单独配置(如:Console只记录Error及以上级别日志,而EventSource则需要记录记录所有级别日志)。像这种,如果没有针对特定的日志记录提供程序进行配置,则该配置将会应用到所有日志记录提供程序。

Windows EventLog 除外。EventLog必须显式地进行配置,否则会使用其默认的LogLevel.Warning

针对指定的日志记录提供程序进行配置

接下来看一下如何针对指定的日志记录提供程序进行配置,先上示例:

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. },
  8. "Console": {
  9. "LogLevel": {
  10. "Default": "Error"
  11. }
  12. },
  13. "Debug": {
  14. "LogLevel": {
  15. "Microsoft": "None"
  16. }
  17. },
  18. "EventSource": {
  19. "LogLevel": {
  20. "Default": "Trace",
  21. "Microsoft": "Trace",
  22. "Microsoft.Hosting.Lifetime": "Trace"
  23. }
  24. }
  25. }
  26. }

就像appsettings.{Environment}.jsonappsettings.json之间的关系一样,Logging.{Provider}.LogLevel中的配置将会覆盖Logging.LogLevel中的配置。

例如Logging.Console.LogLevel.Default将会覆盖Logging.LogLevel.DefaultConsole日志记录器将默认记录Error及其以上级别的日志。

刚才提到了,Windows EventLog比较特殊,它不会继承Logging.LogLevel的配置。EventLog默认日志级别为LogLevel.Warning,如果想要修改,则必须显式进行指定,如:

  1. {
  2. "Logging": {
  3. "EventLog": {
  4. "LogLevel": {
  5. "Default": "Information"
  6. }
  7. }
  8. }
  9. }

配置的筛选原理

当创建ILogger<TCategoryName>的对象实例时,ILoggerFactory根据不同的日志记录提供程序,将会:

  1. 查找匹配该日志记录提供程序的配置。如果找不到,则使用通用配置。
  2. 然后匹配拥有最长前缀的配置类别。如果找不到,则使用Default配置。
  3. 如果匹配到了多条配置,则采用最后一条。
  4. 如果没有匹配到任何配置,则使用MinimumLevel,这是个配置项,默认是LogLevel.Information

可以在ConfigureLogging扩展中使用SetMinimumLevel方法设置MinimumLevel

Log Level

日志级别指示了日志的严重程度,一共分为7等,从轻到重为(最后的None较为特殊):

日志级别 描述
Trace 0 追踪级别,包含最详细的信息。这些信息可能包含敏感数据,默认情况下是禁用的,并且绝不能出现在生产环境中。
Debug 1 调试级别,用于开发人员开发和调试。信息量一般比较大,在生产环境中一定要慎用。
Information 2 信息级别,该级别平时使用较多。
Warning 3 警告级别,一些意外的事件,但这些事件并不对导致程序出错。
Error 4 错误级别,一些无法处理的错误或异常,这些事件会导致当前操作或请求失败,但不会导致整个应用出错。
Critical 5 致命错误级别,这些错误会导致整个应用出错。例如内存不足等。
None 6 指示不记录任何日志

日志记录提供程序

Console

日志将输出到控制台中。

Debug

日志将通过System.Diagnostics.Debug类进行输出,可以通过VS输出窗口查看。

在 Linux 上,可以在/var/log/message/var/log/syslog下找到

EventSource

跨平台日志记录,在Windows上则使用 ETW

Windows EventLog

仅在Windows系统下生效,可通过“事件查看器”进行日志查看。

默认情况下

  • LogName为“Application”
  • SourceName为“NET Runtime”
  • MachineName为本地计算机的名称。

这些字段都可以通过EventLogSettings进行修改:

  1. public static IHostBuilder CreateHostBuilder(string[] args) =>
  2. Host.CreateDefaultBuilder(args)
  3. .ConfigureLogging(logging =>
  4. {
  5. logging.AddEventLog(settings =>
  6. {
  7. settings.LogName = "My App";
  8. settings.SourceName = "My Log";
  9. settings.MachineName = "My Computer";
  10. })
  11. })
  12. .ConfigureWebHostDefaults(webBuilder =>
  13. {
  14. webBuilder.UseStartup<Startup>();
  15. });

日志记录过滤器

通过日志记录过滤器,允许你书写复杂的逻辑,来控制是否要记录日志。

  1. public static IHostBuilder CreateHostBuilder(string[] args) =>
  2. Host.CreateDefaultBuilder(args)
  3. .ConfigureLogging(logging =>
  4. {
  5. logging
  6. // 针对所有 LoggerProvider 设置 Microsoft 最小日志级别,建议通过配置文件进行配置
  7. .AddFilter("Microsoft", LogLevel.Trace)
  8. // 针对 ConsoleLoggerProvider 设置 Microsoft 最小日志级别,建议通过配置文件进行配置
  9. .AddFilter<ConsoleLoggerProvider>("Microsoft", LogLevel.Debug)
  10. // 针对所有 LoggerProvider 进行过滤配置
  11. .AddFilter((provider, category, logLevel) =>
  12. {
  13. // 由于下面单独针对 ConsoleLoggerProvider 添加了过滤配置,所以 ConsoleLoggerProvider 不会进入该方法
  14. if (provider == typeof(ConsoleLoggerProvider).FullName
  15. && category == typeof(ValuesController).FullName
  16. && logLevel <= LogLevel.Warning)
  17. {
  18. // false:不记录日志
  19. return false;
  20. }
  21. // true:记录日志
  22. return true;
  23. })
  24. // 针对 ConsoleLoggerProvider 进行过滤配置
  25. .AddFilter<ConsoleLoggerProvider>((category, logLevel) =>
  26. {
  27. if (category == typeof(ValuesController).FullName
  28. && logLevel <= LogLevel.Warning)
  29. {
  30. // false:不记录日志
  31. return false;
  32. }
  33. // true:记录日志
  34. return true;
  35. });
  36. })
  37. .ConfigureWebHostDefaults(webBuilder =>
  38. {
  39. webBuilder.UseStartup<Startup>();
  40. });

日志消息模版

应用开发过程中,对于某一类的日志,我们希望它们的消息格式保持一致,仅仅是某些参数发生变化。这就要用到日志消息模板了。

举个例子:

  1. [HttpGet("{id}")]
  2. public int Get(int id)
  3. {
  4. _logger.LogInformation("Get {Id}", id);
  5. return id;
  6. }

其中Get {Id}就是一个日志消息模板,{Id}则是模板参数(注意,请在里面书写名称,而不是数字,这样更容易理解参数含义)。

不过,需要注意的是,{Id}这个模板参数,仅仅是用于让人容易理解其含义的,和后面的参数名没有任何关系,模板值关心参数的顺序。例如:

  1. [HttpGet("{id}")]
  2. public int Get(int id)
  3. {
  4. _logger.LogInformation("Get {Id} at {Time}", DateTime.Now, id);
  5. return id;
  6. }

假设传入id = 1,它的输出是:Get 11/02/2021 11:42:14 at 1

日志消息模板是一项非常重要的功能,在众多开源日志中间件中,均有使用。

主机构建期间的日志记录

ASP.NET Core框架不直接支持在主机构建期间进行日志记录。但是可以通过独立的日志记录提供程序进行日志记录,例如,使用第三方日志记录提供程序:Serilog

安装Nuget包:Install-Package Serilog.AspNetCore

  1. public static void Main(string[] args)
  2. {
  3. // 从appsettings.json和命令行参数中读取配置
  4. var config = new ConfigurationBuilder()
  5. .AddJsonFile("appsettings.json")
  6. .AddCommandLine(args)
  7. .Build();
  8. // 创建Logger
  9. Log.Logger = new LoggerConfiguration()
  10. .WriteTo.Console() // 输出到控制台
  11. .WriteTo.File(config["Logging:File:Path"]) // 输出到指定文件
  12. .CreateLogger();
  13. try
  14. {
  15. CreateHostBuilder(args).Build().Run();
  16. }
  17. catch(Exception ex)
  18. {
  19. Log.Fatal(ex, "Host terminated unexpectedly");
  20. throw;
  21. }
  22. finally
  23. {
  24. Log.CloseAndFlush();
  25. }
  26. }

appsettings.json

  1. {
  2. "Logging": {
  3. "File": {
  4. "Path": "logs/host.log"
  5. }
  6. }
  7. }

控制台日志格式配置

控制台日志记录提供程序是我们开发过程中必不可少的,通过上面我们已经得知可以通过AddConsole()进行添加。不过它的局限性比较大,日志格式我们都无法进行自定义。

因此,在.NET 5中,对控制台日志记录提供程序进行了扩展,预置了三种日志输出格式:Json、Simple、Systemd。

实际上,之前也有枚举ConsoleLoggerFormat提供了Simple和Systemd格式,不过不能进行自定义,已经弃用了。

这些 Formatter 均继承自抽象类ConsoleFormatter,该抽象类构造函数接收一个“名字”参数,要求其实现类必须拥有名字。你可以通过静态类ConsoleFormatterNames获取到内置的三种格式的名字。

  1. public abstract class ConsoleFormatter
  2. {
  3. protected ConsoleFormatter(string name)
  4. {
  5. Name = name ?? throw new ArgumentNullException(nameof(name));
  6. }
  7. public string Name { get; }
  8. public abstract void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter);
  9. }
  10. public static class ConsoleFormatterNames
  11. {
  12. public const string Simple = "simple";
  13. public const string Json = "json";
  14. public const string Systemd = "systemd";
  15. }

你可以在使用AddConsole()时,配置ConsoleLoggerOptionsFormatterName属性,以达到自定义格式的目的,其默认值为“simple”。不过,为了方便使用,.NET 框架已经把内置的三种格式帮我们封装好了。

这些 Formatter 的选项类均继承自选项类ConsoleFormatterOptions,该选项类包含以下三个属性:

  1. public class ConsoleFormatterOptions
  2. {
  3. // 启用作用域,默认 false
  4. public bool IncludeScopes { get; set; }
  5. // 设置时间戳的格式,显示在日志消息开头
  6. // 默认为 null,不展示时间戳
  7. public string TimestampFormat { get; set; }
  8. // 是否将时间戳时区设置为 UTC,默认是false,即本地时区
  9. public bool UseUtcTimestamp { get; set; }
  10. }

SimpleConsoleFormatter

通过扩展方法AddSimpleConsole()可以添加支持Simple格式的控制台日志记录提供程序,默认行为与AddConsole()一致。

  1. .ConfigureLogging(logging =>
  2. {
  3. logging.ClearProviders()
  4. .AddSimpleConsole();
  5. }

示例输出:

  1. info: Microsoft.Hosting.Lifetime[0]
  2. Now listening on: http://localhost:5000
  3. info: Microsoft.Hosting.Lifetime[0]
  4. Application started. Press Ctrl+C to shut down.
  5. info: Microsoft.Hosting.Lifetime[0]
  6. Hosting environment: Development
  7. info: Microsoft.Hosting.Lifetime[0]
  8. Content root path: C:\Repos\WebApplication

另外,你可以通过SimpleConsoleFormatterOptions进行一些自定义配置:

  1. .ConfigureLogging(logging =>
  2. {
  3. logging.ClearProviders()
  4. .AddSimpleConsole(options =>
  5. {
  6. // 一条日志消息展示在同一行
  7. options.SingleLine = true;
  8. options.IncludeScopes = true;
  9. options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
  10. options.UseUtcTimestamp = false;
  11. });
  12. }

示例输出:

  1. 2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Now listening on: http://localhost:5000
  2. 2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down.
  3. 2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Hosting environment: Development
  4. 2021-11-02 15:53:33 info: Microsoft.Hosting.Lifetime[0] Content root path: C:\Repos\WebApplication

SystemdConsoleFormatter

通过扩展方法AddSystemdConsole()可以添加支持Systemd格式的控制台日志记录提供程序。如果你熟悉Linux,那你对它也一定不陌生。

  1. .ConfigureLogging(logging =>
  2. {
  3. logging.ClearProviders()
  4. .AddSystemdConsole();
  5. }

示例输出:

  1. <6>Microsoft.Hosting.Lifetime[0] Now listening on: http://localhost:5000
  2. <6>Microsoft.Hosting.Lifetime[0] Application started. Press Ctrl+C to shut down.
  3. <6>Microsoft.Hosting.Lifetime[0] Hosting environment: Development
  4. <6>Microsoft.Hosting.Lifetime[0] Content root path: C:\Repos\WebApplication

前面的<6>表示日志级别info,如果你有兴趣了解Systemd,可以访问阮一峰老师的Systemd 入门教程:命令篇

JsonConsoleFormatter

通过扩展方法AddJsonConsole()可以添加支持Json格式的控制台日志记录提供程序。

  1. .ConfigureLogging(logging =>
  2. {
  3. logging.ClearProviders()
  4. .AddJsonConsole(options =>
  5. {
  6. options.JsonWriterOptions = new JsonWriterOptions
  7. {
  8. // 启用缩进,看起来更舒服
  9. Indented = true
  10. };
  11. });
  12. }

示例输出:

  1. {
  2. "EventId": 0,
  3. "LogLevel": "Information",
  4. "Category": "Microsoft.Hosting.Lifetime",
  5. "Message": "Now listening on: http://localhost:5000",
  6. "State": {
  7. "Message": "Now listening on: http://localhost:5000",
  8. "address": "http://localhost:5000",
  9. "{OriginalFormat}": "Now listening on: {address}"
  10. }
  11. }
  12. {
  13. "EventId": 0,
  14. "LogLevel": "Information",
  15. "Category": "Microsoft.Hosting.Lifetime",
  16. "Message": "Application started. Press Ctrl\u002BC to shut down.",
  17. "State": {
  18. "Message": "Application started. Press Ctrl\u002BC to shut down.",
  19. "{OriginalFormat}": "Application started. Press Ctrl\u002BC to shut down."
  20. }
  21. }
  22. {
  23. "EventId": 0,
  24. "LogLevel": "Information",
  25. "Category": "Microsoft.Hosting.Lifetime",
  26. "Message": "Hosting environment: Development",
  27. "State": {
  28. "Message": "Hosting environment: Development",
  29. "envName": "Development",
  30. "{OriginalFormat}": "Hosting environment: {envName}"
  31. }
  32. }
  33. {
  34. "EventId": 0,
  35. "LogLevel": "Information",
  36. "Category": "Microsoft.Hosting.Lifetime",
  37. "Message": "Content root path: C:\\Repos\\WebApplication",
  38. "State": {
  39. "Message": "Content root path: C:\\Repos\\WebApplication",
  40. "contentRoot": "C:\\Repos\\WebApplication",
  41. "{OriginalFormat}": "Content root path: {contentRoot}"
  42. }
  43. }

如果你同时添加了多种格式的控制台记录程序,那么只有最后一个添加的生效。

以上介绍的是通过代码进行控制台日志记录提供程序的设置,不过我想大家应该更喜欢通过配置去设置日志记录提供程序。下面是一个简单地配置示例:

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Information",
  5. "Microsoft": "Warning",
  6. "Microsoft.Hosting.Lifetime": "Information"
  7. },
  8. "Console": {
  9. "FormatterName": "json",
  10. "FormatterOptions": {
  11. "SingleLine": true,
  12. "IncludeScopes": true,
  13. "TimestampFormat": "yyyy-MM-dd HH:mm:ss ",
  14. "UseUtcTimestamp": false,
  15. "JsonWriterOptions": {
  16. "Indented": true
  17. }
  18. }
  19. }
  20. }
  21. }

ILogger<TCategoryName>对象实例的创建

讲到这里,不知道你会不会对ILogger<TCategoryName>对象实例的创建有疑惑:它到底是如何被new出来的呢?

要解决这个问题,我们先从AddLogging()扩展方法入手:

  1. public static class LoggingServiceCollectionExtensions
  2. {
  3. public static IServiceCollection AddLogging(this IServiceCollection services)
  4. {
  5. return AddLogging(services, builder => { });
  6. }
  7. public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
  8. {
  9. services.AddOptions();
  10. // 注册单例 ILoggerFactory
  11. services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
  12. // 注册单例 ILogger<>
  13. services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
  14. // 批量注册单例 IConfigureOptions<LoggerFilterOptions>
  15. services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
  16. new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
  17. configure(new LoggingBuilder(services));
  18. return services;
  19. }
  20. }

你可能也猜到了,这个Logger<>不会是LoggerFactory创建的吧?要不然注册个这玩意干嘛呢?

别着急,咱们接着先查看ILogger<>服务的实现类Logger<>

  1. public interface ILogger
  2. {
  3. void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
  4. // 检查能否记录该日志等级的日志
  5. bool IsEnabled(LogLevel logLevel);
  6. IDisposable BeginScope<TState>(TState state);
  7. }
  8. public interface ILogger<out TCategoryName> : ILogger
  9. {
  10. }
  11. public class Logger<T> : ILogger<T>
  12. {
  13. // 接口实现内部均是使用该实例进行操作
  14. private readonly ILogger _logger;
  15. // 果不其然,注入了 ILoggerFactory 实例
  16. public Logger(ILoggerFactory factory)
  17. {
  18. // 还记得吗?上面提到显式指定日志类别时,也是这样创建 ILogger 实例的
  19. _logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T), includeGenericParameters: false, nestedTypeDelimiter: '.'));
  20. }
  21. // ...
  22. }

没错,你猜对了,那就来看看这个LoggerFactory吧(只列举核心代码):

  1. public interface ILoggerFactory : IDisposable
  2. {
  3. ILogger CreateLogger(string categoryName);
  4. void AddProvider(ILoggerProvider provider);
  5. }
  6. public class LoggerFactory : ILoggerFactory
  7. {
  8. // 用于单例化 Logger<>
  9. private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal);
  10. // 存放 ILoggerProviderRegistrations
  11. private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>();
  12. private readonly object _sync = new object();
  13. public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption, IOptions<LoggerFactoryOptions> options = null)
  14. {
  15. // ...
  16. // 注册 ILoggerProviders
  17. foreach (ILoggerProvider provider in providers)
  18. {
  19. AddProviderRegistration(provider, dispose: false);
  20. }
  21. // ...
  22. }
  23. public ILogger CreateLogger(string categoryName)
  24. {
  25. lock (_sync)
  26. {
  27. // 如果不存在,则 new
  28. if (!_loggers.TryGetValue(categoryName, out Logger logger))
  29. {
  30. logger = new Logger
  31. {
  32. Loggers = CreateLoggers(categoryName),
  33. };
  34. (logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);
  35. // 单例化 Logger<>
  36. _loggers[categoryName] = logger;
  37. }
  38. return logger;
  39. }
  40. }
  41. private void AddProviderRegistration(ILoggerProvider provider, bool dispose)
  42. {
  43. _providerRegistrations.Add(new ProviderRegistration
  44. {
  45. Provider = provider,
  46. ShouldDispose = dispose
  47. });
  48. // ...
  49. }
  50. private LoggerInformation[] CreateLoggers(string categoryName)
  51. {
  52. var loggers = new LoggerInformation[_providerRegistrations.Count];
  53. // 循环遍历所有 ILoggerProvider
  54. for (int i = 0; i < _providerRegistrations.Count; i++)
  55. {
  56. loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);
  57. }
  58. return loggers;
  59. }
  60. }

注意

  • 若要在Startup.Configure方法中记录日志,直接在参数上注入ILogger<Startup>即可。
  • 不支持在Startup.ConfigureServices方法中使用ILogger,因为此时DI容器还未配置完成。
  • 没有异步的日志记录方法。日志记录动作执行应该很快,不值的牺牲性能使用异步方法。如果日志记录动作比较耗时,如记录到MSSQL中,那么请不要直接写入MSSQL。你应该考虑先将日志写入到快速存储介质,如内存队列,然后通过后台工作线程将其从内存转储到MSSQL中。
  • 无法使用日志记录 API 在应用运行时更改日志记录配置。不过,一些配置提供程序(如文件配置提供程序)可重新加载配置,这可以立即更新日志记录配置。

小结

  • Host.CreateDefaultBuilder方法中,默认添加了ConsoleDebugEventSourceEventLog(仅Windows)共四种日志记录提供程序(Logger Provider)。
  • 通过注入服务ILogger<TCategoryName>,可以方便的进行日志记录。
  • 可以通过代码或配置对日志记录提供程序进行设置,如LogLevelFormatterName等。
  • 可以通过扩展方法AddFilter添加日志记录过滤器,允许你书写复杂的逻辑,来控制是否要记录日志。
  • 支持日志消息模板。
  • 对于控制台记录日志程序,.NET框架内置了Simple(默认)、SystemdJson三种日志输出格式。
  • .NET 6 预览版中新增了一个称为“编译时日志记录源生成”的功能,该功能非常实用,有兴趣的可以先去了解一下
  • 最后,给大家列举一些常用的日志开源中间件:

理解ASP.NET Core - 日志(Logging)的更多相关文章

  1. [转]ASP.NET Core 开发-Logging 使用NLog 写日志文件

    本文转自:http://www.cnblogs.com/Leo_wl/p/5561812.html ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 . ...

  2. ASP.NET Core 开发-Logging 使用NLog 写日志文件

    ASP.NET Core 开发-Logging 使用NLog 写日志文件. NLog 可以适用于 .NET Core 和 ASP.NET Core . ASP.NET Core已经内置了日志支持,可以 ...

  3. 理解ASP.NET Core - [03] Dependency Injection

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 依赖注入 什么是依赖注入 简单说,就是将对象的创建和销毁工作交给DI容器来进行,调用方只需要接 ...

  4. 理解ASP.NET Core - [01] Startup

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 准备工作:一份ASP.NET Core Web API应用程序 当我们来到一个陌生的环境,第一 ...

  5. 目录-理解ASP.NET Core

    <理解ASP.NET Core>基于.NET5进行整理,旨在帮助大家能够对ASP.NET Core框架有一个清晰的认识. 目录 [01] Startup [02] Middleware [ ...

  6. 理解ASP.NET Core - [04] Host

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 本文会涉及部分 Host 相关的源码,并会附上 github 源码地址,不过为了降低篇幅,我会 ...

  7. 理解ASP.NET Core - 路由(Routing)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 Routing Routing(路由):更准确的应该叫做Endpoint Routing,负责 ...

  8. .NET 黑魔法 - asp.net core 日志系统

    asp.net core 里如何记录日志呢? 这要从asp.net core的依赖注入说起,在asp.net core里的依赖注入真是无所不在,各种面向切面的接口与事件. 好吧,来点干货. 首先,我们 ...

  9. 理解 ASP.NET Core: 处理管道

    理解 ASP.NET Core 处理管道 在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式.这导致代码的逻辑大大简化,但是,对于熟悉面向对象 ...

随机推荐

  1. 传说中 VUE 的“语法糖”到底是啥?

    一.什么是语法糖? 语法糖也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语.指的是计算机语言中添加的一种语法,在不影响功能的情况下,添加某种简单的语 ...

  2. MySQL修改root密码的多种方法, mysql 导出数据库(包含视图)

    方法1: 用SET PASSWORD命令 mysql -u root mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('newpass ...

  3. centos7 发送邮件

    yum install sendmail mailx sharutils mutt libreport-plugin-mailx -y yum update libreport-plugin-mail ...

  4. django使用restframework序列化查询集合(querryset)

    第一: pip install djangorestframework 第二: 在setting.py文件中的app添加名为: 'rest_framework', 第三:再项目的APP下面新建名为(可 ...

  5. P2179-[NOI2012]骑行川藏【导数,二分】

    正题 题目链接:https://www.luogu.com.cn/problem/P2179 题目大意 给出\(E\)和\(n\)个\(s_i,k_i,u_i\)求一个序列\(v_i\)满足 \[\s ...

  6. Spring系列之Redis的两种集成方式

    在工作中,我们用到分布式缓存的时候,第一选择就是Redis,今天介绍一下SpringBoot如何集成Redis的,分别使用Jedis和Spring-data-redis两种方式. 一.使用Jedis方 ...

  7. CRM是什么,你有认真了解过CRM吗?

    这是CRM的一个简单定义 客户关系管理 (CRM)是一种用于管理公司与客户和潜在客户的所有关系和互动的技术.目标很简单:改善业务关系.CRM 系统可帮助公司与客户保持联系.简化流程并提高盈利能力. 当 ...

  8. Java(一)——基础知识

    引言 之前一直对 Java 怀有固执的偏见,以为 Java 是编写前端的语言,作为一个机械生,非常抗拒去学它. 但是最近接触一点以后,完全改观了先前的看法,于是开启了对 Java 的大学习. 一.数据 ...

  9. Windows 11正式版来了,下载、安装教程、一起奉上!

    Windows 11正式版已经发布了,今天给大家更新一波Win11系统的安装方法,其实和Win10基本一样,有多种方法.   安装Win11前请先查看电脑是否支持Win11系统,先用微软自家的PC H ...

  10. 洛谷2543AHOI2005]航线规划 (树剖+线段树+割边思路)

    这个题的思路还是比较巧妙的. 首先,我们发现操作只有删除和询问两种,而删除并不好维护连通性和割边之类的信息. 所以我们不妨像WC2006水管局长那样,将询问离线,然后把操作转化成加边和询问. 然后,我 ...