netcore的日志是作为一个扩展库存在的,每个组件都有它的入口,那么作为研究这个组件的入口是最好的,首先看两种方式:

这个是源码例子提供的。

  1. 1 var loggingConfiguration = new ConfigurationBuilder()
  2. 2 .SetBasePath(Directory.GetCurrentDirectory())
  3. 3 .AddJsonFile("logging.json", optional: false, reloadOnChange: true)
  4. 4 .Build();
  5. 5
  6. 6 // A Web App based program would configure logging via the WebHostBuilder.
  7. 7 // Create a logger factory with filters that can be applied across all logger providers.
  8. 8 var serviceCollection = new ServiceCollection()
  9. 9 .AddLogging(builder =>
  10. 10 {
  11. 11 builder
  12. 12 .AddConfiguration(loggingConfiguration.GetSection("Logging"))
  13. 13 .AddFilter("Microsoft", LogLevel.Debug)
  14. 14 .AddFilter("System", LogLevel.Debug)
  15. 15 .AddFilter("SampleApp.Program", LogLevel.Debug)
  16. 16 .AddConsole();
  17. 17 #if NET461
  18. 18 builder.AddEventLog();
  19. 19 #elif NETCOREAPP2_2
  20. 20 #else
  21. 21 #error Target framework needs to be updated
  22. 22 #endif
  23. 23 });
  24. 24
  25. 25 // providers may be added to a LoggerFactory before any loggers are created
  26. 26
  27. 27
  28. 28 var serviceProvider = serviceCollection.BuildServiceProvider();
  29. 29 // getting the logger using the class's name is conventional
  30. 30 _logger = serviceProvider.GetRequiredService<ILogger<Program>>();

View Code

这个是咱们使用hostbuild中的扩展

  1. var host = new WebHostBuilder().ConfigureAppConfiguration((webHostBuild,configBuild) =>
  2. {
  3. var env = webHostBuild.HostingEnvironment;
  4. configBuild.AddJsonFile("appsettings.json")
  5. .AddJsonFile($"appsettings.{env.EnvironmentName}.json"
  6. ,optional:true,reloadOnChange:true)
  7. .SetBasePath(Directory.GetCurrentDirectory());
  8. }).ConfigureLogging((hostingContext, logging) => {
  9. logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"))
  10. .AddCustomizationLogger();
  11. }).UseKestrel((hostcon,opt)=> {
  12. opt.ListenAnyIP(5555);
  13. })
  14. .UseStartup<Startup>();
  15. var ihost= host.Build();
  16. ihost.Run();

View Code

从以上两种可以看出,其实第二种WebHostBuilder是封装第一种的。所以咱们选择从第一个入口着手。

 netcore日志设计思想是:LoggingBuider 构建,LoggerFactory和Logger类负责日志操作和Log提供程序的管理,Configuration是配置功能。

那么咱们基于以上的代码,看LoggingBuilder类

  1. using Microsoft.Extensions.DependencyInjection;
  2. namespace Microsoft.Extensions.Logging
  3. {
  4. internal class LoggingBuilder : ILoggingBuilder
  5. {
  6. public LoggingBuilder(IServiceCollection services)
  7. {
  8. Services = services;
  9. }
  10. public IServiceCollection Services { get; }
  11. }
  12. }

再看为Service做的扩展

  1. using System;
  2. using Microsoft.Extensions.Logging;
  3. using Microsoft.Extensions.DependencyInjection.Extensions;
  4. using Microsoft.Extensions.Options;
  5. namespace Microsoft.Extensions.DependencyInjection
  6. {
  7. /// <summary>
  8. /// Extension methods for setting up logging services in an <see cref="IServiceCollection" />.
  9. /// </summary>
  10. public static class LoggingServiceCollectionExtensions
  11. {
  12. /// <summary>
  13. /// Adds logging services to the specified <see cref="IServiceCollection" />.
  14. /// </summary>
  15. /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
  16. /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
  17. public static IServiceCollection AddLogging(this IServiceCollection services)
  18. {
  19. return AddLogging(services, builder => { });
  20. }
  21. /// <summary>
  22. /// Adds logging services to the specified <see cref="IServiceCollection" />.
  23. /// </summary>
  24. /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
  25. /// <param name="configure">The <see cref="ILoggingBuilder"/> configuration delegate.</param>
  26. /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
  27. public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
  28. {
  29. if (services == null)
  30. {
  31. throw new ArgumentNullException(nameof(services));
  32. }
  33. services.AddOptions();
  34. services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());//生成log的工厂
  35. services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); //Log泛型类
  36. services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(
  37. new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));//Filter配置类
  38. configure(new LoggingBuilder(services)); //提供一个回掉方法,将logbuilder作为上下文传入
  39. return services;
  40. }
  41. }
  42. }

