【Asp.NetCore源码】设计模式 - 提供者模式

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
日志服务 SLS,月写入数据量 50GB 1个月
简介:

【Asp.NetCore源码】设计模式 - 提供者模式

AspNetCore源代码发现日志模块的设计模式(提供者模式),特此记录

学习设计模式的好处是,我们可以容易扩展它达到我们要求,除了要知道如何扩展它,还应该在其他地方应用它

类图 & 分析

角色分析

日志工厂 ( LoggerFactory --> ILoggerFactory)

  • 提供注册提供者
  • 创建日志记录器(Logger)

日志记录器(Logger --> ILogger)

  • 写入日志记录(遍历所有日志提供者的Logger)
  • 这里所有注册的日志提供者聚合

日志提供者(ConsoleLoggerProvider --> ILoggerProvider)

  • 创建具体日志记录器

具体日志记录者(ConsoleLogger,EventLogLogger)

  • 将日志写入具体媒介(控制台,Windows事件日志)

现在来看看这个模式

  1. 提供标准的日志写入接口(ILogger)
  2. 提供日志提供者接口(ILoggerProvider)
  3. 提供注册提供者接口(ILoggerFactory.AddProvider)

这里只是列出部分类和方法,整个Logging要比这个还多,为什么写个日志要整那么多东西?

程序唯一不会变就是不断在变化,这个也是为什么要设计模式运用到程序当中的原因,让程序可扩展来应对这种变化。

AspNetCore内置 8种日志记录提供程序 ,但肯定还是远远不够,因为有的可能想把日志写在文本,有的想写在Mongodb,有的想写在ElasticSearch等等,Microsoft不可能把所有的都实现,就算实现也未必适合你的业务使用。

假设现在需要把日志写在Mongo,只需要

  1. 实现Mongodb的ILogger - 将日志写到Mongodb
  2. 实现Mongodb的ILoggerProvider - 创建Mongodb的Logger
  3. 把Provider注册到AspNetCore - ILoggerFactory.AddProvider

这里都是新增代码达到实现把日志写入到Mongodb,这就是6大设计原则之一对扩展开放(可以添加自己的日志),对修改封闭(不需要修改到内部的方法)

AspNetCore代码实现(只列出接口)

ILoggerFactory

ILogger CreateLogger(string categoryName);
void AddProvider(ILoggerProvider provider);
CreateLogger : 这个和ILoggerProvider提供的CreateLogger虽然都是现实ILogger接口,但是做的事情不一样,LoggerFactory创建的是Logger实例,里面聚合了具体写日志的Logger,遍历它们输出。

categoryName : 可以指定具体,若使用泛型相当于typeof(T).FullName,这个用于筛选过滤日志

AddProvider : 注册一个新的提供者,然后遍历现有的Logger,把新的Provider添加到现有logger里面

ILoggerProvider

ILogger CreateLogger(string categoryName);
CreateLogger : 用于创建具体写日志Logger(例如Console)

ILogger

void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter);
bool IsEnabled(LogLevel logLevel);
IDisposable BeginScope(TState state);
Log(....): 输出日志

bool IsEnabled : 指定的日志级别是否可用

IDisposable BeginScope() : 开启日志作用域,将这个域范围的日志都放一起

AspNetCore使用第三方日志组件(Log4Net)

AspNetCore使用Log4Net作为记录很简单,只需

  1. 安装包:

dotnet install Microsoft.Extensions.Logging.Log4Net.AspNetCore

  1. Configure 添加:

loggerFactory.AddLog4Net();

  1. 添加log4net.config配置文件

看看Microsoft.Extensions.Logging.Log4Net.AspNetCore如何实现ILogger和ILoggerProvider接口(代码有截取)

Log4NetProvider

public ILogger CreateLogger(string categoryName)

=> this.loggers.GetOrAdd(categoryName, this.CreateLoggerImplementation);

private Log4NetLogger CreateLoggerImplementation(string name)
{

var options = new Log4NetProviderOptions
{
    Name = name,
    LoggerRepository = this.loggerRepository.Name
};

options.ScopeFactory = new Log4NetScopeFactory(new Log4NetScopeRegistry());

return new Log4NetLogger(options);

}

Log4NetLogger

