优化 .NET Core logging 中的泛型 logger
優化 .NET Core?logging 中的泛型 logger
Intro
在微軟的 logging 組件中,我們可以比較方便的使用泛型 Logger,如:ILogger<Generic> 這樣的,
但是如果泛型 Logger 的類型是一個泛型類型就會有些問題,具體的泛型參數不會作為 categoryName 的一部分,我們可以實現一個自己的 ILogger<T> 來改變這個行為,詳細可以參考下面的介紹
Reproduce
這個問題非常好重現,只需要一個測試的泛型類就可以了,我寫了一個簡單的測試類,定義如下:
private?class?GenericTest<T> {private?readonly?ILogger<GenericTest<T>>?_logger;public?GenericTest(ILogger<GenericTest<T>>?logger){_logger?=?logger;}public?void?Test()?=>?_logger.LogInformation("test"); }測試代碼如下:
using?var?services?=?new?ServiceCollection().AddLogging(builder?=>?builder.AddConsole()).AddSingleton(typeof(GenericTest<>)).BuildServiceProvider(); services.GetRequiredService<GenericTest<int>>().Test(); services.GetRequiredService<GenericTest<string>>().Test();這里使用了兩個泛型類型,一個泛型參數是 int,一個是 string,來看上面代碼的輸出結果吧,輸出結果如下:
可以看到,默認的日志行為我們沒有辦法區分泛型類的泛型參數具體是什么,這對于我們來說有時候是很不方便的
What's inside
我們可以在 Github 上找到 logging 組件的源代碼,可以參考:https://github.com/dotnet/runtime/blob/fa06656c41947e22fc6efd909cce0a6a180f1078/src/libraries/Microsoft.Extensions.Logging.Abstractions/src/LoggerT.cs
通過源碼我們可以看到默認的行為,并不會記錄泛型參數,經過測試如果我們需要包含泛型參數信息只需要把 includeGenericParameters 參數設置為 true 即可,既然明確了如何實現我們期望的效果改起來就會很簡單
Cutom Generic Logger
微軟的 Logging 非常的依賴注入,泛型的 Logger 也是依賴注入的,我們只需要注入自己的泛型 Logger 實現就可以代替默認的行為了,可以參考:https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Logging/src/LoggingServiceCollectionExtensions.cs#L42
為了不造成 breaking change,我們可以加一個配置,默認還是與微軟現在的行為保持一致,針對想要區分的類型使用帶泛型參數的行為,實現代碼如下:
//?泛型?logger?配置 public?sealed?class?GenericLoggerOptions {//?返回?true?則使用帶泛型參數的?typeName,否則使用默認的行為public?Func<Type,?bool>??FullNamePredict?{?get;?set;?} }internal?sealed?class?GenericLogger<T>?:?ILogger<T> {private?readonly?ILogger?_logger;///?<summary>///?Creates?a?new?<see?cref="GenericLogger{T}"/>.///?</summary>///?<param?name="factory">The?factory.</param>///?<param?name="options">GenericLoggerOptions</param>public?GenericLogger(ILoggerFactory?factory,?IOptions<GenericLoggerOptions>?options){if?(factory?==?null){throw?new?ArgumentNullException(nameof(factory));}//?通過配置的委托來判斷是否要包含泛型參數var?includeGenericParameters?=?options.Value.FullNamePredict?.Invoke(typeof(T))?==?true;_logger?=?factory.CreateLogger(TypeHelper.GetTypeDisplayName(typeof(T),?includeGenericParameters:?includeGenericParameters,?nestedTypeDelimiter:?'.'));}///?<inheritdoc?/>IDisposable?ILogger.BeginScope<TState>(TState?state){return?_logger.BeginScope(state);}///?<inheritdoc?/>bool?ILogger.IsEnabled(LogLevel?logLevel){return?_logger.IsEnabled(logLevel);}///?<inheritdoc?/>void?ILogger.Log<TState>(LogLevel?logLevel,?EventId?eventId,?TState?state,?Exception??exception,?Func<TState,?Exception?,?string>?formatter){_logger.Log(logLevel,?eventId,?state,?exception,?formatter);}public?void?Log<TState>(LogLevel?logLevel,?EventId?eventId,?TState?state,?Exception?exception,?Func<TState,?Exception,?string>?formatter)?=>?throw?new?NotImplementedException();public?bool?IsEnabled(LogLevel?logLevel)?=>?throw?new?NotImplementedException();public?IDisposable?BeginScope<TState>(TState?state)?=>?throw?new?NotImplementedException(); }TypeHelper 中的方法就是微軟 Logging 中引用的 TypeNameHelper,因為是 internal,所以單獨拷出來一份,
上面的 Logger 與微軟默認的 logger 唯一的不同之處就在于多了一個配置。。
為了使用起來方便,定義了一個 ILoggingBuilder 的擴展方法,定義如下:
public?static?ILoggingBuilder?UseCustomGenericLogger(this?ILoggingBuilder?loggingBuilder,?Action<GenericLoggerOptions>?genericLoggerConfig) {Guard.NotNull(loggingBuilder,?nameof(loggingBuilder));Guard.NotNull(genericLoggerConfig,?nameof(genericLoggerConfig));loggingBuilder.Services.Configure(genericLoggerConfig);loggingBuilder.Services.AddSingleton(typeof(ILogger<>),?typeof(GenericLogger<>));return?loggingBuilder; }好了,現在我們來測試一下我們自己的泛型 logger 吧,測試代碼如下:
using?var?services?=?new?ServiceCollection().AddLogging(builder?=>?builder.AddConsole().UseCustomGenericLogger(options?=>?options.FullNamePredict?=?_?=>?true)).AddSingleton(typeof(GenericTest<>)).BuildServiceProvider(); services.GetRequiredService<GenericTest<int>>().Test(); services.GetRequiredService<GenericTest<string>>().Test();輸出結果如下:
可以看到現在的輸出日志中已經包含了泛型類型的泛型參數,如果你對自己名稱還不夠滿意,也可以自定義 GetTypeDisplayName 的行為
More
上面的測試代碼有需要的可以從 Github 上獲取:https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/DotNetCoreSample/LoggerTest.cs#L37
感覺泛型參數還是記錄一下的比較好,這樣我們才能知道具體是哪一個類型打印出來的日志,像第一種方式打印出來的日志,完全就是一臉懵逼,真正出現了問題,完全不知道是哪一個類型的日志,只能靠猜了,這體驗就太不好了,不過還好我們可以比較方便的進行定制。
不知道你是否也有這樣的想法呢,在 Github 上提了一個 issue https://github.com/dotnet/runtime/issues/51368,如果感興趣,可以關注一下 ,留下你的看法
References
https://github.com/dotnet/runtime/issues/51368
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/DotNetCoreSample/LoggerTest.cs#L37
總結
以上是生活随笔為你收集整理的优化 .NET Core logging 中的泛型 logger的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Monitor 监测CPU与内存
- 下一篇: 记一次 .NET医院公众号程序 线程CP