.NET 6新特性试用 | 自动生成高性能日志记录代码

前言

要想记录日志,常用的方式是访问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;
}

其实,.NET下还有一个高性能日志记录类LoggerMessage[1]

与ILogger记录器扩展方法(例如LogInformation和LogDebug)相比,LoggerMessage具有以下性能优势:

  • 记录器扩展方法需要将值类型(例如 int)“装箱”(转换)到 object中。LoggerMessage模式使用带强类型参数的静态Action字段和扩展方法来避免装箱。

  • 记录器扩展方法每次写入日志消息时必须分析消息模板(命名的格式字符串)。如果已定义消息,那么LoggerMessage只需分析一次模板即可。

示例代码如下:

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消息模板中的参数占位符并没有任何控制(例如{aa}),很可能导致传递错误参数。

而在.NET 6中,可以使用Source Generator帮助我们自动生成高性能日志记录代码。

Demo

你需要创建一个partial方法,然后在其头部声明LoggerMessageAttribute

示例代码如下:

[LoggerMessage(0, LogLevel.Information, "LoggerMessageAttribute: {weatherForecasts}")]
partial void LogWeatherForecast(IEnumerable<WeatherForecast> weatherForecasts);

//使用
LogWeatherForecast(result);

查看自动生成的代码,其实是Source Generator帮我们编写了LoggerMessage.Define代码:

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避免不必要的日志写入操作,对性能有进一步提高。

更为重要的是,它不会允许传入错误的参数:

0d2b4275b34e530bef1b2b5abf6dcd96.png

结论

使用LoggerMessageAttribute可以提高日志记录性能,但它也有其缺点:

  • 使用partial方法声明必须将类也定义成partial

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

    00122f69d1872ae275a504d5ca68e92f.png

参考资料

[1]

LoggerMessage: https://docs.microsoft.com/zh-cn/dotnet/core/extensions/high-performance-logging

如果你觉得这篇文章对你有所启发,请关注我的个人公众号”My IO“

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值