带有 Autofac 和 DynamicProxy2 异常处理的 AOP

cmd*_*del 0 c# aop autofac

我正在尝试集中管理某个方法的异常处理,但我似乎无法到达那里。

public class ExceptionInterceptor : IInterceptor
{
    private readonly Logger _logger;

    public ExceptionInterceptor(Logger logger)
    {
        _logger = logger;
        Measure.Configure(new StatsdConfig());
    }

    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
            //if ((Task<System.Threading.Tasks.VoidTaskReturn>) invocation.ReturnValue.Status == "Failed")
            //{
            //    throw new Exception(invocation.ReturnValue.Exception[0]);
            //}
        }
        catch (Exception e)
        {
            var errorMessage =
                String.Format(
                    "An error occurred while retrieving fund data. Error Message: {0} Inner Exception: {1}",
                    e.Message, e.InnerException != null ? e.InnerException.Message : "<None>");

            _logger.Log(errorMessage);
            Measure.Counter("Exception", 1);
            Measure.Event("Exception", errorMessage);
            throw;
        }

    }
Run Code Online (Sandbox Code Playgroud)

我将它连接到一个模块中,如下所示:

builder.RegisterType<DataConsumer>().
            As<IConsumer<DataRequest>>().
            EnableInterfaceInterceptors().
            InterceptedBy(typeof(ExceptionInterceptor));

builder.RegisterType<ExceptionInterceptor>().AsSelf();
var loggingInterceptor = new LoggingInterceptor(Logger);
builder.Register(c => loggingInterceptor);
Run Code Online (Sandbox Code Playgroud)

但是,当我在方法调用中抛出异常时,这不会作为抛出的异常冒泡到拦截器,因此它永远不会进入 catch 块。有没有办法在拦截器中捕获被拦截方法的异常?

由于某种原因,我也无法访问 invocation.ReturnValue.Status,因此无法测试是否存在抛出异常以重新抛出。

任何人都可以对我在这里可能或不会做的事情有所了解吗?

Tra*_*lig 6

由于一些不完整的信息,我很难重现您的问题。例如,您注意到IConsumer<T>接口是 MassTransit 接口,但 MassTransit 中的接口不是通用的。它还特别提到接口应该是仅用于 IoC 容器的标记,这可能会对您的接线产生一些影响。

首先,让我们发布一个有效的异常处理示例。为了自包含,我将创建一个IWorker<T>接口来代替IConsumer<T>一个简单的实现:

public interface IWorker<T>
{
    bool DoWork(T message);
}

public class StringWorker : IWorker<string>
{
    public bool DoWork(string message)
    {
        throw new DivideByZeroException();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我将创建一个简单的异常记录器,它只是将信息通过管道传输到控制台。

public class ExceptionLogger : IInterceptor
{
    private readonly TextWriter _output;

    public ExceptionLogger(TextWriter output)
    {
        _output = output;
    }

    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        catch(Exception ex)
        {
            _output.WriteLine("Logged Exception: {0}", ex.Message);
            throw;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以将它连接起来并查看它的运行情况,如下所示:

var builder = new ContainerBuilder();
builder.RegisterInstance(Console.Out).As<TextWriter>();
builder.RegisterType<ExceptionLogger>();
builder.RegisterType<StringWorker>()
       .As<IWorker<string>>()
       .EnableInterfaceInterceptors()
       .InterceptedBy(typeof(ExceptionLogger));

var container = builder.Build();
var worker = container.Resolve<IWorker<string>>();
worker.DoWork("Test!");
Run Code Online (Sandbox Code Playgroud)

当我运行它时,我在程序终止之前在控制台上看到(带有未处理的异常 - 请注意我的处理程序没有吞下它,只是记录了它):

Logged Exception: Attempted to divide by zero.
Run Code Online (Sandbox Code Playgroud)

所以它的工作。

我认为您的环境中还有更多可能会在这里造成麻烦。它可能是您认为无关但实际上很重要的事情。

要调查的一般事项:

  • 临时更新您DataConsumer以立即在接口方法之一内抛出异常。构建容器后,解决一个,IConsumer<DataRequest>并调用该接口方法。它会被记录吗?
  • 查看您希望看到日志记录发生的地方。您是否正在解决并与之合作IConsumer<DataRequest>还是其他什么?它包装了接口方法,而不是对象类型,因此并未涵盖所有方法。
  • 在拦截器中设置一个断点,看看是否有任何调用通过它。如果它没有被击中,它就不会捕获异常。:)
  • 检查是否有任何其他异常处理策略或代码在起作用。例如,有些人使用企业库异常处理块来处理异常,这可能会干扰您的工作。
  • 我没有使用过 MassTransit,但请检查是否有任何其他对象代理正在进行。(令人怀疑,但我知道我已经在使用 Glimpse 之类的产品时遇到过这种情况,因此您最终会使用代理包裹代理,这变得具有挑战性。)
  • 异常是否真的发生在你认为的地方?它可能正在发生并在没有被代理包裹的地方得到处理。

基本上,将工件减少到尽可能小的一组,直到您可以看到它在工作,然后慢慢扩展,直到找到它分解的地方。我不知道这些是否适用于您的情况,但如果我正在排除故障,这些是我会开始考虑的事情。

但是......使用拦截器以 AOP 方式处理异常确实有效,所以是其他原因造成了挑战。