以上就是Log日志部分完成相当于注册功能的代码。

在入口中咱们看到了,回掉函数中放着加载配置和制定Logging提供程序。首先看加载配置:

builder.AddConfiguration(loggingConfiguration.GetSection(“Logging”)) 
AddConfiguration是在 Logging.Configuration中的LoggingBuilderExtensions.cs
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3.  
  4. using Microsoft.Extensions.Configuration;
  5. using Microsoft.Extensions.DependencyInjection;
  6. using Microsoft.Extensions.Logging.Configuration;
  7. using Microsoft.Extensions.Options;
  8. namespace Microsoft.Extensions.Logging
  9. {
  10. /// <summary>
  11. /// Extension methods for setting up logging services in an <see cref="ILoggingBuilder" />.
  12. /// </summary>
  13. public static class LoggingBuilderExtensions
  14. {
  15. /// <summary>
  16. /// Configures <see cref="LoggerFilterOptions" /> from an instance of <see cref="IConfiguration" />.
  17. /// </summary>
  18. /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
  19. /// <param name="configuration">The <see cref="IConfiguration" /> to add.</param>
  20. /// <returns>The builder.</returns>
  21. public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
  22. {
  23. builder.AddConfiguration(); //这个是下面紧挨着代码块的实现,主要是根据类名或者别名,找出对应的configuration并加载
  24. builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>>
    (new LoggerFilterConfigureOptions(configuration)); //添加filter结点的配置
  25. builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>>
    (new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration));//更改后需要通知检控类刷新操作
  26. builder.Services.AddSingleton(new LoggingConfiguration(configuration));//将这个配直节放入service
  27. return builder;
  28. }
  29. }
  30. }
  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3.  
  4. using Microsoft.Extensions.DependencyInjection.Extensions;
  5. namespace Microsoft.Extensions.Logging.Configuration
  6. {
  7. /// <summary>
  8. /// Extension methods for setting up logging services in an <see cref="ILoggingBuilder" />.
  9. /// </summary>
  10. public static class LoggingBuilderConfigurationExtensions
  11. {
  12. /// <summary>
  13. /// 这个类就是添加根据类型或者是别名去找到配置节的功能 Adds services required to consume <see cref="ILoggerProviderConfigurationFactory"/> or <see cref="ILoggerProviderConfiguration{T}"/>
  14. /// </summary>
  15. public static void AddConfiguration(this ILoggingBuilder builder)
  16. {
  17. builder.Services.TryAddSingleton<ILoggerProviderConfigurationFactory
    , LoggerProviderConfigurationFactory>();
  18. builder.Services.TryAddSingleton(typeof(ILoggerProviderConfiguration<>)
    , typeof(LoggerProviderConfiguration<>));
  19. }
  20. }
  21. }

大家其实看到了,全部是往ServiceColl中扔东西,但是细想一下,这不就是根据部件组合出功能的思想吗?不同的部件会组合出新的功能。那日志是如何组合出来的?在咱们的入口类中有这一句:

_logger = serviceProvider.GetRequiredService<ILogger<Program>>();在上面为Service做扩展的那一段代码中已经为ILogger<>注册了服务,还有LoggerFactory也是。那么就从这个类入手,看序列图:

我们知道netcore把DI集成了,基础组件和业务类都可以作为服务来看待,然后进行控制服务以及服务的相互组合,比如在服务中

 LoggerFactory类需要初始化,那么看看它的构造函数:

  1. public LoggerFactory() : this(Enumerable.Empty<ILoggerProvider>())
  2. {
  3. }
  4. public LoggerFactory(IEnumerable<ILoggerProvider> providers) : this(providers, new StaticFilterOptionsMonitor(new LoggerFilterOptions()))
  5. {
  6. }
  7. public LoggerFactory(IEnumerable<ILoggerProvider> providers, LoggerFilterOptions filterOptions) : this(providers, new StaticFilterOptionsMonitor(filterOptions))
  8. {
  9. }
  10. public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption)
  11. {
  12. foreach (var provider in providers)
  13. {
  14. AddProviderRegistration(provider, dispose: false);
  15. }
  16. _changeTokenRegistration = filterOption.OnChange(RefreshFilters);
  17. RefreshFilters(filterOption.CurrentValue);
  18. }

会不会奇怪为什么构造函数中 ILoggerProvider是以列表的形式出现?

再看看console提供程序的服务注册代码:

  1. public static class ConsoleLoggerExtensions
  2. {
  3. /// <summary>
  4. /// Adds a console logger named 'Console' to the factory.
  5. /// </summary>
  6. /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
  7. public static ILoggingBuilder AddConsole(this ILoggingBuilder builder)
  8. {
  9. builder.AddConfiguration();
  10. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());
  11. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<ConsoleLoggerOptions>, ConsoleLoggerOptionsSetup>());
  12. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<ConsoleLoggerOptions>, LoggerProviderOptionsChangeTokenSource<ConsoleLoggerOptions, ConsoleLoggerProvider>>());
  13. return builder;
  14. }

