.NET Core 依赖注入改造(3)- ILogger
日志其实可以算是一个独立的组件,但它与IOC/DI又有着紧密的联系,所以我吧日志也算到DI改造里了。
.NET Core 依赖注入改造(1)- 命名服务
.NET Core 依赖注入改造(2)- 委托转换
.NET Core 依赖注入改造(3)- ILogger
.NET Core 依赖注入改造(4)- ActivatorUtilities
.NET Core 依赖注入改造(5)- Context
.NET Core 依赖注入改造(附1)- Autowrite
一、
其实现在的日志已经算是比较好用的了;
使用也很简单
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("测试错误"), "debug error...");
但是依然有可以简化的地方
二、
托拓展方法的福,我们可以在不修改的源码的基础上增加一些非常实用的功能
先从添加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>
/// 获取日志服务
/// </summary>
public static ILogger GetLogger(this IServiceProvider serviceProvider, string categoryName)
{
if (serviceProvider?.GetService(typeof(ILoggerFactory)) is ILoggerFactory factory)
{
return factory.CreateLogger(categoryName);
}
//如果不存在任何服务, 则返回默认服务
return new ConsoleLogger(categoryName);
}
/// <summary>
/// 获取日志服务
/// </summary>
public static ILogger GetLogger(this IServiceProvider serviceProvider) =>
serviceProvider.GetLogger(new StackFrame(1).GetMethod()?.ReflectedType);
/// <summary>
/// 获取日志服务
/// </summary>
public static ILogger GetLogger(this IServiceProvider serviceProvider, Type type) =>
serviceProvider.GetLogger(TypeNameHelper.GetTypeDisplayName(type));
/// <summary>
/// 获取日志服务
/// </summary>
public static ILogger GetLogger<T>(this IServiceProvider serviceProvider) =>
serviceProvider.GetLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
}
之前的代码做如下调整
tips:即使不提供类型也可以获得一个ILogger
public static ILogger GetLogger(this IServiceProvider serviceProvider) =>
serviceProvider.GetLogger(new StackFrame(1).GetMethod()?.ReflectedType);
三、
接下来我想为日志加上文件和行号。
先来看看ILogger
接口的定义
写日志的只有一个Log方法,所以
LogDebug
,LogError
这些方法,应该是扩展方法Logger扩展方法
那么如果要加上行号,可以增加一些自己的拓展方法
四
- 放弃对默认格式化方式的支持
logger.LogDebug("id: {id}, name: {name}", 1, "blqw");
毕竟 C#6.0的 Interpolated Strings 已经很好用了$"id: {id}, name: {name}"
- 使用调用方信息
CallerMemberName
,CallerFilePath
,CallerLineNumber
三个特性获取文件名,行号等信息 - 将文件名,行号等信息通过
EventId
传递到日志组件
事件ID:行号
事件名:文件名->方法名
- 声明新的接口以支持任意类型的自定义格式化功能
大致就是这个样子
自定义扩展方法
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 会调用注入的ILogFormatService
服务,这部分这篇暂时不展开了,大概改造(5)会说到
五、
现在已经可以显示行号了
显示行号
六、
在Logging
之前微软其实已经有一套诊断框架了,位于命名空间System.Diagnostics
,
之前还专门写过几篇文章介绍过这个,我自己之前的组件也是用这个诊断框架记录日志的。
考虑到新旧组件的兼容性,所以需要将Trace中的日志转发到ILogger
中
实现方式很简单,只要创建一个TraceListener
侦听器,然后转发日志就可以了
(其他日志框架也可以通过类似的方法兼容官方的日志框架)
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);
}
增加一个扩展方法
using static System.Diagnostics.Trace;
/// <summary>
/// 将通过 <see cref="Trace"/> 记录的内容转发到日志
/// </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转发
七、
最后,我觉得官方的控制台日志信息太少了,格式也不直观,所以重新实现了一个ConsoleLogger
这是基类 TextWriterLogger
八、
五、
github:https://github.com/blqw/blqw.logging
nuget:https://www.nuget.org/packages/blqw.logging