switch (logLevel)
{

case LogLevel.None:
    break;
case LogLevel.Critical:
    {
        string overrideCriticalLevelWith = options.OverrideCriticalLevelWith;
        if (!string.IsNullOrEmpty(overrideCriticalLevelWith) && overrideCriticalLevelWith.Equals(LogLevel.Critical.ToString(), StringComparison.OrdinalIgnoreCase))
        {
            log.Critical(text, exception);
        }
        else
        {
            log.Fatal(text, exception);
        }
        break;
    }
case LogLevel.Debug:
    log.Debug(text, exception);
    break;
case LogLevel.Error:
    log.Error(text, exception);
    break;
......

}

log4net的ILog是没有Trace和Critical方法,这两个是扩展方法,调用log4net log4net.Repository.Hierarchy.Logger.Log()方法

log4net 里面有Fatal代表日志最高级别,AspNetCore的Critical是日志最高级别,习惯log4net可能习惯用Fatal,这个时候只需要在注册的时候

loggerFactory.AddLog4Net(new Log4NetProviderOptions()
{

OverrideCriticalLevelWith = "Critical"

});
在Controller调用

_logger.LogCritical("Log Critical");
看看效果

2020-04-27 13:42:05,042 [10] FATAL LoggingPattern.Controllers.WeatherForecastController (null) - Log Critical

奇怪,没有按预期发生。这个组件是开源的,可以下载下来调试看看,github克隆下来 Microsoft.Extensions.Logging.Log4Net.AspNetCore

调试过程

  1. 将Microsoft.Extensions.Logging.Log4Net.AspNetCore.csproj的SignAssembly设置false(这个是程序集强签名)

false
 2. 将引用改成引用本地,我这里是放在跟项目平级

<ProjectReference Include="..\Microsoft.Extensions.Logging.Log4Net.AspNetCore\src\Microsoft.Extensions.Logging.Log4Net.AspNetCore\Microsoft.Extensions.Logging.Log4Net.AspNetCore.csproj" />

我这里是用VSCode,如果用VS不用这么麻烦

  1. 然后就可以打断点,在写日志和之前看到的那个判断打个断点
  1. 接下来就是看看这个值怎么来的

builder.Services.AddSingleton(new Log4NetProvider(options));

public Log4NetProvider(Log4NetProviderOptions options)
{
}

注册一个单例的Log4NetProvider,参入参数options,Logger是在Provider的CreateLogger创建,现在看看CreateLogger

public ILogger CreateLogger(string categoryName)

=> this.loggers.GetOrAdd(categoryName, this.CreateLoggerImplementation);

private Log4NetLogger CreateLoggerImplementation(string name)
{

var options = new Log4NetProviderOptions
{
    Name = name,
    LoggerRepository = this.loggerRepository.Name
};
options.ScopeFactory = new Log4NetScopeFactory(new Log4NetScopeRegistry());
return new Log4NetLogger(options);

}

到这里就清楚了,CreateLoggerImplementation里面又new了一个options,然后没有给OverrideCriticalLevelWith赋值(我认为这是个Bug,应该也很少人会用这个功能)这里之所以没用单例的options,因为要给每个Logger的目录名称动态赋值。

给这个库作者提了Issues和PR

添加自定义的日志记录器

假设现在需要把日志加入到Mongodb,只需完成下面几个步骤

  1. 添加Mongodb驱动,(dotnet-cli)

dotnet add package MongoDB.Driver

  1. 实现接口ILogger

public class MongodbLogger : ILogger
{

private readonly string _name;
private MongoDB.Driver.IMongoDatabase _database;

public MongodbLogger(string name, MongoDB.Driver.IMongoDatabase database)
{
    _name = name;
    _database = database;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
    var collection = _database.GetCollection<dynamic>(logLevel.ToString().ToLower());

    string message = formatter(state, exception);

    collection.InsertOneAsync(new
    {
        time = DateTime.Now,
        name = _name,
        message,
        exception
    });
}
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;

public System.IDisposable BeginScope<TState>(TState state) => NullScope.Instance;

}

  1. 实现ILoggerProvider接口

public class MongodbProvider : ILoggerProvider
{

private readonly ConcurrentDictionary<string, MongodbLogger> _loggers = new ConcurrentDictionary<string, MongodbLogger>();
private MongoDB.Driver.IMongoDatabase _database;
public MongodbProvider(MongoDB.Driver.IMongoDatabase database)
{
    _database = database;
}
public ILogger CreateLogger(string categoryName)
    => _loggers.GetOrAdd(categoryName, name => new MongodbLogger(categoryName, this._database));
public void Dispose() => this._loggers.Clear();

}

  1. 添加MongodbLogging扩展函数(非必须)

