You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用C# Unity Interception实现带关联ID的调用链路日志

解决Unity Interceptor日志关联ID的实现方案

这个问题我太熟了!嵌套调用的日志追踪确实容易出现这种“分不清谁调用谁”的情况,用关联ID绑定整个调用链是最直接的解决方案,刚好Unity Interceptor可以配合上下文存储来实现,我给你捋捋具体怎么做:

核心思路

要让嵌套调用的日志共享同一个关联ID,关键是在调用链的上下文中传递这个ID——也就是进入MethodA时生成ID,调用MethodB的时候直接复用这个ID,而不是每次都生成新的。这里推荐用AsyncLocal<string>来存储关联ID,它能在同步/异步调用中都保持上下文唯一性,不会出现串号的问题。

具体实现步骤

1. 写一个关联ID管理器

先整个静态类来管关联ID,用AsyncLocal存,确保每个调用链有独立的ID:

public static class CorrelationIdManager
{
    private static readonly AsyncLocal<string> _correlationId = new AsyncLocal<string>();

    public static string GetCorrelationId()
    {
        if (string.IsNullOrEmpty(_correlationId.Value))
        {
            // 这里用简化的Guid当ID,你也可以换成自增序列、业务标识之类的
            _correlationId.Value = Guid.NewGuid().ToString("N").Substring(0, 6);
        }
        return _correlationId.Value;
    }

    // 可选:手动设置ID的方法,比如外部调用时指定自定义ID
    public static void SetCorrelationId(string id)
    {
        _correlationId.Value = id;
    }

    // 可选:调用链结束后清空ID,避免上下文残留
    public static void ClearCorrelationId()
    {
        _correlationId.Value = null;
    }
}

2. 自定义日志拦截器

接下来写一个实现IInterceptor的日志拦截器,在方法调用前后获取/复用关联ID,输出带ID的日志:

public class LoggingInterceptor : IInterceptor
{
    private readonly ILogger _logger; // 这里假设你用了ILogger,换成自己的日志组件也没问题

    public LoggingInterceptor(ILogger logger)
    {
        _logger = logger;
    }

    public void Intercept(IInvocation invocation)
    {
        var correlationId = CorrelationIdManager.GetCorrelationId();
        var methodName = invocation.Method.Name;

        // 输出调用开始日志
        _logger.LogInformation("[Id: {CorrelationId}] Method {MethodName} is called", correlationId, methodName);

        try
        {
            // 执行目标方法——这里会触发嵌套方法的拦截器,此时CorrelationId已经存在,直接复用
            invocation.Proceed();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[Id: {CorrelationId}] Method {MethodName} threw an exception", correlationId, methodName);
            throw; // 别吞异常,继续往上抛
        }
        finally
        {
            // 输出调用完成日志
            _logger.LogInformation("[Id: {CorrelationId}] Method {MethodName} completed", correlationId, methodName);
            // 小贴士:如果是顶级入口方法,可以在这里调用ClearCorrelationId()清理上下文,嵌套方法别清,留给上层处理
        }
    }
}

3. 配置Unity容器

把拦截器注册到Unity容器,确保需要拦截的类都能用上:

var container = new UnityContainer();
// 注册日志组件(示例用Microsoft.Extensions.Logging,换成Serilog/NLog都可以)
container.RegisterInstance<ILogger>(LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger("App"));
// 注册拦截器
container.RegisterType<LoggingInterceptor>();
// 注册需要拦截的服务类,并绑定拦截器
container.RegisterType<IMyService, MyService>(
    new Interceptor<InterfaceInterceptor>(),
    new InterceptionBehavior<LoggingInterceptor>()
);

4. 测试调用链

假设你的服务类是这样的:

public interface IMyService
{
    void MethodA();
    void MethodB();
}

public class MyService : IMyService
{
    private readonly IMyService _self; // 用自身实例触发拦截器,注入其他依赖也一样

    public MyService(IMyService self)
    {
        _self = self;
    }

    public void MethodA()
    {
        _self.MethodB(); // 嵌套调用MethodB
    }

    public void MethodB()
    {
        // 你的业务逻辑
    }
}

当调用MethodA时,日志会输出:

[Id: 0a1b2c] Method MethodA is called
[Id: 0a1b2c] Method MethodB is called
[Id: 0a1b2c] Method MethodB completed
[Id: 0a1b2c] Method MethodA completed

如果独立调用MethodB,会生成新的关联ID:

[Id: 3d4e5f] Method MethodB is called
[Id: 3d4e5f] Method MethodB completed

额外注意点

  • 异步方法场景下,AsyncLocal是最优选择,它会自动在异步流中传递上下文;如果用CallContext,异步场景下容易出问题,需要手动处理。
  • 关联ID的生成规则可以按需调整,比如结合请求ID(Web应用)、业务流水号等,方便后续排查问题。

内容的提问来源于stack exchange,提问作者Ray Tang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:00:46