asp.net core mcroservices 架构之 分布式日志(二)之自定义日志开发
netcore日志原理
netcore的日志是作为一个扩展库存在的,每个组件都有它的入口,那么作为研究这个组件的入口是最好的,首先看两种方式:
这个是源码例子提供的。
var loggingConfiguration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("logging.json", optional: false, reloadOnChange: true)
.Build(); // A Web App based program would configure logging via the WebHostBuilder.
// Create a logger factory with filters that can be applied across all logger providers.
var serviceCollection = new ServiceCollection()
.AddLogging(builder =>
{
builder
.AddConfiguration(loggingConfiguration.GetSection("Logging"))
.AddFilter("Microsoft", LogLevel.Debug)
.AddFilter("System", LogLevel.Debug)
.AddFilter("SampleApp.Program", LogLevel.Debug)
.AddConsole();
#if NET461
builder.AddEventLog();
#elif NETCOREAPP2_2
#else
#error Target framework needs to be updated
#endif
}); // providers may be added to a LoggerFactory before any loggers are created var serviceProvider = serviceCollection.BuildServiceProvider();
// getting the logger using the class's name is conventional
_logger = serviceProvider.GetRequiredService<ILogger<Program>>();
这个是咱们使用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();
})
.UseStartup<Startup>();
var ihost= host.Build();
ihost.Run();
从以上两种可以看出,其实第二种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));//将这个配直节放入service return 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 = ; i < _providerRegistrations.Count; i++)
{
SetLoggerInformation(ref loggers[i], _providerRegistrations[i].Provider, categoryName);
} ApplyRules(loggers, categoryName, , 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 == -)
{
yield return "Default";
break;
}
name = name.Substring(, 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,希望大家关注和讨论。
asp.net core mcroservices 架构之 分布式日志(二)之自定义日志开发的更多相关文章
- asp.net core mcroservices 架构之 分布式日志(一)
一 简介 无论是微服务还是其他任何分布式系统,都需要一个统一处理日志的系统,这个系统 必须有收集,索引,分析查询的功能.asp .net core自己的日志是同步方式的,正如文档所言: 所以必须自己提 ...
- asp.net core mcroservices 架构之 分布式日志(三):集成kafka
一 kafka介绍 kafka是基于zookeeper的一个分布式流平台,既然是流,那么大家都能猜到它的存储结构基本上就是线性的了.硬盘大家都知道读写非常的慢,那是因为在随机情况下,线性下,硬盘的读写 ...
- asp.net core microservices 架构之 分布式自动计算(二)
一 简介 上一篇介绍了zookeeper如何进行分布式协调,这次主要讲解quartz使用zookeeper进行分布式计算,因为上一篇只是讲解原理,而这次实际使用, ...
- asp.net core microservices 架构之分布式自动计算(三)-kafka日志同步至elasticsearch和kibana展示
一 kafka consumer准备 前面的章节进行了分布式job的自动计算的概念讲解以及实践.上次分布式日志说过日志写进kafka,是需要进行处理,以便合理的进行展示,分布式日志的量和我们对日志的重 ...
- asp.net core microservices 架构之 分布式自动计算(一)
一:简介 自动计算都是常驻内存的,没有人机交互.我们经常用到的就是console job和sql job了.sqljob有自己的宿主,与数据库产品有很关联,暂时不提.console job使 ...
- .net core microservices 架构之 分布式
.net core microservices 架构之 分布式 一:简介 自动计算都是常驻内存的,没有人机交互.我们经常用到的就是console job和sql job了.sqljob有自己的宿 ...
- ASP.NET Core 使用 Redis 实现分布式缓存:Docker、IDistributedCache、StackExchangeRedis
ASP.NET Core 使用 Redis 实现分布式缓存:Docker.IDistributedCache.StackExchangeRedis 前提:一台 Linux 服务器.已安装 Docker ...
- ASP.NET Core 1.1 静态文件、路由、自定义中间件、身份验证简介
概述 之前写过一篇关于<ASP.NET Core 1.0 静态文件.路由.自定义中间件.身份验证简介>的文章,主要介绍了ASP.NET Core中StaticFile.Middleware ...
- 基于Asp.Net Core,利用ZXing来生成二维码的一般流程
本文主要介绍如何在.net环境下,基于Asp.Net Core,利用ZXing来生成二维码的一般操作.对二维码工作原理了解,详情见:https://blog.csdn.net/weixin_36191 ...
随机推荐
- Android:日常学习笔记(2)——分析第一个Android应用程序
Android:日常学习笔记(2)——分析第一个Android应用程序 Android项目结构 整体目录结构分析 说明: 除了APP目录外,其他目录都是自动生成的.APP目录的下的内容才是我们的工作重 ...
- 函数的调用规则(__cdecl,__stdcall,__fastcall,__pascal)
关于函数的调用规则(调用约定),大多数时候是不需要了解的,但是如果需要跨语言的编程,比如VC写的dll要delphi调用,则需要了解. microsoft的vc默认的是__cdecl方式,而windo ...
- Linux centos开机执行JAR Shell脚本
Linux centos开机执行shell脚本 Linux centos开机执行 java jar 1.编写jar执行脚本 vim start.sh 加入如下内容(根据自己真实路径与数据进行编写) ...
- NAS、SAN、DAS 说明
NAS 说明 1.NAS(Network Attached Storage:网络附属存储) 2.NAS 是一种采用直接与网络介质相连的特殊设备实现数据存储的机制. 3.NAS本身能够支持多种协议(如N ...
- 安装Discuz开源论坛
11.添加mysql普通用户 接着上篇的lamp这篇安装Discuz 配置虚拟主机 1.打开虚拟主机配置 [root@NFS-31 ~]# vim /usr/local/apache2/conf/ht ...
- 一个声明被new多次
如果声明一个指针变量,然后为这个变量new多次, eg:Type* A: A = new Type();//1 A = new Type();//2 最后变量A指向的是最后的那个对象,与前面new的无 ...
- FreeMarker 使用实例
以下内容全部是网上收集: FreeMarker的模板文件并不比HTML页面复杂多少,FreeMarker模板文件主要由如下4个部分组成: 1,文本:直接输出的部分 2,注释:<#-- ... - ...
- contenteditable支持度
contenteditable attribute (basic support) - Working Draft Global user stats*: Support: 86.71% Partia ...
- Codeforces Round #200 (Div. 1) BCD
为了锻炼个人能力奋力div1 为了不做原题从200开始 B 两个电线缠在一起了 能不能抓住两头一扯就给扯分开 很明显当len为odd的时候无解 当len为偶数的时候 可以任选一段长度为even的相同字 ...
- QT 学习记录:渐变-QLinearGradient,QRadialGradient,QConicalGradient)
http://blog.csdn.net/wangwei890702/article/details/8552482 QT:渐变 渐变,是指逐渐的,有规律性的变化,是一种规律性很强的现象.Qt提供了一 ...