惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
The Exploit Database - CXSecurity.com
F
Fortinet All Blogs
U
Unit 42
F
Full Disclosure
雷峰网
雷峰网
博客园 - 司徒正美
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tailwind CSS Blog
The Cloudflare Blog
Last Week in AI
Last Week in AI
罗磊的独立博客
D
DataBreaches.Net
C
Check Point Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
O
OpenAI News
C
CXSECURITY Database RSS Feed - CXSecurity.com
aimingoo的专栏
aimingoo的专栏
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
SegmentFault 最新的问题
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Hacker News
The Hacker News
Webroot Blog
Webroot Blog
Security Latest
Security Latest
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Google DeepMind News
Google DeepMind News
酷 壳 – CoolShell
酷 壳 – CoolShell
N
News | PayPal Newsroom
P
Proofpoint News Feed
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
Google Online Security Blog
Google Online Security Blog
H
Help Net Security
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
I
Intezer
Application and Cybersecurity Blog
Application and Cybersecurity Blog
M
MIT News - Artificial intelligence
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
IT之家
IT之家
MyScale Blog
MyScale Blog
腾讯CDC

博客园 - Charltsing

C# 预处理器指令语言 - 完整语法速查 PDFImageViewer v2.1 PDF查看图像和无损导出工具,一键转换单图PDF为JBig2黑白PDF。兼谈PDF内嵌图像。 PDF的色彩空间 25 个使用.NET 10 的性能技巧 PDFontFixer v1.5 免费版,解决PDF文档复制粘贴乱码的问题,修复PDF文档字体的Unicode映射。 为什么 PDF 编辑这么难? Unicode、UTF-8、UTF-16 如何直接编辑Github的Readme.md文件 自动导入工程项目属性,Directory.Build.props 和 Directory.Build.targets 新版 C# 高效率编程指南 C# 内联数组(Inline Array):高性能数组的新选择 如何在 .NET 中使用 SIMD .NET异步编程进阶:从语法糖到高性能架构的核心突破 C# 中的安全零拷贝 使用UnsafeAccessor 访问私有字段 C# 12与.NET 8实战指南:20个提升代码质量的最佳实践 C# Net9的模块初始化器(Module Initializer) ECMA-335 CLI 规范附录 C#中避免GC压力和提高性能的8种技术 函数内联 硬件内在函数 官方:oPDF v2.1免费版,专业的PDF水印分析处理工具,无损去除水印,通杀八类PDF水印。它是PDFCommander 万能PDF水印删除工具的升级版。 C#自动引用Debug | Release版本的dll
LoggerMessage:编写高性能的 .NET 日志
Charltsing · 2026-06-17 · via 博客园 - Charltsing

LoggerMessage 的四大性能法宝

LoggerMessage(尤其是结合 .NET 6+ 的源代码生成器)专门为了解决上述痛点而生。它通过以下四个核心机制,实现了极高性能和零内存分配(Zero Allocation)

1. 预解析并缓存模板

LoggerMessage 会在底层将模板的解析工作提前。字符串模板只会在类加载时被解析一次,并将结果缓存在静态字段中。后续每次记录该日志时,直接复用缓存,彻底干掉重复解析的 CPU 开销。

2. 强类型泛型委托(拒绝装箱)

LoggerMessage 使用预生成的泛型委托(例如 Action<ILogger, int, string, Exception?>)来处理参数。因为明确指出了 orderIdint 类型,参数传递时不会发生任何装箱操作。

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 实例必须作为参数传入


生成的代码示例

编译器会自动生成类似下面的代码:

[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] 源代码生成器