Skip to content

调试技巧 ​

🔍 高效排查 MediatR 应用中的问题


📖 概述 ​

调试 MediatR 应用需要掌握一些特殊技巧,因为请求处理涉及管道行为链、依赖注入和异步执行。本章介绍实用的调试方法和工具。


🛠️ 日志输出请求执行流程 ​

增强版日志行为 ​

csharp
public class DetailedLoggingBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly ILogger<DetailedLoggingBehavior<TRequest, TResponse>> _logger;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public DetailedLoggingBehavior(
        ILogger<DetailedLoggingBehavior<TRequest, TResponse>> logger,
        IHttpContextAccessor httpContextAccessor)
    {
        _logger = logger;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<TResponse> Handle(
        TRequest request, 
        RequestHandlerDelegate<TResponse> next, 
        CancellationToken cancellationToken)
    {
        var requestId = Guid.NewGuid();
        var requestName = typeof(TRequest).Name;
        var userId = _httpContextAccessor.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
        
        _logger.LogInformation(
            "═══════════════════════════════════════\n" +
            "📨 开始处理请求\n" +
            "   请求ID: {RequestId}\n" +
            "   请求类型: {RequestName}\n" +
            "   用户ID: {UserId}\n" +
            "   时间戳: {Timestamp}\n" +
            "   请求数据: {@Request}\n" +
            "═══════════════════════════════════════",
            requestId,
            requestName,
            userId ?? "Anonymous",
            DateTime.UtcNow,
            request);

        var stopwatch = Stopwatch.StartNew();

        try
        {
            var response = await next();

            stopwatch.Stop();

            _logger.LogInformation(
                "✅ 请求处理成功\n" +
                "   请求ID: {RequestId}\n" +
                "   耗时: {ElapsedMilliseconds}ms\n" +
                "   响应数据: {@Response}",
                requestId,
                stopwatch.ElapsedMilliseconds,
                response);

            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();

            _logger.LogError(
                ex,
                "❌ 请求处理失败\n" +
                "   请求ID: {RequestId}\n" +
                "   耗时: {ElapsedMilliseconds}ms\n" +
                "   异常类型: {ExceptionType}\n" +
                "   异常消息: {Message}",
                requestId,
                stopwatch.ElapsedMilliseconds,
                ex.GetType().Name,
                ex.Message);

            throw;
        }
    }
}

日志输出示例 ​

═══════════════════════════════════════
📨 开始处理请求
   请求ID: a3f8b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o
   请求类型: CreateOrderCommand
   用户ID: user123
   时间戳: 2026-04-12 10:30:45.123
   请求数据: {"ProductName":"iPhone","Quantity":2,"Price":7999}
═══════════════════════════════════════

✅ 请求处理成功
   请求ID: a3f8b2c1-4d5e-6f7g-8h9i-0j1k2l3m4n5o
   耗时: 125ms
   响应数据: {"OrderId":"b4c9d3e2-...","TotalAmount":15998}

📊 可视化管道执行顺序 ​

调试行为链 ​

csharp
public class PipelineTraceBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private static int _behaviorCounter = 0;
    private readonly int _order;
    private readonly string _behaviorName;

    public PipelineTraceBehavior()
    {
        _order = Interlocked.Increment(ref _behaviorCounter);
        _behaviorName = GetType().Name;
    }

    public async Task<TResponse> Handle(
        TRequest request, 
        RequestHandlerDelegate<TResponse> next, 
        CancellationToken cancellationToken)
    {
        var indent = new string(' ', _order * 2);
        Console.WriteLine($"{indent}➡️  进入 [{_order}] {_behaviorName}");

        try
        {
            var response = await next();
            
            Console.WriteLine($"{indent}⬅️  离开 [{_order}] {_behaviorName}");
            
            return response;
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{indent}❌ 异常 [{_order}] {_behaviorName}: {ex.Message}");
            throw;
        }
    }
}

// 注册(按顺序)
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PipelineTraceBehavior<,>)); // Logging
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PipelineTraceBehavior<,>)); // Validation
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PipelineTraceBehavior<,>)); // Transaction

输出示例 ​

➡️  进入 [1] LoggingBehavior
  ➡️  进入 [2] ValidationBehavior
    ➡️  进入 [3] TransactionBehavior
      ➡️  进入 [4] CachingBehavior
        ➡️  进入 [5] PerformanceBehavior
          🎯 Handler 执行业务逻辑
        ⬅️  离开 [5] PerformanceBehavior
      ⬅️  离开 [4] CachingBehavior
    ⬅️  离开 [3] TransactionBehavior
  ⬅️  离开 [2] ValidationBehavior
⬅️  离开 [1] LoggingBehavior

🔬 诊断监听器与 DiagnosticSource ​

创建诊断监听器 ​

csharp
public class MediatRDiagnosticsObserver : IObserver<KeyValuePair<string, object>>
{
    private readonly ILogger<MediatRDiagnosticsObserver> _logger;
    private readonly ActivitySource _activitySource;

