作为一名C#开发者,你是否遭遇过这样的情况:系统运行一段时间后,响应变得奇慢无比?打开性能监视器,发现CPU被某些莫名其妙的操作占用?等会儿...为啥日志记录占了这么大的开销?
最近在优化一个电商平台的订单处理模块时,我发现了一个让人震撼的数据:传统的 LogInformation 调用竟然比高性能日志方法慢了整整9倍!更要命的是,即使日志级别设置为Error,不输出任何日志内容,传统方法依然要消耗几乎相同的时间。
今天咱们就来深入剖析.NET日志系统的性能瓶颈,并掌握一种让日志性能飞跃的神器——LoggerMessage特性。读完这篇文章,你将获得:
让我先给你看个典型场景。在处理用户请求时,咱们经常这样写日志:
csharpint orderCount = 100;
DateTime processTime = DateTime.Now;
_logger.LogInformation($"处理了 {orderCount} 个订单,时间:{processTime}");
这玩意儿看起来人畜无害,实际上却是性能杀手!问题出在哪儿?
让咱们把这个看似无害的代码拆解一下:
$"处理了 {orderCount} 个订单,时间:{processTime}" 这个字符串插值都会被执行orderCount 这个int值要被装箱成object类型processTime 要转换成字符串表示在我测试的项目中,这些看似微不足道的操作累积起来,让单次日志调用耗时达到了0.45毫秒。你可能觉得,这点时间算什么?但在高并发场景下,一个接口调用包含10-20个日志点,累积效应就相当可怕了。
更糟糕的是——即使你把日志级别设为Error,不输出任何信息,这些开销依然存在!
Microsoft意识到了这个问题,在.NET 6中引入了 LoggerMessage 特性。这个特性通过编译时代码生成,彻底解决了传统日志的性能瓶颈。
LoggerMessage 特性的工作原理其实很巧妙:
这种方式适合工具类或静态日志帮助方法:
csharpusing Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;
namespace AppLoggerUp
{
internal static partial class HighPerformanceLogger
{
/// <summary>
/// 记录订单处理信息
/// </summary>
[LoggerMessage(
EventId = 1001,
Level = LogLevel.Information,
Message = "订单处理完成:订单数量 {orderCount},处理时间 {processTime}")]
public static partial void LogOrderProcessed(
ILogger logger,
int orderCount,
DateTime processTime);
/// <summary>
/// 记录用户操作
/// </summary>
[LoggerMessage(
EventId = 1002,
Level = LogLevel.Information,
Message = "用户 {userId} 执行了 {action} 操作,结果:{result}")]
public static partial void LogUserAction(
ILogger logger,
string userId,
string action,
string result);
/// <summary>
/// 记录异常信息
/// </summary>
[LoggerMessage(
EventId = 2001,
Level = LogLevel.Error,
Message = "订单处理发生异常:订单ID {orderId},用户ID {userId}")]
public static partial void LogOrderError(
ILogger logger,
int orderId,
string userId,
Exception exception);
}
}
使用方法:
csharpusing Microsoft.Extensions.Logging;
namespace AppLoggerUp
{
public class OrderService
{
private readonly ILogger<OrderService> _logger;
// 通过 DI 注入 ILogger
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public async Task ProcessOrdersAsync()
{
var startTime = DateTime.Now;
// 模拟异步批量处理
var processedCount = await ProcessOrderBatchAsync();
// 高性能日志调用
HighPerformanceLogger.LogOrderProcessed(_logger, processedCount, startTime);
}
private async Task<int> ProcessOrderBatchAsync()
{
// 模拟异步处理耗时
await Task.Delay(150);
// 模拟处理了 18 笔订单
return 18;
}
}
}
c#using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using AppLoggerUp;
// 1. 构建 DI 容器 & Logger
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Debug); // 允许所有级别通过
});
services.AddTransient<OrderService>();
var provider = services.BuildServiceProvider();
// 2. 直接测试 HighPerformanceLogger
var loggerFactory = provider.GetRequiredService<ILoggerFactory>();
var rawLogger = loggerFactory.CreateLogger("TestLogger");
Console.WriteLine("=== 直接调用 HighPerformanceLogger ===\n");
// 测试 LogOrderProcessed
HighPerformanceLogger.LogOrderProcessed(rawLogger, 42, DateTime.Now);
// 测试 LogUserAction
HighPerformanceLogger.LogUserAction(rawLogger, "user_001", "CreateOrder", "成功");
// 测试 LogOrderError(带 Exception)
try
{
throw new InvalidOperationException("库存不足,无法完成订单");
}
catch (Exception ex)
{
HighPerformanceLogger.LogOrderError(rawLogger, orderId: 9988, userId: "user_001", exception: ex);
}
// 3. 通过 OrderService 调用(走完整业务流程)
Console.WriteLine("\n=== 通过 OrderService 调用 ===\n");
var orderService = provider.GetRequiredService<OrderService>();
await orderService.ProcessOrdersAsync();
Console.WriteLine("\n=== 测试完成 ===");

