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<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
bool IsEnabled(LogLevel logLevel);
IDisposable BeginScope<TState>(TState state);

Log<TState>(....): 输出日志

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

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

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

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

1. 安装包:

dotnet install Microsoft.Extensions.Logging.Log4Net.AspNetCore

2. Configure 添加:

loggerFactory.AddLog4Net();

3. 添加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");

看看效果

-- ::, [] FATAL LoggingPattern.Controllers.WeatherForecastController (null) - Log Critical

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

调试过程

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

<SignAssembly>false</SignAssembly>

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

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

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

3. 然后就可以打断点,在写日志和之前看到的那个判断打个断点

4. 接下来就是看看这个值怎么来的

builder.Services.AddSingleton<ILoggerProvider>(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

2. 实现接口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;
}

3. 实现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();
}

4. 添加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;
}

5. Configure注册MongodbLogging

loggerFactory.AddMongodb();

运行效果

扩展

  

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

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

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

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

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

【AspNetCore源码】设计模式 - 提供者模式的更多相关文章

  1. Retrofit源码设计模式解析(下)

    本文将接着<Retrofit源码设计模式解析(上)>,继续分享以下设计模式在Retrofit中的应用: 适配器模式 策略模式 观察者模式 单例模式 原型模式 享元模式 一.适配器模式 在上 ...

  2. 《Android源码设计模式》学习笔记之ImageLoader

    微信公众号:CodingAndroid cnblog:http://www.cnblogs.com/angel88/ CSDN:http://blog.csdn.net/xinpengfei521 需 ...

  3. 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 百篇博客分析OpenHarmony源码 | v36.04

    百篇博客系列篇.本篇为: v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CP ...

  4. 《Android源码设计模式》--抽象工厂模式

    No1: 4种MediaPlayer Factory分别会生成不同的MediaPlayer基类:StagefrightPlayer.NuPlayerDriver.MidiFile和TestPlayer ...

  5. 《Android源码设计模式》--Builder模式

    No1: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 No2: 在Android源码中,最常用到的Builder模式就是AlertDialog.Builder No3: ...

  6. Retrofit源码设计模式解析(上)

    Retrofit通过注解的方法标记HTTP请求参数,支持常用HTTP方法,统一返回值解析,支持异步/同步的请求方式,将HTTP请求对象化,参数化.真正执行网络访问的是Okhttp,Okhttp支持HT ...

  7. AspNetCore源码解析_1_CORS中间件

    概述 什么是跨域 在前后端分离开发方式中,跨域是我们经常会遇到的问题.所谓的跨域,就是处于安全考虑,A域名向B域名发出Ajax请求,浏览器会拒绝,抛出类似下图的错误. JSONP JSONP不是标准跨 ...

  8. spark 源码编译 standalone 模式部署

    本文介绍如何编译 spark 的源码,并且用 standalone 的方式在单机上部署 spark. 步骤如下: 1. 下载 spark 并且解压 本文选择 spark 的最新版本 2.2.0 (20 ...

  9. Akka源码分析-ask模式

    在我之前的博文中,已经介绍过要慎用Actor的ask.这里我们要分析一下ask的源码,看看它究竟是怎么实现的. 开发时,如果要使用ask方法,必须要引入akka.pattern._,这样才能使用ask ...

随机推荐

  1. vue-父组件传递参数到子组件

    案例: 父组件 <template> <div id="app"> <h1>vuex</h1> <h3>count:{{ ...

  2. 《Java多线程编程实战指南(核心篇)》阅读笔记

    <Java多线程编程实战指南(核心篇)>阅读笔记 */--> <Java多线程编程实战指南(核心篇)>阅读笔记 Table of Contents 1. 线程概念 1.1 ...

  3. Springboot使用自定义注解实现简单参数加密解密(注解+HandlerMethodArgumentResolver)

    前言 我黄汉三又回来了,快半年没更新博客了,这半年来的经历实属不易,疫情当头,本人实习的公司没有跟员工共患难, 直接辞掉了很多人.作为一个实习生,本人也被无情开除了.所以本人又得重新准备找工作了. 算 ...

  4. SpringCloud入门(九): Zuul 上传&回退&异常处理&跨域

    Zuul的上传 1.构建一个上传类 import org.springframework.web.bind.annotation.PostMapping; import org.springframe ...

  5. 【tensorflow2.0】AutoGraph的机制原理

    有三种计算图的构建方式:静态计算图,动态计算图,以及Autograph. TensorFlow 2.0主要使用的是动态计算图和Autograph. 动态计算图易于调试,编码效率较高,但执行效率偏低. ...

  6. CF632(div.2)C. Eugene and an array

    https://codeforces.ml/contest/1333/problem/C 大概题意是规定和为0的数组为不合格数组,询问给定数组中共有多少个合格子数组. 解题 子数组的数量 一个长度为 ...

  7. Vulnhub DC-9靶机渗透

    信息搜集 nmap -sP 192.168.146.0/24 #主机发现 nmap -A 192.168.146.147 #扫描端口等信息 22端口过滤,80端口开放,同样的从80端口入手. 不是现成 ...

  8. java web数据库的增删改查详细

    本次课上实验是完成数据库的增删改查. 包括增加用户信息.删除用户信息.多条件查找用户信息.修改用户信息(主要是复选框单选框等的相关操作.) 下面下看一下各个界面的样子. 总页面:显示全部页面:增加页面 ...

  9. 记一次pgsql中查询优化(子查询)

    记一次pgsql的查询优化 前言 这是一个子查询的场景,对于这个查询我们不能避免子查询,下面是我一次具体的优化过程. 优化策略 1.拆分子查询,将需要的数据提前在cte中查询出来 2.连表查询,直接去 ...

  10. 绕过CDN查找真实 IP 姿势总结

    返回域名解析对应多个 IP 地址,网站可能部署CDN业务,我们就需要bypass CDN,去查找真正的服务器ip地址 0x01.域名搜集 由于成本问题,可能某些厂商并不会将所有的子域名都部署 CDN, ...