欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【AspNetCore源码】设计模式 - 提供者模式

程序员文章站 2022-05-04 11:57:56
今天看AspNetCore源代码发现日志模块的设计模式(提供者模式)。设计模式的好处是,我们可以容易扩展它达到我们要求,除了要知道如何扩展它,还应该在其他地方应用它, ......

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

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

 

类图 & 分析

【AspNetCore源码】设计模式 - 提供者模式

角色分析

日志工厂 ( loggerfactory --> iloggerfactory)

- 提供注册提供者

- 创建日志记录器(logger)

 

日志记录器(logger --> ilogger)

- 写入日志记录(遍历所有日志提供者的logger)

- 这里所有注册的日志提供者聚合

 

日志提供者(consoleloggerprovider --> iloggerprovider)

- 创建具体日志记录器

 

具体日志记录者(consolelogger,eventloglogger)

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

 

现在来看看这个模式

1. 提供标准的日志写入接口(ilogger)

2. 提供日志提供者接口(iloggerprovider)

3. 提供注册提供者接口(iloggerfactory.addprovider)

 

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

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

 

aspnetcore内置  ,但肯定还是远远不够,因为有的可能想把日志写在文本,有的想写在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");

看看效果

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(这个是程序集强签名)

<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. 然后就可以打断点,在写日志和之前看到的那个判断打个断点

【AspNetCore源码】设计模式 - 提供者模式

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();

运行效果

【AspNetCore源码】设计模式 - 提供者模式

 

扩展

   

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

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

 

【AspNetCore源码】设计模式 - 提供者模式

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