0
点赞
收藏
分享

微信扫一扫

.NET 6新特性试用 | 自动生成高性能日志记录代码 #yyds干货盘点#

前言

要想记录日志,常用的方式是访问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​​避免不必要的日志写入操作,对性能有进一步提高。

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

.NET 6新特性试用 | 自动生成高性能日志记录代码 #yyds干货盘点#_microsoft

结论

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

  • 使用​​partial​​方法声明必须将类也定义成​​partial​​。
  • 日志使用了参数对象的​ToString()方法​,对于复杂类型,不能在方法中传入序列化对象​​LogWeatherForecast(JsonSerializer.Serialize(result))​​,因为会始终执行影响性能,可以通过定义成​​record class​​或自定义ToString()方法变通解决:
    .NET 6新特性试用 | 自动生成高性能日志记录代码 #yyds干货盘点#_扩展方法_02

参考资料

[1]

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

 


举报

相关推荐

0 条评论