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 ...
随机推荐
- js hash
1)新建hash hash= { name : "image", "number" : &q ...
- Yii2 自定义独立验证器
新建一个文件: ?php /** * author : forecho <caizhenghai@gmail.com> * createTime : 2015/7/1 14:54 * de ...
- PAT 天梯赛 L2-024. 部落 【并查集】
题目链接 https://www.patest.cn/contests/gplt/L2-024 题意 给出 几个不同的圈子,然后 判断 有哪些人 是属于同一个部落的,或者理解为 ,有哪些人 是有关系的 ...
- asp.net 下载图片
public class DownLoad : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Res ...
- GP DBA基本操作
1.查看队列情况 SELECT * FROM gp_toolkit.gp_resqueue_status; 如果出现了资源占用大于1.1 e+11SQL,则可能存在不合理执行计划的SQL, 基本跑不出 ...
- eclipse修改项目默认编码为UTF-8
1.windows->Preferences...打开"首选项"对话框,左侧导航树,导航到general->Workspace,右侧 Text file encodin ...
- CSS3 3D旋转下拉菜单
在线演示 本地下载
- Ubuntu 15.10环境下安装Hive
1. 安装MySQL sudo tar -xzvf mysql-5.7.13-linux-glibc2.5-x86_64.tar.gz sudo mv mysql-5.7.13-linux-glibc ...
- 模型融合之blending和stacking
1. blending 需要得到各个模型结果集的权重,然后再线性组合. """Kaggle competition: Predicting a Biological Re ...
- kubernetes liveness readiness
Liveness Probe(存活探针):用于判断容器是否存货(running状态),如果LivenessProbe探测到容器不健康,则kubelet将杀掉该容器,并根据容器的重启策略做相应的处理.如 ...