【AspNetCore源碼】設計模式 - 提供者模式

AspNetCore源代碼發現日誌模塊的設計模式(提供者模式),特此記錄html

學習設計模式的好處是,咱們能夠容易擴展它達到咱們要求,除了要知道如何擴展它,還應該在其餘地方應用它git

 

類圖 & 分析github

角色分析mongodb

日誌工廠 ( LoggerFactory --> ILoggerFactory)設計模式

- 提供註冊提供者微信

- 建立日誌記錄器(Logger)ide

 

日誌記錄器(Logger --> ILogger)函數

- 寫入日誌記錄(遍歷全部日誌提供者的Logger)學習

- 這裏全部註冊的日誌提供者聚合ui

 

日誌提供者(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");

看看效果

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. 而後就能夠打斷點,在寫日誌和以前看到的那個判斷打個斷點

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(如何發送)

相關文章
相關標籤/搜索