日志其實(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接口的定義

寫日志的只有一個(gè)Log方法,所以
LogDebug,LogError這些方法,應(yīng)該是擴(kuò)展方法
那么如果要加上行號(hào),可以增加一些自己的拓展方法
四
- 放棄對(duì)默認(rèn)格式化方式的支持
logger.LogDebug("id: {id}, name: {name}", 1, "blqw");
畢竟 C#6.0的 Interpolated Strings 已經(jīng)很好用了$"id: {id}, name: {name}" - 使用調(diào)用方信息
CallerMemberName,CallerFilePath,CallerLineNumber三個(gè)特性獲取文件名,行號(hào)等信息 - 將文件名,行號(hào)等信息通過(guò)
EventId傳遞到日志組件
事件ID:行號(hào)
事件名:文件名->方法名 - 聲明新的接口以支持任意類型的自定義格式化功能
大致就是這個(gè)樣子

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

六、
在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;
}

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

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