写点什么

.NET 6 新东西 -- 高性能日志

作者:喵叔
  • 2021 年 12 月 17 日
  • 本文字数:1937 字

    阅读完需:约 6 分钟

一提到日志记录,大家就会想到 log4net,如果提到.NET 中的日志记录,一定会想到 ILogger,这个 ILogger 是.NET 中常用的提供的日志记录的方式,下面的代码是.NET Core WebAPI 项目初始化的代码,其中就使用了 ILogger 来提供日志记录:


private readonly ILogger<WeatherForecastController> _logger;public WeatherForecastController(ILogger<WeatherForecastController> logger){    _logger = logger;}[HttpGet(Name = "GetWeatherForecast")]public IEnumerable<WeatherForecast> Get(){     var result =  Enumerable.Range(1, 5).Select(index => new WeatherForecast    {        TemperatureC = Random.Shared.Next(-20, 55),    })    .ToArray();    _logger.LogInformation("LogInformation: {0}", JsonSerializer.Serialize(result));    return result;}
复制代码


其实.NET6 中微软为我们提供了一个高性能日志记录类 LoggerMessage。与 ILogger 记录器和它的扩展方法相比,LoggerMessage 更具性能优势。首先 ILogger 记录器扩展方法需要将值类型转换到 object 中,但是 LoggerMessage 使用了带有强类型参数的静态方法以及扩展方法来避免这个问题。并且 ILogger 记录器及其扩展方法在每次写入日志时都必须先去分析消息模板,但是 LoggerMessage 在已定义消息模板的情况下,只需分析一次模板即可。使用代码如下(修改 WebAPI 项目初始化代码):


private static readonly Action<ILogger, IEnumerable<WeatherForecast>, Exception?> _logWeatherForecast =    LoggerMessage.Define<IEnumerable<WeatherForecast>>(        logLevel: LogLevel.Information,        eventId: 0,        formatString: "LoggerMessage: {aa}");_logWeatherForecast(_logger, result, null);
复制代码


虽然 LoggerMessage 为我们提供了性能更好的日志记录,但它需要手工编写大量的 LoggerMessage.Define 代码,并且 formatString 消息模板中的参数占位符没有进行任何控制,可能会导致传参错误。在.NET 6 中微软提供了 Source Generator,来帮助我们自动生成高性能日志记录代码。使用起来很简单,首先需要创建 partial 方法,其次在 partial 方法头部声明 LoggerMessageAttribute 属性。使用代码如下:


[LoggerMessage(0, LogLevel.Information, "LoggerMessageAttribute: {weatherForecasts}")]partial void LogWeatherForecast(IEnumerable<WeatherForecast> weatherForecasts);//使用LogWeatherForecast(result);
复制代码


编译后,LoggerMessage 就为我们自动生成了代码:


partial class WeatherForecastController {[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.5.2210")]    private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.Collections.Generic.IEnumerable<global::WebApplication1.WeatherForecast>, global::System.Exception?> __LogWeatherForecastCallback =        global::Microsoft.Extensions.Logging.LoggerMessage.Define<global::System.Collections.Generic.IEnumerable<global::WebApplication1.WeatherForecast>>(global::Microsoft.Extensions.Logging.LogLevel.Information, new global::Microsoft.Extensions.Logging.EventId(0, nameof(LogWeatherForecast)), "LoggerMessageAttribute: {weatherForecasts}", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });     [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.5.2210")]    partial void LogWeatherForecast(global::System.Collections.Generic.IEnumerable<global::WebApplication1.WeatherForecast> weatherForecasts)    {        if (_logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Information))        {            __LogWeatherForecastCallback(_logger, weatherForecasts, null);        }    }}
复制代码


在上面的代码中,LogWeatherForecast 方法直接使用了 Controller 中声明的_logger 对象,不需要我们传入,并且写入日志前还判断了_logger.IsEnabled,这样就避免了不必要的日志写入,并且对性能有了进一步的提高。使用 LoggerMessageAttribute 虽然可以提高日志记录性能,但它也有其缺点:


  1. 使用 partial 方法声明必须将类也定义成 partial。

  2. 日志使用了参数对象的 ToString()方法,对于复杂类型不能在方法中传入序列化对象 LogWeatherForecast(JsonSerializer.Serialize(result)),因为会始终执行影响性能,但是可以通过定义成 record class 或自定义 ToString()方法变通解决。

用户头像

喵叔

关注

还未添加个人签名 2020.01.14 加入

还未添加个人简介

评论

发布
暂无评论
.NET 6新东西--高性能日志