public static ILoggerFactory AddMongodb(this ILoggerFactory factory, string connetionString = "mongodb://127.0.0.1:27017/logging")
{

var mongoUrl = new MongoDB.Driver.MongoUrl(connetionString);
var client = new MongoDB.Driver.MongoClient(mongoUrl);

factory.AddProvider(new MongodbProvider(client.GetDatabase(mongoUrl.DatabaseName)));

return factory;

}

  1. Configure注册MongodbLogging

loggerFactory.AddMongodb();
运行效果

扩展

设计模式的好处是,我们可以容易扩展它达到我们要求,除了要知道如何扩展它,还应该在其他地方应用它,例如我们经常需要消息通知用户,但是通知渠道(提供者),在一开始未必全部知道,例如一开始只有短信,邮件通知,随着业务发展可能需要增加微信推送,提供者模式就很好应对这一种情况,很容易画出下面类图。

当需要扩展发送消息渠道,只需要实现ISenderProvider(哪个提供),ISender(如何发送)

当需要扩展发送消息渠道,只需要实现ISenderProvider(哪个提供),ISender(如何发送)

转发请标明出处:https://www.cnblogs.com/WilsonPan/p/12793220.html

示例代码:https://github.com/WilsonPan/AspNetCoreExamples/tree/master/LoggingPattern

相关实践学习
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
2月前
|
设计模式 Java API
【设计模式】JAVA Design Patterns——Combinator(功能模式)
【设计模式】JAVA Design Patterns——Combinator(功能模式)
|
2月前
|
设计模式 监控 Java
【设计模式】JAVA Design Patterns——Circuit Breaker(断路器模式)
【设计模式】JAVA Design Patterns——Circuit Breaker(断路器模式)
|
5天前
|
设计模式 Go
Go语言设计模式:使用Option模式简化类的初始化
在Go语言中,面对构造函数参数过多导致的复杂性问题,可以采用Option模式。Option模式通过函数选项提供灵活的配置,增强了构造函数的可读性和可扩展性。以`Foo`为例,通过定义如`WithName`、`WithAge`、`WithDB`等设置器函数,调用者可以选择性地传递所需参数,避免了记忆参数顺序和类型。这种模式提升了代码的维护性和灵活性,特别是在处理多配置场景时。
41 8
|
1月前
|
开发框架 前端开发 .NET
LIMS(实验室)信息管理系统源码、有哪些应用领域?采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码
集成于VS 2019,EXT.NET前端和ASP.NET后端,搭配MSSQL 2018数据库。系统覆盖样品管理、数据分析、报表和项目管理等实验室全流程。应用广泛,包括生产质检(如石化、制药)、环保监测、试验研究等领域。随着技术发展,现代LIMS还融合了临床、电子实验室笔记本和SaaS等功能,以满足复杂多样的实验室管理需求。
36 3
LIMS(实验室)信息管理系统源码、有哪些应用领域?采用C# ASP.NET dotnet 3.5 开发的一套实验室信息系统源码
|
13天前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 构造函数模式
js设计模式【详解】—— 构造函数模式
18 6
|
19天前
|
设计模式 存储 算法
设计模式学习心得之五种创建者模式(2)
设计模式学习心得之五种创建者模式(2)
15 2
|
20天前
|
设计模式 搜索推荐
工厂方法模式-大话设计模式
工厂方法模式-大话设计模式
11 1
|
25天前
|
设计模式 算法
行为型设计模式之模板模式
行为型设计模式之模板模式
|
2月前
|
设计模式 Java 数据库
【设计模式】JAVA Design Patterns——Converter(转换器模式)
转换器模式旨在实现不同类型间的双向转换,减少样板代码。它通过通用的Converter类和特定的转换器(如UserConverter)简化实例映射。Converter类包含两个Function对象,用于不同类型的转换,同时提供列表转换方法。当需要在逻辑上对应的类型间转换,或处理DTO、DO时,此模式尤为适用。
【设计模式】JAVA Design Patterns——Converter(转换器模式)
|
1月前
|
Web App开发 开发框架 .NET
ASP淘特二手房房地产系统源码
ASP淘特二手房房地产系统源码主要提供了房屋信息出售、出租、求购、求租、合租等信息的发布平台。 本系统已提供成熟的赢利模式,通过向中介会员提供发布信息平台收取会员费为网站的主要收入来源,中介会员申请开通后,可以添加经济人和管理中介公司所属的房源信息。可在线续费购买服务期(支付宝接口)、购买置顶等。
21 2