.NET Core 依賴注入改造(3)- ILogger

日志其實(shí)可以算是一個(gè)獨(dú)立的組件,但它與IOC/DI又有著緊密的聯(lián)系,所以我吧日志也算到DI改造里了。

.NET Core 依賴注入改造(1)- 命名服務(wù)
.NET Core 依賴注入改造(2)- 委托轉(zhuǎn)換
.NET Core 依賴注入改造(3)- ILogger
.NET Core 依賴注入改造(4)- ActivatorUtilities
.NET Core 依賴注入改造(5)- Context

.NET Core 依賴注入改造(附1)- Autowired

一、

其實(shí)現(xiàn)在的日志已經(jīng)算是比較好用的了;
使用也很簡(jiǎn)單

var provider = new ServiceCollection()
                    .AddLogging(x => x.SetMinimumLevel(0))
                    .BuildServiceProvider();

var factory = provider.GetService<ILoggerFactory>();
factory.AddConsole();
factory.AddDebug();

var logger = provider.GetService<ILoggerFactory>()?.CreateLogger<Program>();

logger.LogDebug("debug test...");
logger.LogError(new Exception("測(cè)試錯(cuò)誤"), "debug error...");

但是依然有可以簡(jiǎn)化的地方

二、

拓展方法的福,我們可以在不修改的源碼的基礎(chǔ)上增加一些非常實(shí)用的功能
先從添加Logger和獲取Logger開始

public static class LoggingExtensions
{
   /// <summary>
   /// 配置日志
   /// </summary>
   public static IServiceProvider ConfigLogger(this IServiceProvider serviceProvider, Action<ILoggerFactory> configure)
   {
       if (serviceProvider?.GetService(typeof(ILoggerFactory)) is ILoggerFactory factory)
       {
           configure(factory);
       }
       return serviceProvider;
   }

   /// <summary>
   /// 獲取日志服務(wù)
   /// </summary>
   public static ILogger GetLogger(this IServiceProvider serviceProvider, string categoryName)
   {
       if (serviceProvider?.GetService(typeof(ILoggerFactory)) is ILoggerFactory factory)
       {
           return factory.CreateLogger(categoryName);
       }
       //如果不存在任何服務(wù), 則返回默認(rèn)服務(wù)
       return new ConsoleLogger(categoryName);
   }

   /// <summary>
   /// 獲取日志服務(wù)
   /// </summary>
   public static ILogger GetLogger(this IServiceProvider serviceProvider) =>
       serviceProvider.GetLogger(new StackFrame(1).GetMethod()?.ReflectedType);

   /// <summary>
   /// 獲取日志服務(wù)
   /// </summary>
   public static ILogger GetLogger(this IServiceProvider serviceProvider, Type type) =>
       serviceProvider.GetLogger(TypeNameHelper.GetTypeDisplayName(type));

   /// <summary>
   /// 獲取日志服務(wù)
   /// </summary>
   public static ILogger GetLogger<T>(this IServiceProvider serviceProvider) =>
       serviceProvider.GetLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
}

之前的代碼做如下調(diào)整


tips:即使不提供類型也可以獲得一個(gè)ILogger

public static ILogger GetLogger(this IServiceProvider serviceProvider) =>
    serviceProvider.GetLogger(new StackFrame(1).GetMethod()?.ReflectedType);

三、

接下來(lái)我想為日志加上文件和行號(hào)。

先來(lái)看看ILogger接口的定義

ILogger

寫日志的只有一個(gè)Log方法,所以LogDebugLogError這些方法,應(yīng)該是擴(kuò)展方法
Logger擴(kuò)展方法

那么如果要加上行號(hào),可以增加一些自己的拓展方法

  1. 放棄對(duì)默認(rèn)格式化方式的支持logger.LogDebug("id: {id}, name: {name}", 1, "blqw");
    畢竟 C#6.0的 Interpolated Strings 已經(jīng)很好用了$"id: {id}, name: {name}"
  2. 使用調(diào)用方信息CallerMemberName,CallerFilePath,CallerLineNumber三個(gè)特性獲取文件名,行號(hào)等信息
  3. 將文件名,行號(hào)等信息通過(guò)EventId傳遞到日志組件
    事件ID行號(hào)
    事件名文件名->方法名
  4. 聲明新的接口以支持任意類型的自定義格式化功能

