asp.net core mcroservices 架构之 分布式日志(二)之自定义日志开发
netcore日志原理
netcore的日志是作为一个扩展库存在的,每个组件都有它的入口,那么作为研究这个组件的入口是最好的,首先看两种方式:
这个是源码例子提供的。
1 var loggingConfiguration = new ConfigurationBuilder() 2 .SetBasePath(Directory.GetCurrentDirectory()) 3 .AddJsonFile("logging.json", optional: false, reloadOnChange: true) 4 .Build(); 5 6 // A Web App based program would configure logging via the WebHostBuilder. 7 // Create a logger factory with filters that can be applied across all logger providers. 8 var serviceCollection = new ServiceCollection() 9 .AddLogging(builder => 10 { 11 builder 12 .AddConfiguration(loggingConfiguration.GetSection("Logging")) 13 .AddFilter("Microsoft", LogLevel.Debug) 14 .AddFilter("System", LogLevel.Debug) 15 .AddFilter("SampleApp.Program", LogLevel.Debug) 16 .AddConsole(); 17 #if NET461 18 builder.AddEventLog(); 19 #elif NETCOREAPP2_2 20 #else 21 #error Target framework needs to be updated 22 #endif 23 }); 24 25 // providers may be added to a LoggerFactory before any loggers are created 26 27 28 var serviceProvider = serviceCollection.BuildServiceProvider(); 29 // getting the logger using the class's name is conventional 30 _logger = serviceProvider.GetRequiredService<ILogger<Program>>();
View Code
这个是咱们使用hostbuild中的扩展
var host = new WebHostBuilder().ConfigureAppConfiguration((webHostBuild,configBuild) =>{var env = webHostBuild.HostingEnvironment;configBuild.AddJsonFile("appsettings.json").AddJsonFile($"appsettings.{env.EnvironmentName}.json",optional:true,reloadOnChange:true).SetBasePath(Directory.GetCurrentDirectory());}).ConfigureLogging((hostingContext, logging) => {logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")).AddCustomizationLogger();}).UseKestrel((hostcon,opt)=> {opt.ListenAnyIP(5555);}).UseStartup<Startup>();var ihost= host.Build(); ihost.Run();
View Code
从以上两种可以看出,其实第二种WebHostBuilder是封装第一种的。所以咱们选择从第一个入口着手。
netcore日志设计思想是:LoggingBuider 构建,LoggerFactory和Logger类负责日志操作和Log提供程序的管理,Configuration是配置功能。
那么咱们基于以上的代码,看LoggingBuilder类
using Microsoft.Extensions.DependencyInjection;namespace Microsoft.Extensions.Logging {internal class LoggingBuilder : ILoggingBuilder{public LoggingBuilder(IServiceCollection services){Services = services;}public IServiceCollection Services { get; }} }
再看为Service做的扩展
using System; using Microsoft.Extensions.Logging; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options;namespace Microsoft.Extensions.DependencyInjection {/// <summary>/// Extension methods for setting up logging services in an <see cref="IServiceCollection" />./// </summary>public static class LoggingServiceCollectionExtensions{/// <summary>/// Adds logging services to the specified <see cref="IServiceCollection" />./// </summary>/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>public static IServiceCollection AddLogging(this IServiceCollection services){return AddLogging(services, builder => { });}/// <summary>/// Adds logging services to the specified <see cref="IServiceCollection" />./// </summary>/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>/// <param name="configure">The <see cref="ILoggingBuilder"/> configuration delegate.</param>/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure){if (services == null){throw new ArgumentNullException(nameof(services));}services.AddOptions();services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());//生成log的工厂services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); //Log泛型类services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<LoggerFilterOptions>>(new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));//Filter配置类configure(new LoggingBuilder(services)); //提供一个回掉方法,将logbuilder作为上下文传入return services;}} }
以上就是Log日志部分完成相当于注册功能的代码。
在入口中咱们看到了,回掉函数中放着加载配置和指定Logging提供程序。首先看加载配置:
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Options;namespace Microsoft.Extensions.Logging {/// <summary>/// Extension methods for setting up logging services in an <see cref="ILoggingBuilder" />./// </summary>public static class LoggingBuilderExtensions{/// <summary>/// Configures <see cref="LoggerFilterOptions" /> from an instance of <see cref="IConfiguration" />./// </summary>/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>/// <param name="configuration">The <see cref="IConfiguration" /> to add.</param>/// <returns>The builder.</returns>public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration){builder.AddConfiguration(); //这个是下面紧挨着代码块的实现,主要是根据类名或者别名,找出对应的configuration并加载builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>> (new LoggerFilterConfigureOptions(configuration)); //添加filter结点的配置builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>> (new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration));//更改后需要通知检控类刷新操作builder.Services.AddSingleton(new LoggingConfiguration(configuration));//将这个配直节放入servicereturn builder;}} }
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using Microsoft.Extensions.DependencyInjection.Extensions;namespace Microsoft.Extensions.Logging.Configuration {/// <summary>/// Extension methods for setting up logging services in an <see cref="ILoggingBuilder" />./// </summary>public static class LoggingBuilderConfigurationExtensions{/// <summary>/// 这个类就是添加根据类型或者是别名去找到配置节的功能 Adds services required to consume <see cref="ILoggerProviderConfigurationFactory"/> or <see cref="ILoggerProviderConfiguration{T}"/>/// </summary>public static void AddConfiguration(this ILoggingBuilder builder){builder.Services.TryAddSingleton<ILoggerProviderConfigurationFactory, LoggerProviderConfigurationFactory>();builder.Services.TryAddSingleton(typeof(ILoggerProviderConfiguration<>), typeof(LoggerProviderConfiguration<>));}} }
大家其实看到了,全部是往ServiceColl中扔东西,但是细想一下,这不就是根据部件组合出功能的思想吗?不同的部件会组合出新的功能。那日志是如何组合出来的?在咱们的入口类中有这一句:
_logger = serviceProvider.GetRequiredService<ILogger<Program>>();在上面为Service做扩展的那一段代码中已经为ILogger<>注册了服务,还有LoggerFactory也是。那么就从这个类入手,看序列图:
我们知道netcore把DI集成了,基础组件和业务类都可以作为服务来看待,然后进行控制服务以及服务的相互组合,比如在服务中
LoggerFactory类需要初始化,那么看看它的构造函数:
public LoggerFactory() : this(Enumerable.Empty<ILoggerProvider>()){}public LoggerFactory(IEnumerable<ILoggerProvider> providers) : this(providers, new StaticFilterOptionsMonitor(new LoggerFilterOptions())){}public LoggerFactory(IEnumerable<ILoggerProvider> providers, LoggerFilterOptions filterOptions) : this(providers, new StaticFilterOptionsMonitor(filterOptions)){}public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption){foreach (var provider in providers){AddProviderRegistration(provider, dispose: false);}_changeTokenRegistration = filterOption.OnChange(RefreshFilters);RefreshFilters(filterOption.CurrentValue);}
会不会奇怪为什么构造函数中 ILoggerProvider是以列表的形式出现?
再看看console提供程序的服务注册代码:
public static class ConsoleLoggerExtensions{/// <summary>/// Adds a console logger named 'Console' to the factory./// </summary>/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>public static ILoggingBuilder AddConsole(this ILoggingBuilder builder){builder.AddConfiguration();builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, ConsoleLoggerProvider>());builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<ConsoleLoggerOptions>, ConsoleLoggerOptionsSetup>());builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<ConsoleLoggerOptions>, LoggerProviderOptionsChangeTokenSource<ConsoleLoggerOptions, ConsoleLoggerProvider>>());return builder;}
它是以 TryAddEnumerable 的形式添加的,也就是说 ILoggerProvider 在服务中有一个列表。那么这个就清楚了,你可以添加
AddDebug AddConsole许多的提供程序,然后全部被注入进LoggerFacotry,LoggerFacotry会生成Logger然后传递给主Logger统一管理。
Add和TryAdd区别就是一个如果有相同的接口和实现,调用几次就会有几个服务,而后者不会,永远只会创建一个服务。
private void SetLoggerInformation(ref LoggerInformation loggerInformation, ILoggerProvider provider, string categoryName){loggerInformation.Logger = provider.CreateLogger(categoryName);loggerInformation.ProviderType = provider.GetType();loggerInformation.ExternalScope = provider is ISupportExternalScope;}private LoggerInformation[] CreateLoggers(string categoryName){var loggers = new LoggerInformation[_providerRegistrations.Count];for (int i = 0; i < _providerRegistrations.Count; i++){SetLoggerInformation(ref loggers[i], _providerRegistrations[i].Provider, categoryName);}ApplyRules(loggers, categoryName, 0, loggers.Length);return loggers;}
然后
public ILogger CreateLogger(string categoryName){if (CheckDisposed()){throw new ObjectDisposedException(nameof(LoggerFactory));}lock (_sync){if (!_loggers.TryGetValue(categoryName, out var logger)){logger = new Logger(this){Loggers = CreateLoggers(categoryName) //上面的代码块生成};_loggers[categoryName] = logger;}return logger;}}
二 netcore自定义日志开发
说了那么多,咱们开始动手自己做一个。咱们并不是新建一个服务,而是为Logging服务做一个扩展,
所以不用新建builder部分,而是为LoggerBuilder新建一个扩展,先看代码结构:
然后看看为LoggerBuilder做的扩展方法:
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Configuration; using Microsoft.Extensions.Logging.Console; using Microsoft.Extensions.Options;namespace Walt.Freamwork.Log {public static class CustomizationLoggerLoggerExtensions{/// <summary>/// Adds a console logger named 'Console' to the factory./// </summary>/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>public static ILoggingBuilder AddCustomizationLogger(this ILoggingBuilder builder){builder.AddConfiguration();builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, CustomizationLoggerProvider>());builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CustomizationLoggerOptions>, CustomizationLoggerOptionsSetup>());builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<ConsoleLoggerOptions>, LoggerProviderOptionsChangeTokenSource<ConsoleLoggerOptions, ConsoleLoggerProvider>>());return builder;}/// <summary>/// Adds a console logger named 'Console' to the factory./// </summary>/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>/// <param name="configure"></param>public static ILoggingBuilder AddCustomizationLogger(this ILoggingBuilder builder, Action<CustomizationLoggerOptions> configure){if (configure == null){throw new ArgumentNullException(nameof(configure));}builder.AddCustomizationLogger();builder.Services.Configure(configure);return builder;}} }
就像第一部分的原理,将你自己的CustomizationLoggerProvider服务添加进服务中,就完成了一半。配置文件和配置Token,
有了这个token,就可以监控到配置文件更改,从而引发change方法,让开发去做一些事情。那么看配置文件:
{"Logging": {"LogLevel": {"Default": "Debug","System": "Debug","Microsoft": "Debug"},"KafkaLog":{"Prix":"这是我的自定义日志提供程序"}} }
再看看配置类:
就两个参数,咱们配置了一个。再来看看配置安装程序:
仅此而已,然后就是上面的扩展方法,给注册就ok了。
如何用这些配置尼?看provider类
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.using System; using System.Collections.Concurrent; using System.Collections.Generic; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console.Internal; using Microsoft.Extensions.Options;namespace Walt.Freamwork.Log { // IConsoleLoggerSettings is obsolete #pragma warning disable CS0618 // Type or member is obsolete[ProviderAlias("KafkaLog")] //还记得上面讲原理是注入了providerconfiguration等类,就是根据这个别名去找配直节的。public class CustomizationLoggerProvider : ILoggerProvider, ISupportExternalScope{private readonly ConcurrentDictionary<string, CustomizationLogger> _loggers =new ConcurrentDictionary<string, CustomizationLogger>();private readonly Func<string, LogLevel, bool> _filter; private readonly CustomizationLoggerProcessor _messageQueue = new CustomizationLoggerProcessor();private static readonly Func<string, LogLevel, bool> trueFilter = (cat, level) => true;private static readonly Func<string, LogLevel, bool> falseFilter = (cat, level) => false;private IDisposable _optionsReloadToken;private bool _includeScopes; private string _prix;private IExternalScopeProvider _scopeProvider;public CustomizationLoggerProvider(IOptionsMonitor<CustomizationLoggerOptions> options) //这里自动和configuration中的值绑定后,被注入到这里来了。{// Filter would be applied on LoggerFactory level_filter = trueFilter;_optionsReloadToken = options.OnChange(ReloadLoggerOptions); //这个就是我说的需要注册token服务,然后配置更改就会激发这个方法ReloadLoggerOptions(options.CurrentValue);}private void ReloadLoggerOptions(CustomizationLoggerOptions options){_includeScopes = options.IncludeScopes; _prix=options.Prix;var scopeProvider = GetScopeProvider();foreach (var logger in _loggers.Values){logger.ScopeProvider = scopeProvider; }}private IEnumerable<string> GetKeyPrefixes(string name){while (!string.IsNullOrEmpty(name)){yield return name;var lastIndexOfDot = name.LastIndexOf('.');if (lastIndexOfDot == -1){yield return "Default";break;}name = name.Substring(0, lastIndexOfDot);}}private IExternalScopeProvider GetScopeProvider(){if (_includeScopes && _scopeProvider == null){_scopeProvider = new LoggerExternalScopeProvider();}return _includeScopes ? _scopeProvider : null;}public void Dispose(){_optionsReloadToken?.Dispose();_messageQueue.Dispose();}public void SetScopeProvider(IExternalScopeProvider scopeProvider){_scopeProvider = scopeProvider;}public ILogger CreateLogger(string name){return _loggers.GetOrAdd(name, CreateLoggerImplementation);}private CustomizationLogger CreateLoggerImplementation(string name){var includeScopes = _includeScopes; return new CustomizationLogger(name,null,includeScopes? _scopeProvider: null,_messageQueue,_prix); //这里就是你的终端类了,里面实现为kafka发消息或者写到redis都行。}} #pragma warning restore CS0618 }
下面打包然后上传到nuget服务:
调用方查看包
如果没有这个包 dotnet add添加,如果有,直接把project项目文件中的包版本改以下,restore就ok了。
下面进行调用:
运行:还记得配置文件中的
这个就是用来做测试的,目前输出还是用console:
运行结果:
customizationLogger的程序是从console那个提供程序中挖过来的,console中有很多的上一个版本的代码,而我肯定是需要新的,所以把Obsolete的代码全部删除。
这是console的程序。
日志第三部分讲集成kafka,希望大家关注和讨论。
转载于:https://www.cnblogs.com/ck0074451665/p/10171668.html
asp.net core mcroservices 架构之 分布式日志(二)之自定义日志开发相关推荐
- 学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听、接收和响应请求的...
学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一个服务器感受一下管道是如何监听.接收和响应请求的 原文:学习ASP.NET Core, 怎能不了解请求处理管道[3]: 自定义一 ...
- python:继承日志模块生成自定义日志
1 继承日志模块生成自定义日志 from __future__ import absolute_importimport os import sys import time import dateti ...
- fastdfs安装_用asp.net core结合fastdfs打造分布式文件存储系统
今天主要是对开发过程,以及对FastDFS这个通用的分布式文件存储服务的单机及集群安装部署过程做个总结.希望对想要自建分布式文件系统的朋友有所帮助. 什么是FastDFS 这里先简单介绍下分布式文件存 ...
- Asp.Net Core使用Skywalking实现分布式链路追踪
介绍 Skywalking 是 Apache 基金会下面的一个开源 APM 项目,是一套(APM)分布式追踪系统,提供了很多数据存储列如:Mysql,H2,Elasticsearch7 等.其中APM ...
- 用asp.net core结合fastdfs打造分布式文件存储系统
最近被安排开发文件存储微服务,要求是能够通过配置来无缝切换我们公司内部研发的文件存储系统,FastDFS,MongDb GridFS,阿里云OSS,腾讯云OSS等.根据任务紧急度暂时先完成了通过配置来 ...
- ASP.NET Core使用Jaeger实现分布式追踪
前言 最近我们公司的部分.NET Core的项目接入了Jaeger,也算是稍微完善了一下.NET团队的技术栈. 至于为什么选择Jaeger而不是Skywalking,这个问题我只能回答,大佬们说了算. ...
- client mac addr不能开机进不去系统_用asp.net core结合fastdfs打造分布式文件存储系统
今天主要是对开发过程,以及对FastDFS这个通用的分布式文件存储服务的单机及集群安装部署过程做个总结.希望对想要自建分布式文件系统的朋友有所帮助. 什么是FastDFS 这里先简单介绍下分布式文件存 ...
- 我心中的ASP.NET Core 新核心对象WebHost(二)
这是ASP.NET Core新核心对象系列的第二篇,上一篇 WebHost准备阶段 我们讲到了WebHostBuilder的初始化及配置.我们给WebHostBuilder进行以下配置 UseKest ...
- asp.net core MVC 过滤器之ActionFilter过滤器(二)
简介 Action过滤器将在controller的Action执行之前和之后执行相应的方法. 实现一个自定义Action过滤器 自定义一个全局异常过滤器需要实现IActionFilter接口 publ ...
最新文章
- C语言的单链表求交点
- android中的AIDL进程间通信
- React学习:生命周期、过滤器、event、axios-学习笔记
- 【 HDU - 5363】Key Set(水题,快速幂,组合数学)
- vs2012里用easyUI,显示不正常
- idea中使用逆向工程----三部曲
- SQL Server 分离
- 云课堂在登陆时显示服务器错误,我的云课堂不能登陆怎么解决
- 统计学中相关数学符号、希腊字母的标准读法
- python精准识别图片文字
- 对称矩阵特征向量正交的推导
- mysql表文件被删除,MySQL数据表InnoDB引擎表文件误删恢复
- 初学python:输入某年某月某日,判断这一天是这一年的第几天
- 什么是极大似然估计?
- 参加华为HCIP的培训班吗?
- MPLS和LDP基本配置
- 免费OCR图片文字识别小工具,一键提取图片中文字,支持多语言翻译和发票识别
- Dell(戴尔)笔记本加装内存条后出现警告:“Warning Message : Alter!The amount of system memory has changed”
- 在vue中使用高德地图添加窗体
- Data and system ownership in the CISSP
热门文章
- axure中备注线_1分钟K线、日K线、月K线……不同周期的K线图到底有啥用?
- java文件比较_Java 比较两个任意文件是否相同
- php判断版本号大小,通用javascript代码判断版本号是否在版本范围之间_javascript技巧...
- 一个 TypeScript keyof 泛型用法
- php 5.3新增的闭包语法介绍function() use() {}
- 运维小白死磕的专业术语,你真的理解透了吗?
- 《InsideUE4》GamePlay 架构(二)Level 和 World
- VMware中centos6.5 minimal 使用NAT模式联网
- jQuery Mobile 学习资料
- 八大妙招:改善企业网络安全