    public MediatRDiagnosticsObserver(
        ILogger<MediatRDiagnosticsObserver> logger,
        ActivitySource activitySource)
    {
        _logger = logger;
        _activitySource = activitySource;
    }

    public void OnNext(KeyValuePair<string, object> kvp)
    {
        switch (kvp.Key)
        {
            case "MediatR.Request.Start":
                HandleRequestStart(kvp.Value);
                break;
            case "MediatR.Request.Stop":
                HandleRequestStop(kvp.Value);
                break;
            case "MediatR.Request.Error":
                HandleRequestError(kvp.Value);
                break;
            case "MediatR.Notification.Start":
                HandleNotificationStart(kvp.Value);
                break;
        }
    }

    private void HandleRequestStart(object data)
    {
        var requestData = (dynamic)data;
        _logger.LogInformation("🚀 请求开始: {RequestName}", requestData.RequestName);
        
        // 创建 Activity 用于分布式追踪
        var activity = _activitySource.StartActivity($"MediatR.{requestData.RequestName}");
        activity?.SetTag("request.name", requestData.RequestName);
    }

    private void HandleRequestStop(object data)
    {
        var responseData = (dynamic)data;
        _logger.LogInformation(
            "✅ 请求完成: {RequestName}, 耗时: {Duration}ms",
            responseData.RequestName,
            responseData.Duration);
    }

    private void HandleRequestError(object data)
    {
        var errorData = (dynamic)data;
        _logger.LogError(
            errorData.Exception,
            "❌ 请求失败: {RequestName}, 错误: {Message}",
            errorData.RequestName,
            errorData.Exception.Message);
    }

    public void OnCompleted() { }
    public void OnError(Exception error) { }
}

注册诊断监听器 ​

csharp
// Program.cs
var diagnosticListener = new DiagnosticListener("MediatR");
var observer = new MediatRDiagnosticsObserver(
    loggerFactory.CreateLogger<MediatRDiagnosticsObserver>(),
    new ActivitySource("MediatR"));

diagnosticListener.Subscribe(observer);
builder.Services.AddSingleton(diagnosticListener);

⚡ 性能分析(BenchmarkDotNet) ​

安装 ​

bash
dotnet add package BenchmarkDotNet

创建基准测试 ​

csharp
[MemoryDiagnoser]
[HtmlExporter]
public class MediatRBenchmarks
{
    private IMediator _mediator;
    private CreateOrderCommand _command;

    [GlobalSetup]
    public void Setup()
    {
        var services = new ServiceCollection();
        
        services.AddMediatR(cfg => 
            cfg.RegisterServicesFromAssembly(typeof(CreateOrderHandler).Assembly));
        
        services.AddScoped<IOrderRepository, InMemoryOrderRepository>();
        
        var provider = services.BuildServiceProvider();
        _mediator = provider.GetRequiredService<IMediator>();
        
        _command = new CreateOrderCommand
        {
            ProductName = "Benchmark Product",
            Quantity = 1,
            Price = 100
        };
    }

    [Benchmark]
    public async Task<OrderResult> SendCommand()
    {
        return await _mediator.Send(_command);
    }

    [Benchmark]
    public async Task PublishNotification()
    {
        await _mediator.Publish(new OrderCreatedNotification
        {
            OrderId = Guid.NewGuid(),
            TotalAmount = 100
        });
    }
}

// 运行基准测试
public class Program
{
    public static void Main(string[] args)
    {
        var summary = BenchmarkRunner.Run<MediatRBenchmarks>();
    }
}

基准测试结果示例 ​

| Method              | Mean     | Error   | StdDev  | Gen0   | Allocated |
|-------------------- |---------:|--------:|--------:|-------:|----------:|
| SendCommand         | 125.3 μs | 2.14 μs | 1.89 μs | 5.3711 |  44.2 KB  |
| PublishNotification | 89.7 μs  | 1.56 μs | 1.38 μs | 3.1738 |  26.1 KB  |

🐛 常见问题排查 ​

问题 1:Handler 未被发现 ​

症状:InvalidOperationException: No service for type 'IRequestHandler'

排查步骤:

csharp
// 1. 检查程序集扫描
var assembly = typeof(Program).Assembly;
Console.WriteLine($"扫描程序集: {assembly.FullName}");

// 2. 列出所有注册的 Handler
var handlers = assembly.GetTypes()
    .Where(t => t.GetInterfaces().Any(i => i.IsGenericType && 
           i.GetGenericTypeDefinition() == typeof(IRequestHandler<,>)))
    .ToList();

Console.WriteLine($"找到 {handlers.Count} 个 Handler:");
foreach (var handler in handlers)
{
    Console.WriteLine($"  - {handler.FullName}");
}

// 3. 确认 Handler 是 public 且非抽象
public class MyHandler : IRequestHandler<MyCommand> // ✅ 正确
internal class MyHandler : IRequestHandler<MyCommand> // ❌ 错误:必须是 public

解决方案:

csharp
// 确保扫描正确的程序集
builder.Services.AddMediatR(cfg => {
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.RegisterServicesFromAssembly(typeof(SharedLibraryMarker).Assembly);
});

