优化 .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相关推荐

  1. .NET Core 3中的性能提升(译文)

    回顾我们准备推出.NET Core 2.0的时候,我写了一篇博文来介绍.NET已经引入的诸多性能优化中的一部分,我很喜欢把它们放在一起讲述,也收获了很多正面反馈,因此我又给.NET Core 2.1, ...

  2. c#中的vector_.NET Core 3 中的性能提升

    (给DotNet加星标,提升.Net技能) 转自:森林蝙蝠原文:devblogs.microsoft.com英文:zhuanlan.zhihu.com/p/66152703 回顾我们准备推出.NET ...

  3. java中什么泛型_【原创】java中的泛型是什么,有什么作用

    泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方 ...

  4. Entity Framework Core 5中实现批量更新、删除

    本文介绍了一个在EntityFramework Core 5中不需要预先加载数据而使用一句SQL语句批量更新.删除数据的开发包,并且分析了其实现原理,并且与其他实现方案做了比较. 一.背景 随着微软全 ...

  5. 在 ASP.NET Core 项目中使用 AutoMapper 进行实体映射

    一.前言 在实际项目开发过程中,我们使用到的各种 ORM 组件都可以很便捷的将我们获取到的数据绑定到对应的 List<T> 集合中,因为我们最终想要在页面上展示的数据与数据库实体类之间可能 ...

  6. [译]聊聊C#中的泛型的使用

    写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发现了一些问题,因此也进行了纠正,当然,原文的地 ...

  7. ASP.NET Core SignalR中的流式传输

    什么是流式传输? 流式传输是这一种以稳定持续流的形式传输数据的技术. 流式传输的使用场景 有些场景中,服务器返回的数据量较大,等待时间较长,客户端不得不等待服务器返回所有数据后,再进行相应的操作.这时 ...

  8. 在 .NET Core 应用中使用 NHibernate

    NHibernate 最近发布了 5.1.3 版本,  支持 .NET Standard 2.0 , 这意味着可以在 .NET Core 2.0 应用中使用, 本文就已 WebAPI 应用为例, 介绍 ...

  9. Java 中的泛型是什么,它有什么作用?(十五)

    Java中的泛型是一种类型参数化机制,它使代码更具可读性.可重用性和稳健性.在Java中,通过使用泛型,可以将类型作为参数传递给类或方法,并在编译时执行类型检查,从而避免许多运行时错误. 泛型的基础 ...

最新文章

  1. 【计算机视觉】EmguCV学习笔记(2)图像的载入、显示和输出
  2. 宏基因组扩增子最新分析流程QIIME2:官方中文帮助文档
  3. 百度网络推广介绍网站在更换老域名时都需注意哪些?
  4. js中对日期进行加减
  5. Web UI 制作规范
  6. Effective C# Item23:避免返回内部类对象的引用
  7. C语言写300k文件大概多少行,为什么 DELPHI 编译出的程序一般的来说至少都有300k呢?...
  8. java线程的状态有几种_Java线程的几种可用状态
  9. 基于SU的快速傅里叶变换(FFT)
  10. Linux防CC攻击脚本
  11. Hadoop2 实战系列之1 -- Hortonworks Sandbox的安装和使用
  12. [转]解决2003不支持FLV的方法
  13. 英特尔nuc能代替主机吗_拆了拆了!Intel NUC装机!小机箱退烧器啊!主机显示器合体...
  14. Ansys Speos | 手把手教你画光导
  15. FreeRtos在RH850 D1L芯片上移植
  16. 两个人相处久了会越来越像吗? | 无意识模仿的秘密
  17. 读书会 | 第一季读书会《蛤蟆先生去看心理医生》完美收官啦
  18. 浏览器连不上 Flink WebUI 8081 端口
  19. 计算机毕业设计之java+ssm基于个人需求的外卖订餐推荐系统
  20. 笔记本不能用无线网策略服务器,明明有无线网笔记本就是搜索不到怎么处理

热门文章

  1. Ajax跨域提交JSON和JSONP
  2. 百度地图移动端开发和ArcGIS for Android 开发入门
  3. 算法马拉松13 A-E解题报告
  4. C# 对Datatable排序
  5. Unity中使用RequireComponent,没有添加上组件
  6. 静态注册BroadcastReceiver内部类
  7. 从无到有到完善 - Teams抽奖机器人开发历程
  8. zune linux_快速提示:在出售Zune HD之前,先擦除所有内容
  9. angular-ui-tab-scroll
  10. PHP中常见的五种设计模式