大致就是這個(gè)樣子


自定義擴(kuò)展方法

GetEventName

private static string GetEventName(string path, string member)
{
    using (StringBuilderPool.Pop(out var builder))
    {
        if (string.IsNullOrWhiteSpace(path) == false)
        {
            builder.Append(Path.GetFileNameWithoutExtension(path.Trim()));
        }
        if (string.IsNullOrWhiteSpace(member) == false)
        {
            if (builder.Length > 0)
            {
                builder.Append("->");
            }
            builder.Append(Path.GetFileNameWithoutExtension(member.Trim()));
        }
        return builder.ToString();
    }
}

StringBuilderPool看這里

Format

private static string Format(object state, Exception exception)
{
    var formatter = Startup.GetFormatter(state?.GetType()) ?? DefaultFormatter;
    return formatter(state, exception);
}
interface ILogFormatService
{
    Func<object, Exception, string> GetFormatter(Type type);
}

GetFormatter 會(huì)調(diào)用注入的ILogFormatService服務(wù),這部分這篇暫時(shí)不展開了,大概改造(5)會(huì)說(shuō)到

五、

現(xiàn)在已經(jīng)可以顯示行號(hào)了


顯示行號(hào)

六、

Logging之前微軟其實(shí)已經(jīng)有一套診斷框架了,位于命名空間System.Diagnostics,
之前還專門寫過(guò)幾篇文章介紹過(guò)這個(gè),我自己之前的組件也是用這個(gè)診斷框架記錄日志的。
考慮到新舊組件的兼容性,所以需要將Trace中的日志轉(zhuǎn)發(fā)到ILogger
實(shí)現(xiàn)方式很簡(jiǎn)單,只要?jiǎng)?chuàng)建一個(gè)TraceListener偵聽器,然后轉(zhuǎn)發(fā)日志就可以了
(其他日志框架也可以通過(guò)類似的方法兼容官方的日志框架)

class LoggerTraceListener : TraceListener
{
    public LoggerTraceListener(ILogger logger) => Logger = logger;

    public ILogger Logger { get; }

    public override void Write(string message) => 
        Logger.Log(LogLevel.Trace, new EventId(0, "Trace.Write"), message, null, (a, b) => a);

    public override void WriteLine(string message) => 
        Logger.Log(LogLevel.Trace, new EventId(0, "Trace.WriteLine"), message, null, (a, b) => a);
}

增加一個(gè)擴(kuò)展方法

using static System.Diagnostics.Trace;

/// <summary>
/// 將通過(guò) <see cref="Trace"/> 記錄的內(nèi)容轉(zhuǎn)發(fā)到日志
/// </summary>
public static IServiceProvider TraceListenerToLogger(this IServiceProvider serviceProvider)
{
    if (serviceProvider?.GetService(typeof(ILoggerFactory)) is ILoggerFactory factory)
    {
        var categoryName = TypeNameHelper.GetTypeDisplayName(typeof(TraceListener));
        var logger = factory.CreateLogger(categoryName);
        if (Listeners.OfType<LoggerTraceListener>().Any(x => x.Logger == logger) == false)
        {
            Listeners.Add(new LoggerTraceListener(logger));
        }
    }
    return serviceProvider;
}
Trace轉(zhuǎn)發(fā)

七、

最后,我覺得官方的控制臺(tái)日志信息太少了,格式也不直觀,所以重新實(shí)現(xiàn)了一個(gè)ConsoleLogger
這是基類 TextWriterLogger

ConsoleLogger

八、

五、

github:https://github.com/blqw/blqw.logging
nuget:https://www.nuget.org/packages/blqw.logging

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容