问题 2:管道行为未执行 ​

症状:日志行为没有输出日志

排查步骤:

csharp
// 1. 检查行为是否注册
var behaviors = builder.Services
    .Where(s => s.ServiceType == typeof(IPipelineBehavior<,>))
    .ToList();

Console.WriteLine($"注册了 {behaviors.Count} 个管道行为:");
foreach (var behavior in behaviors)
{
    Console.WriteLine($"  - {behavior.ImplementationType?.Name}");
}

// 2. 确认注册顺序
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

问题 3:循环依赖 ​

症状:InvalidOperationException: A circular dependency was detected

排查方法:

csharp
// 使用依赖注入容器检测循环依赖
try
{
    var mediator = serviceProvider.GetRequiredService<IMediator>();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("circular"))
{
    Console.WriteLine("检测到循环依赖!");
    Console.WriteLine(ex.StackTrace);
}

解决方案:

csharp
// ❌ 错误:循环依赖
public class OrderHandler : IRequestHandler<OrderCommand>
{
    private readonly PaymentHandler _paymentHandler; // 循环依赖
}

// ✅ 正确:提取共享服务
public class OrderProcessingService
{
    public async Task ProcessPayment(Guid orderId) { /* ... */ }
}

public class OrderHandler : IRequestHandler<OrderCommand>
{
    private readonly OrderProcessingService _orderProcessingService;
}

public class PaymentHandler : IRequestHandler<PaymentCommand>
{
    private readonly OrderProcessingService _orderProcessingService;
}

问题 4:通知处理器异常中断 ​

症状:一个处理器失败导致其他处理器不执行

解决方案:使用容错 Mediator

csharp
public class ResilientMediator : Mediator
{
    private readonly ILogger<ResilientMediator> _logger;

    protected override async Task PublishCore(
        IEnumerable<NotificationHandlerExecutor> handlerExecutors, 
        INotification notification, 
        CancellationToken cancellationToken)
    {
        foreach (var handlerExecutor in handlerExecutors)
        {
            try
            {
                await handlerExecutor.HandlerCallback(notification, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    "通知处理器失败,但继续执行其他处理器: {HandlerType}",
                    handlerExecutor.HandlerInstance.GetType().Name);
            }
        }
    }
}

🔍 Visual Studio 调试技巧 ​

1. 条件断点 ​

csharp
// 在 Handler 中设置条件断点
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
{
    // 右键点击行号 → 条件 → 输入条件
    // 例如:request.ProductName == "iPhone"
    
    var order = await _repo.CreateAsync(request);
    return new OrderResult { OrderId = order.Id };
}

2. 数据断点(监视变量变化) ​

csharp
// 在 Watch 窗口中添加变量
// 当变量值改变时中断
order.Status

3. 调用堆栈分析 ​

// 当异常发生时,查看完整调用堆栈
MediatR.Pipeline.RequestPostProcessorBehavior.Handle()
MediatR.Pipeline.RequestPreProcessorBehavior.Handle()
YourApp.Handlers.CreateOrderHandler.Handle()  ← 你的代码
Controllers.OrdersController.CreateOrder()

🎯 最佳实践 ​

✅ 推荐做法 ​

csharp
// 1. 始终记录请求 ID
var requestId = Guid.NewGuid();
_logger.LogInformation("处理请求: {RequestId}", requestId);

// 2. 记录性能指标
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds > 500)
    _logger.LogWarning("慢查询: {Duration}ms", stopwatch.ElapsedMilliseconds);

// 3. 使用结构化日志
_logger.LogInformation("订单创建: {@Order}", order);

// 4. 捕获并记录异常上下文
catch (Exception ex)
{
    _logger.LogError(ex, "处理失败: {RequestName} {@Request}", 
        typeof(TRequest).Name, request);
    throw;
}

❌ 避免的做法 ​

csharp
// 1. 不要吞掉异常
catch (Exception ex)
{
    _logger.LogError(ex.ToString());
    return default; // ❌ 静默失败
}

// 2. 不要在生产环境输出敏感数据
_logger.LogInformation("用户密码: {Password}", password); // ❌ 安全风险

// 3. 不要过度日志
foreach (var item in list)
{
    _logger.LogInformation("处理项: {Item}", item); // ❌ 可能产生大量日志
}

🎓 总结 ​

调试工具箱 ​

工具用途适用场景
详细日志追踪请求流程日常开发和问题排查
管道追踪可视化行为链理解执行顺序
DiagnosticSource深度诊断性能分析和监控
BenchmarkDotNet性能基准优化前后对比
VS 调试器逐步调试复杂逻辑排查

关键要点 ​

  1. ✅ 使用结构化日志记录关键信息
  2. ✅ 为每个请求分配唯一 ID
  3. ✅ 监控性能指标和慢查询
  4. ✅ 实现容错的通知发布
  5. ✅ 使用基准测试验证优化效果

💡 提示: 良好的日志和监控是快速定位问题的关键!

Released under the CC BY-SA 4.0 License.