这种方式更适合服务类内部使用,代码更简洁:
csharppublic partial class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
#region 高性能日志方法
/// <summary>
/// 记录方法开始
/// </summary>
[LoggerMessage(
EventId = 3001,
Level = LogLevel.Information,
Message = "OrderService.{methodName} 开始执行,关联ID:{correlationId}")]
public partial void LogMethodStart(string methodName, string correlationId);
/// <summary>
/// 记录方法完成
/// </summary>
[LoggerMessage(
EventId = 3002,
Level = LogLevel.Information,
Message = "OrderService.{methodName} 执行完成,关联ID:{correlationId},耗时:{elapsed}ms")]
public partial void LogMethodEnd(string methodName, string correlationId, double elapsed);
/// <summary>
/// 记录业务异常
/// </summary>
[LoggerMessage(
EventId = 3003,
Level = LogLevel.Error,
Message = "OrderService.{methodName} 执行异常,关联ID:{correlationId}")]
public partial void LogMethodException(string methodName, string correlationId, Exception ex);
#endregion
public async Task<int> ProcessOrderBatchAsync(string correlationId)
{
var methodName = nameof(ProcessOrderBatchAsync);
var stopwatch = Stopwatch.StartNew();
LogMethodStart(methodName, correlationId);
try
{
// 业务逻辑...
var result = await DoActualProcessingAsync();
stopwatch.Stop();
LogMethodEnd(methodName, correlationId, stopwatch.Elapsed.TotalMilliseconds);
return result;
}
catch (Exception ex)
{
LogMethodException(methodName, correlationId, ex);
throw;
}
}
}
c#using AppLoggerUp;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace AppLoggerUp
{
[MemoryDiagnoser] // 显示 GC 分配量(最关键的对比维度)
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[RankColumn] // 显示排名列
public class LoggingBenchmark
{
private ILogger _enabledLogger = null!; // 日志级别开启(会真实写入)
private ILogger _disabledLogger = null!; // 日志级别关闭(测试 guard 开销)
private const int OrderCount = 100;
private static readonly DateTime ProcessTime = new(2026, 4, 16, 10, 31, 0);
[GlobalSetup]
public void Setup()
{
// 开启 Information 级别的 Logger(控制台输出会干扰计时,用 NullLogger 隔离 I/O)
var factory = LoggerFactory.Create(b =>
{
b.AddProvider(NullLoggerProvider.Instance);
b.SetMinimumLevel(LogLevel.Information);
});
_enabledLogger = factory.CreateLogger("Benchmark");
// 关闭 Information 级别(MinLevel = Warning),模拟生产中大量被过滤的日志
var disabledFactory = LoggerFactory.Create(b =>
{
b.AddProvider(NullLoggerProvider.Instance);
b.SetMinimumLevel(LogLevel.Warning);
});
_disabledLogger = disabledFactory.CreateLogger("Benchmark");
}
// 场景 A:日志级别开启
/// <summary>
/// 字符串插值:无论日志级别是否开启,都会先完成字符串拼接 + 装箱,再传入
/// </summary>
[Benchmark(Baseline = true, Description = "插值-开启")]
public void Interpolation_Enabled()
{
_enabledLogger.LogInformation(
$"处理了 {OrderCount} 个订单,时间:{ProcessTime}");
}
/// <summary>
/// [LoggerMessage] 源生成器:编译时生成委托,运行时零装箱
/// </summary>
[Benchmark(Description = "LoggerMessage-开启")]
public void SourceGen_Enabled()
{
HighPerformanceLogger.LogOrderProcessed(_enabledLogger, OrderCount, ProcessTime);
}
// 场景 B:日志级别关闭(最能体现差距的场景)
/// <summary>
/// 字符串插值:即使日志被过滤,插值字符串和装箱已经发生,白白浪费
/// </summary>
[Benchmark(Description = "插值-关闭")]
public void Interpolation_Disabled()
{
_disabledLogger.LogInformation(
$"处理了 {OrderCount} 个订单,时间:{ProcessTime}");
}
/// <summary>
/// [LoggerMessage] 源生成器:IsEnabled() 短路,参数根本不会被求值
/// </summary>
[Benchmark(Description = "LoggerMessage-关闭")]
public void SourceGen_Disabled()
{
HighPerformanceLogger.LogOrderProcessed(_disabledLogger, OrderCount, ProcessTime);
}
// 场景 C:手动 IsEnabled guard(传统最佳实践,作为参照)
[Benchmark(Description = "手动Guard-开启")]
public void ManualGuard_Enabled()
{
if (_enabledLogger.IsEnabled(LogLevel.Information))
_enabledLogger.LogInformation("处理了 {orderCount} 个订单,时间:{processTime}",
OrderCount, ProcessTime);
}
[Benchmark(Description = "手动Guard-关闭")]
public void ManualGuard_Disabled()
{
if (_disabledLogger.IsEnabled(LogLevel.Information))
_disabledLogger.LogInformation("处理了 {orderCount} 个订单,时间:{processTime}",
OrderCount, ProcessTime);
}
}
}

