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

今天看AspNetCore源代碼發(fā)現(xiàn)日志模塊的設計模式(提供者模式),特此記錄

類圖 & 分析


僅截取部分類&方法

角色分析

日志工廠 ( LoggerFactory --> ILoggerFactory)
  • 提供注冊提供者
  • 創(chuàng)建日志記錄器(Logger)
日志記錄器(Logger --> ILogger)
  • 寫入日志記錄(遍歷所有日志提供者的Logger)
  • 這里所有注冊的日志提供者聚合
日志提供者(ConsoleLoggerProvider --> ILoggerProvider)
  • 創(chuàng)建具體日志記錄器
具體日志記錄者(ConsoleLogger赢赊,EventLogLogger)
  • 將日志寫入具體媒介(控制臺棒呛,Windows事件日志)

現(xiàn)在來看看這個模式

  1. 提供標準的日志寫入接口(ILogger)
  2. 提供日志提供者接口(ILoggerProvider)
  3. 提供注冊提供者接口(ILoggerFactory.AddProvider)

這里只是列出部分類和方法,整個Logging要比這個還多域携,為什么寫個日志要整那么多東西簇秒?

程序唯一不會變就是不斷在變化,這個也是為什么要設計模式運用到程序當中的原因秀鞭,讓程序可擴展來應對這種變化趋观。

AspNetCore內置8種日志記錄提供程序,但肯定還是遠遠不夠锋边,因為有的可能想把日志寫在文本皱坛,有的想寫在Mongodb,有的想寫在ElasticSearch等等豆巨,Microsoft不可能把所有的都實現(xiàn)剩辟,就算實現(xiàn)也未必適合你的業(yè)務使用。
假設現(xiàn)在需要把日志寫在Mongo往扔,只需要

  1. 實現(xiàn)Mongodb的ILogger - 將日志寫到Mongodb
  2. 實現(xiàn)Mongodb的ILoggerProvider - 創(chuàng)建Mongodb的Logger
  3. 把Provider注冊到AspNetCore - ILoggerFactory.AddProvider
    這里都是新增代碼達到實現(xiàn)把日志寫入到Mongodb贩猎,這就是6大設計原則之一對擴展開放(可以添加自己的日志),對修改封閉(不需要修改到內部的方法)
AspNetCore代碼實現(xiàn)(只列出接口)

ILoggerFactory

 ILogger CreateLogger(string categoryName);
 void AddProvider(ILoggerProvider provider);

CreateLogger : 這個和ILoggerProvider提供的CreateLogger雖然都是現(xiàn)實ILogger接口萍膛,但是做的事情不一樣吭服,LoggerFactory創(chuàng)建的是Logger實例,里面聚合了具體寫日志的Logger蝗罗,遍歷它們輸出艇棕。
categoryName : 可以指定具體,若使用泛型相當于typeof(T).FullName串塑,這個用于篩選過濾日志

AddProvider : 注冊一個新的提供者沼琉,然后遍歷現(xiàn)有的Logger,把新的Provider添加到現(xiàn)有l(wèi)ogger里面

ILoggerProvider

 ILogger CreateLogger(string categoryName);

