LoggerMessage:编写高性能的 .NET 日志
LoggerMessage 的四大性能法宝
LoggerMessage(尤其是结合 .NET 6+ 的源代码生成器)专门为了解决上述痛点而生。它通过以下四个核心机制,实现了极高性能和零内存分配(Zero Allocation)。
1. 预解析并缓存模板
LoggerMessage 会在底层将模板的解析工作提前。字符串模板只会在类加载时被解析一次,并将结果缓存在静态字段中。后续每次记录该日志时,直接复用缓存,彻底干掉重复解析的 CPU 开销。
2. 强类型泛型委托(拒绝装箱)
LoggerMessage 使用预生成的泛型委托(例如 Action<ILogger, int, string, Exception?>)来处理参数。因为明确指出了 orderId 是 int 类型,参数传递时不会发生任何装箱操作。
3. 消灭 params object[] 数组
由于直接调用的是强类型委托,参数被直接塞进方法调用中,底层的 object[] 数组分配也被完美避开。
4. 前置的日志级别检查
生成的委托会在执行任何逻辑之前,第一时间检查 _logger.IsEnabled(LogLevel)。如果级别未启用,直接 return,实现零浪费。
🛠️ 现代玩法:使用源代码生成器 (.NET 6+)
在以前,手动编写 LoggerMessage.Define 需要写一堆繁琐的样板代码。从 .NET 6 开始,微软引入了 [LoggerMessage] 特性,配合编译器 Source Generators(源代码生成器),让你只需声明方法签名,剩下的全由编译器代劳。
你的代码(极简且优雅):
public partialclassOrderProcessor { privatereadonly ILogger _logger; public OrderProcessor(ILogger<OrderProcessor> logger) => _logger = logger; // 1. 加上 [LoggerMessage] 特性 // 2. 声明为 partial 方法 [LoggerMessage( EventId = 1, Level = LogLevel.Information, Message = "Processing order {OrderId} for user {UserId}")] public partial void LogOrderProcessing(int orderId, string userId); public void Process(int orderId, string userId) { // 像普通方法一样调用,享受极高的性能 LogOrderProcessing(orderId, userId); } }
********************
.NET 9 中的 LoggerMessage 源码生成器
LoggerMessage 是 .NET 6 引入的编译时日志源码生成特性,在 .NET 9 中持续演进,是目前 .NET 中高性能日志记录的推荐方案。核心概念
[LoggerMessage] 特性通过 Source Generator 在编译时自动生成高效的日志方法,底层调用 LoggerMessage.Define() 缓存委托,避免运行时字符串拼接和装箱开销。基本用法
1. 扩展方法方式(推荐)
public static partial class MyServiceLogging { [LoggerMessage( EventId = 1000, Level = LogLevel.Information, Message = "用户 {UserId} 在 {LoginTime:yyyy-MM-dd HH:mm:ss} 登录")] public static partial void UserLoggedIn(this ILogger logger, int userId, DateTime loginTime); [LoggerMessage( EventId = 1001, Level = LogLevel.Error, Message = "处理订单 {OrderId} 失败")] public static partial void OrderProcessingFailed(this ILogger logger, Exception ex, Guid orderId); }
使用:
public class MyService(ILogger<MyService> logger) { public void DoWork(int userId) { logger.UserLoggedIn(userId, DateTime.Now); } }
2. 实例方法方式(依赖注入)
public partial class MyService { private readonly ILogger<MyService> _logger; public MyService(ILogger<MyService> logger) { _logger = logger; } [LoggerMessage( EventId = 0, Level = LogLevel.Information, Message = "PrintMessage: {message}")] public partial void LogPrintMessage(string message); }
3. 动态日志级别
[LoggerMessage( EventId = 0, Message = "PrintMessage: {message}")] public static partial void LogPrintMessage(this ILogger logger, LogLevel logLevel, string message);
.NET 9 的重要改进
| 改进项 | 说明 |
|---|---|
| 主构造函数支持 | .NET 9 修复了 LoggerMessage 与 C# 12 主构造函数不兼容的问题
|
| 更简洁的 API | EventId 不再是必需参数,可直接写 [LoggerMessage(LogLevel.Warning, "message")]
|
| 属性支持 | 修复了使用 ILogger 属性(而非字段)时源码生成器崩溃的问题
|
约束条件
LoggerMessage 时必须遵守以下规则:-
日志方法必须是
partial且返回void -
方法名不能以
_开头 -
参数名不能以
_开头 -
日志方法不能是泛型方法
-
如果是
static方法,ILogger实例必须作为参数传入 -
类必须是
partial的
生成的代码示例
[GeneratedCode("Microsoft.Extensions.Logging.Generators", "9.0.12.6610")] private static readonly Action<ILogger, int, DateTime, Exception?> __UserLoggedInCallback = LoggerMessage.Define<int, DateTime>( LogLevel.Information, new EventId(1000, "UserLoggedIn"), "Goat number {Id} logged in at {LoginTime}", new LogDefineOptions() { SkipEnabledCheck = true } ); [GeneratedCode("Microsoft.Extensions.Logging.Generators", "9.0.12.6610")] public static void UserLoggedIn(this ILogger logger, int id, DateTime loginTime) { if (!logger.IsEnabled(LogLevel.Information)) return; __UserLoggedInCallback(logger, id, loginTime, null); }
揭秘魔法:编译器为你生成的另一半代码
为什么上面那个没有方法体的 partial 方法能工作?如果你扒开编译器的幕后,你会发现它为你生成了这样一段高度优化的代码:
// <auto-generated/> // 编译器自动生成的另一半 OrderProcessor 类 #nullable enable publicpartialclassOrderProcessor { // 静态缓存强类型委托,模板只解析一次! privatestaticreadonly Action<ILogger, int, string, Exception?> __LogOrderProcessingCallback = LoggerMessage.Define<int, string>( LogLevel.Information, new EventId(1, nameof(LogOrderProcessing)), "Processing order {OrderId} for user {UserId}", new LogDefineOptions() { SkipEnabledCheck = true }); // 补全你的 partial 方法 public partial void LogOrderProcessing(int orderId, string userId) { // 极致的前置性能检查 if (_logger.IsEnabled(LogLevel.Information)) { // 直接调用委托,传值类型 int,零装箱,零数组分配! __LogOrderProcessingCallback(_logger, orderId, userId, null); } } }
看看这段生成的代码,我们前面提到的四大性能法宝(静态缓存、前置检查、强类型、无数组分配)在这里展现得淋漓尽致。
💡 总结
如果你的应用对性能要求极高,或者某个模块(如中间件、后台循环任务、高频接口)会产生海量的日志,请务必抛弃标准的扩展方法,拥抱 [LoggerMessage] 源代码生成器。

浙公网安备 33010602011771号