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

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了.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避免不必要的日志写入操作,对性能有进一步提高。

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

结论

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

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

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

参考资料

[1]

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

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

以上是关于.NET 6新特性试用 | 自动生成高性能日志记录代码的主要内容,如果未能解决你的问题,请参考以下文章

.NET 6新特性试用 | System.Text.Json序列化代码自动生成

.NET 6新特性试用 | 总结:我最喜欢的5个特性

.NET 6新特性试用 | 热重载

.NET 6新特性试用 可写JSON DOM API

.NET 6新特性试用 | PeriodicTimer

.NET 6新特性试用 | 可空引用类型