CreateLogger : 用于創(chuàng)建具體寫日志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如何實現(xiàn)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;
        case LogLevel.Information:
            log.Info(text, exception);
            break;
        case LogLevel.Warning:
            log.Warn(text, exception);
            break;
        case LogLevel.Trace:
            log.Trace(text, exception);
            break;
        default:
            log.Warn($"Encountered unknown log level {logLevel}, writing out as Info.");
            log.Info(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

奇怪,沒有按預期發(fā)生借杰。這個組件是開源的过吻,可以下載下來調試看看
github克隆下來 Microsoft.Extensions.Logging.Log4Net.AspNetCore

調試過程

  1. 將Microsoft.Extensions.Logging.Log4Net.AspNetCore.csproj的SignAssembly設置false(這個是程序集強簽名)
<SignAssembly>false</SignAssembly>
  1. 將引用改成引用本地,我這里是放在跟項目平級
  <ItemGroup>
    <ProjectReference Include="..\Microsoft.Extensions.Logging.Log4Net.AspNetCore\src\Microsoft.Extensions.Logging.Log4Net.AspNetCore\Microsoft.Extensions.Logging.Log4Net.AspNetCore.csproj" />
  </ItemGroup>

我這里是用VSCode,如果用VS不用這么麻煩

  1. 然后就可以打斷點纤虽,在寫日志和之前看到的那個判斷打個斷點


    這個值為空乳绕,導致都是寫Faltal
  2. 接下來就是看看這個值怎么來的

builder.Services.AddSingleton<ILoggerProvider>(new Log4NetProvider(options));
public Log4NetProvider(Log4NetProviderOptions options)
{
}

注冊一個單例的Log4NetProvider,參入?yún)?shù)options逼纸,Logger是在Provider的CreateLogger創(chuàng)建洋措,現(xiàn)在看看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的目錄名稱動態(tài)賦值贺嫂。
給這個庫作者提了Issues和PR

添加自定義的日志記錄器


假設現(xiàn)在需要把日志加入到Mongodb

  1. 添加Mongodb驅動
dotnet add package MongoDB.Driver
  1. 實現(xiàn)接口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. 實現(xiàn)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擴展函數(shù)(非必須)
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();
運行效果

擴展


設計模式的好處是滓鸠,我們可以容易擴展它達到我們要求,除了要知道如何擴展它第喳,還應該在其他地方應用它糜俗,例如我們經常需要消息通知用戶,但是通知渠道(提供者)曲饱,在一開始未必全部知道悠抹,例如一開始只有短信,郵件通知扩淀,隨著業(yè)務發(fā)展可能需要增加微信推送锌钮,提供者模式就很好應對這一種情況,很容易畫出下面類圖引矩。


消息發(fā)送簡單類圖

當需要擴展發(fā)送消息渠道梁丘,只需要實現(xiàn)ISenderProvider(哪個提供),ISender(如何發(fā)送)

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末旺韭,一起剝皮案震驚了整個濱河市氛谜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌区端,老刑警劉巖值漫,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異织盼,居然都是意外死亡杨何,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門沥邻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來危虱,“玉大人,你說我怎么就攤上這事唐全“u危” “怎么了蕊玷?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長弥雹。 經常有香客問我垃帅,道長,這世上最難降的妖魔是什么剪勿? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任贸诚,我火速辦了婚禮,結果婚禮上厕吉,老公的妹妹穿的比我還像新娘酱固。我一直安慰自己,他們只是感情好赴涵,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布媒怯。 她就那樣靜靜地躺著,像睡著了一般髓窜。 火紅的嫁衣襯著肌膚如雪扇苞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天寄纵,我揣著相機與錄音鳖敷,去河邊找鬼。 笑死程拭,一個胖子當著我的面吹牛定踱,可吹牛的內容都是我干的。 我是一名探鬼主播恃鞋,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼崖媚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恤浪?” 一聲冷哼從身側響起畅哑,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎水由,沒想到半個月后荠呐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡砂客,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年泥张,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鞠值。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡媚创,死狀恐怖,靈堂內的尸體忽然破棺而出齿诉,到底是詐尸還是另有隱情筝野,我是刑警寧澤晌姚,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布粤剧,位于F島的核電站歇竟,受9級特大地震影響,放射性物質發(fā)生泄漏抵恋。R本人自食惡果不足惜焕议,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弧关。 院中可真熱鬧盅安,春花似錦、人聲如沸世囊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽株憾。三九已至蝙寨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嗤瞎,已是汗流浹背墙歪。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贝奇,地道東北人虹菲。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像掉瞳,于是被迫代替她去往敵國和親毕源。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容