它是以 TryAddEnumerable 的形式添加的,也就是说 ILoggerProvider 在服务中有一个列表。那么这个就清楚了,你可以添加

AddDebug AddConsole许多的提供程序,然后全部被注入进LoggerFacotry,LoggerFacotry会生成Logger然后传递给主Logger统一管理。

LoggerFactory有段代码 :
  1. private void SetLoggerInformation(ref LoggerInformation loggerInformation, ILoggerProvider provider, string categoryName)
  2. {
  3. loggerInformation.Logger = provider.CreateLogger(categoryName);
  4. loggerInformation.ProviderType = provider.GetType();
  5. loggerInformation.ExternalScope = provider is ISupportExternalScope;
  6. }
  7. private LoggerInformation[] CreateLoggers(string categoryName)
  8. {
  9. var loggers = new LoggerInformation[_providerRegistrations.Count];
  10. for (int i = 0; i < _providerRegistrations.Count; i++)
  11. {
  12. SetLoggerInformation(ref loggers[i], _providerRegistrations[i].Provider, categoryName);
  13. }
  14. ApplyRules(loggers, categoryName, 0, loggers.Length);
  15. return loggers;
  16. }

然后

  1. public ILogger CreateLogger(string categoryName)
  2. {
  3. if (CheckDisposed())
  4. {
  5. throw new ObjectDisposedException(nameof(LoggerFactory));
  6. }
  7. lock (_sync)
  8. {
  9. if (!_loggers.TryGetValue(categoryName, out var logger))
  10. {
  11. logger = new Logger(this)
  12. {
  13. Loggers = CreateLoggers(categoryName) //上面的代码块生成
  14. };
  15. _loggers[categoryName] = logger;
  16. }
  17. return logger;
  18. }
  19. }

 

 

 

 

        说了那么多,咱们开始动手自己做一个。咱们并不是新建一个服务,而是为Logging服务做一个扩展,

所以不用新建builder部分,而是为LoggerBuilder新建一个扩展,先看代码结构:

然后看看为LoggerBuilder做的扩展方法:

  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3.  
  4. using System;
  5. using Microsoft.Extensions.Configuration;
  6. using Microsoft.Extensions.DependencyInjection;
  7. using Microsoft.Extensions.DependencyInjection.Extensions;
  8. using Microsoft.Extensions.Logging;
  9. using Microsoft.Extensions.Logging.Configuration;
  10. using Microsoft.Extensions.Logging.Console;
  11. using Microsoft.Extensions.Options;
  12. namespace Walt.Freamwork.Log
  13. {
  14. public static class CustomizationLoggerLoggerExtensions
  15. {
  16. /// <summary>
  17. /// Adds a console logger named 'Console' to the factory.
  18. /// </summary>
  19. /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
  20. public static ILoggingBuilder AddCustomizationLogger(this ILoggingBuilder builder)
  21. {
  22. builder.AddConfiguration();
  23. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, CustomizationLoggerProvider>());
  24. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CustomizationLoggerOptions>, CustomizationLoggerOptionsSetup>());
  25. builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<ConsoleLoggerOptions>, LoggerProviderOptionsChangeTokenSource<ConsoleLoggerOptions, ConsoleLoggerProvider>>());
  26. return builder;
  27. }
  28. /// <summary>
  29. /// Adds a console logger named 'Console' to the factory.
  30. /// </summary>
  31. /// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
  32. /// <param name="configure"></param>
  33. public static ILoggingBuilder AddCustomizationLogger(this ILoggingBuilder builder, Action<CustomizationLoggerOptions> configure)
  34. {
  35. if (configure == null)
  36. {
  37. throw new ArgumentNullException(nameof(configure));
  38. }
  39. builder.AddCustomizationLogger();
  40. builder.Services.Configure(configure);
  41. return builder;
  42. }
  43. }
  44. }

就像第一部分的原理,将你自己的CustomizationLoggerProvider服务添加进服务中,就完成了一半。配置文件和配置Token,

有了这个token,就可以监控到配置文件更改,从而引发change方法,让开发去做一些事情。那么看配置文件:

  1. {
  2. "Logging": {
  3. "LogLevel": {
  4. "Default": "Debug",
  5. "System": "Debug",
  6. "Microsoft": "Debug"
  7. },
  8. "KafkaLog":{
  9. "Prix":"这是我的自定义日志提供程序"
  10. }
  11. }
  12. }

再看看配置类:

就两个参数,咱们配置了一个。再来看看配置安装程序:

仅此而已,然后就是上面的扩展方法,给注册就ok了。

如何用这些配置尼?看provider类

  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3.  
  4. using System;
  5. using System.Collections.Concurrent;
  6. using System.Collections.Generic;
  7. using Microsoft.Extensions.Logging;
  8. using Microsoft.Extensions.Logging.Console.Internal;
  9. using Microsoft.Extensions.Options;
  10. namespace Walt.Freamwork.Log
  11. {
  12. // IConsoleLoggerSettings is obsolete
  13. #pragma warning disable CS0618 // Type or member is obsolete
  14. [ProviderAlias("KafkaLog")] //还记得上面讲原理是注入了providerconfiguration等类,就是根据这个别名去找配直节的。
  15. public class CustomizationLoggerProvider : ILoggerProvider, ISupportExternalScope
  16. {
  17. private readonly ConcurrentDictionary<string, CustomizationLogger> _loggers =
  18. new ConcurrentDictionary<string, CustomizationLogger>();
  19. private readonly Func<string, LogLevel, bool> _filter;
  20. private readonly CustomizationLoggerProcessor _messageQueue =
  21. new CustomizationLoggerProcessor();
  22. private static readonly Func<string, LogLevel, bool> trueFilter = (cat, level) => true;
  23. private static readonly Func<string, LogLevel, bool> falseFilter = (cat, level) => false;
  24. private IDisposable _optionsReloadToken;
  25. private bool _includeScopes;
  26. private string _prix;
  27. private IExternalScopeProvider _scopeProvider;
  28. public CustomizationLoggerProvider(IOptionsMonitor<CustomizationLoggerOptions> options)
    //这里自动和configuration中的值绑定后,被注入到这里来了。
  29. {
  30. // Filter would be applied on LoggerFactory level
  31. _filter = trueFilter;
  32. _optionsReloadToken = options.OnChange(ReloadLoggerOptions); //这个就是我说的需要注册token服务,然后配置更改就会激发这个方法
  33. ReloadLoggerOptions(options.CurrentValue);
  34. }
  35. private void ReloadLoggerOptions(CustomizationLoggerOptions options)
  36. {
  37. _includeScopes = options.IncludeScopes;
  38. _prix=options.Prix;
  39. var scopeProvider = GetScopeProvider();
  40. foreach (var logger in _loggers.Values)
  41. {
  42. logger.ScopeProvider = scopeProvider;
  43. }
  44. }
  45. private IEnumerable<string> GetKeyPrefixes(string name)
  46. {
  47. while (!string.IsNullOrEmpty(name))
  48. {
  49. yield return name;
  50. var lastIndexOfDot = name.LastIndexOf('.');
  51. if (lastIndexOfDot == -1)
  52. {
  53. yield return "Default";
  54. break;
  55. }
  56. name = name.Substring(0, lastIndexOfDot);
  57. }
  58. }
  59. private IExternalScopeProvider GetScopeProvider()
  60. {
  61. if (_includeScopes && _scopeProvider == null)
  62. {
  63. _scopeProvider = new LoggerExternalScopeProvider();
  64. }
  65. return _includeScopes ? _scopeProvider : null;
  66. }
  67. public void Dispose()
  68. {
  69. _optionsReloadToken?.Dispose();
  70. _messageQueue.Dispose();
  71. }
  72. public void SetScopeProvider(IExternalScopeProvider scopeProvider)
  73. {
  74. _scopeProvider = scopeProvider;
  75. }
  76. public ILogger CreateLogger(string name)
  77. {
  78. return _loggers.GetOrAdd(name, CreateLoggerImplementation);
  79. }
  80. private CustomizationLogger CreateLoggerImplementation(string name)
  81. {
  82. var includeScopes = _includeScopes;
  83. return new CustomizationLogger(name,null
    ,includeScopes? _scopeProvider: null,_messageQueue,_prix); //这里就是你的终端类了,里面实现为kafka发消息或者写到redis都行。
  84. }
  85. }
  86. #pragma warning restore CS0618
  87. }

 

 

 

 下面打包然后上传到nuget服务:

 

 调用方查看包

 

如果没有这个包 dotnet add添加,如果有,直接把project项目文件中的包版本改以下,restore就ok了。

下面进行调用:

运行:还记得配置文件中的

这个就是用来做测试的,目前输出还是用console:

运行结果:

 

 

customizationLogger的程序是从console那个提供程序中挖过来的,console中有很多的上一个版本的代码,而我肯定是需要新的,所以把Obsolete的代码全部删除。

这是console的程序。

 

日志第三部分讲集成kafka,希望大家关注和讨论。

 

posted on 2018-12-25 15:01 一夜寒江 阅读() 评论() 编辑 收藏

版权声明:本文为ck0074451665原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ck0074451665/p/10171668.html