为啥差距这么大?咱们看看编译器生成的代码:
csharp// 编译器为LoggerMessage特性生成的代码
public static partial void LogOrderProcessed(ILogger logger, int orderCount, DateTime processTime)
{
// 第一步:立即检查日志级别
if (logger.IsEnabled(LogLevel.Information))
{
// 只有在需要输出时,才构建消息字符串
__LogOrderProcessedCallback(logger, orderCount, processTime, null);
}
// 如果日志级别不匹配,直接返回,零开销!
}
而传统方法呢?
csharp// 传统方法的执行流程
_logger.LogInformation($"订单处理完成:订单数量 {orderCount},处理时间 {processTime}");
// 等价于:
// 1. 先构建字符串(无论是否需要输出)
var message = $"订单处理完成:订单数量 {orderCount},处理时间 {processTime}";
// 2. 再调用日志方法
_logger.LogInformation(message);
// 3. 在日志方法内部才检查级别
在项目中应用这个技术时,我踩了几个坑,分享给你避免重复踩坑:
csharp// ❌ 错误:缺少partial关键字
public class OrderService
{
[LoggerMessage(...)]
public void LogSomething() { } // 编译错误!
}
// ✅ 正确:必须是partial类和partial方法
public partial class OrderService
{
[LoggerMessage(...)]
public partial void LogSomething();
}
csharp// ❌ 错误:返回类型不是void
[LoggerMessage(Message = "测试 {value}")]
public partial string LogTest(int value); // 编译错误!
// ❌ 错误:静态方法缺少ILogger参数
[LoggerMessage(Message = "测试 {value}")]
public static partial void LogTest(int value); // 编译错误!
// ✅ 正确
[LoggerMessage(Message = "测试 {value}")]
public static partial void LogTest(ILogger logger, int value);
csharp// ❌ 错误:Message模板中的参数与方法参数不匹配
[LoggerMessage(Message = "用户 {userId} 执行了 {action}")]
public static partial void LogUserAction(ILogger logger, string userId); // 缺少action参数
// ✅ 正确:参数必须一一对应
[LoggerMessage(Message = "用户 {userId} 执行了 {action}")]
public static partial void LogUserAction(ILogger logger, string userId, string action);
csharp// ❌ 错误:EventId重复了
[LoggerMessage(EventId = 1001, Message = "订单创建")]
public static partial void LogOrderCreated(ILogger logger);
[LoggerMessage(EventId = 1001, Message = "订单更新")] // 重复的EventId!
public static partial void LogOrderUpdated(ILogger logger);
// ✅ 正确:每个日志方法使用不同的EventId
[LoggerMessage(EventId = 1001, Message = "订单创建")]
public static partial void LogOrderCreated(ILogger logger);
[LoggerMessage(EventId = 1002, Message = "订单更新")]
public static partial void LogOrderUpdated(ILogger logger);
对于需要在多个日志点使用相同参数的场景,可以用Scoped:
csharppublic class OrderService
{
private readonly ILogger<OrderService> _logger;
public async Task ProcessOrderAsync(int orderId, string userId)
{
// 创建作用域,自动添加到所有日志中
using var scope = _logger.BeginScope(new Dictionary<string, object>
{
["OrderId"] = orderId,
["UserId"] = userId,
["TraceId"] = Activity.Current?.Id ?? Guid.NewGuid().ToString()
});
LogOrderProcessStart();
try
{
await DoProcessingAsync();
LogOrderProcessSuccess();
}
catch (Exception ex)
{
LogOrderProcessError(ex);
throw;
}
}
[LoggerMessage(EventId = 5001, Level = LogLevel.Information,
Message = "开始处理订单")]
private partial void LogOrderProcessStart();
[LoggerMessage(EventId = 5002, Level = LogLevel.Information,
Message = "订单处理成功")]
private partial void LogOrderProcessSuccess();
[LoggerMessage(EventId = 5003, Level = LogLevel.Error,
Message = "订单处理失败")]
private partial void LogOrderProcessError(Exception ex);
}
在Release版本中,可以完全禁用Debug级别的日志:
csharppublic static partial class DebugLogger
{
#if DEBUG
[LoggerMessage(EventId = 9001, Level = LogLevel.Debug,
Message = "调试信息: {info}")]
public static partial void LogDebugInfo(ILogger logger, string info);
#else
public static void LogDebugInfo(ILogger logger, string info)
{
// Release版本中完全是空操作
}
#endif
}
在应用这些优化后,建议设置一些监控指标:
csharppublic class PerformanceLogger
{
private static readonly Counter<int> LogCallCount =
Metrics.CreateCounter<int>("app.logging.calls.count");
private static readonly Histogram<double> LogCallDuration =
Metrics.CreateHistogram<double>("app.logging.calls.duration");
[LoggerMessage(EventId = 8001, Level = LogLevel.Information,
Message = "性能指标 - 方法:{method}, 耗时:{duration}ms")]
public static partial void LogPerformanceMetric(ILogger logger, string method, double duration);
public static void LogWithMetrics(ILogger logger, string method, double duration)
{
LogCallCount.Add(1, new KeyValuePair<string, object?>("method", method));
LogCallDuration.Record(duration, new KeyValuePair<string, object?>("method", method));
LogPerformanceMetric(logger, method, duration);
}
}
通过这篇文章,咱们深入了解了.NET高性能日志的核心秘诀:
你在项目中遇到过哪些日志性能问题?尝试LoggerMessage后效果如何?欢迎在评论区分享你的经验!
如果这篇文章对你有帮助,别忘了点赞收藏,说不定哪天在优化性能时就用上了呢!
#C#开发 #性能优化 #日志系统 #LoggerMessage #.NET高性能
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!