Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get accurate Exception stack in WCF Task-based Operation

Tags:

c#

wcf

I'm using the WCF IErrorHandler interface to trap and log errors on the server side of a WCF service. However, the StackTrace of the exception passed to HandleError and ProvideFault is messed up:

at System.ServiceModel.Dispatcher.TaskMethodInvoker.InvokeEnd(Object instance, Object[]& outputs, IAsyncResult result) at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeEnd(MessageRpc& rpc) at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage7(MessageRpc& rpc) ....lots more

I'm not surprised to see random Dispatcher methods in the stack trace, but I assumed I would see my own method at the top of the stack.

I've determined that this only happens on operations that look like

[OperationContract]
public Task<int> MyOperation()
{
  throw new ApplicationException("test");
}

Services that look like this have a proper stack trace for me to log:

[OperationContract]
public int MySyncOperation()
{
  throw new ApplicationException("test");
}

As an FYI, here's what the error handler methods are like:

public class MyErrorHandler : IErrorHandler
{
  public bool HandleError(Exception error)
  {
    //variable 'error' has wrong stack trace if exception sourced from Task<int> operation
    return false;
  }
  public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
  {
    //variable 'error' has wrong stack trace if exception sourced from Task<int> operation
  }
}

Note that the exception type and message are correct, so it's as if they're incorrectly rethrowing my exception somewhere with a 'throw ex' rather than just 'throw';

Is there any way to get the correct stack trace of the exception from one of the IErrorHandler methods?

like image 836
Clyde Avatar asked Nov 05 '14 16:11

Clyde


1 Answers

I ended up solving this using the following custom operation invoker. My only goal was to log the errors with the proper stack trace, so the error that ends up being thrown retains the poor stack trace.

public class ErrorLoggingOperationInvokerFacade : IOperationInvoker
{
    private readonly IOperationInvoker _invoker;
    private readonly ILog _log;

    public ErrorLoggingOperationInvokerFacade(IOperationInvoker invoker, ILog log)
    {
        _invoker = invoker;
        _log = log;
    }

    public object[] AllocateInputs()
    {
        return _invoker.AllocateInputs();
    }

    public object Invoke(object instance, object[] inputs, out object[] outputs)
    {
        return _invoker.Invoke(instance, inputs, out outputs);
    }

    public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
    {
        return _invoker.InvokeBegin(instance, inputs, callback, state);
    }

    public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
    {
        var task = result as Task;
        if (task != null && task.IsFaulted && task.Exception != null)
        {
            foreach (var error in task.Exception.InnerExceptions)
            {
                _log.Log(error);
            }
        }

        return _invoker.InvokeEnd(instance, out outputs, result);
    }

    public bool IsSynchronous { get { return _invoker.IsSynchronous; } }
}

It can be attached with an attribute on your service class or method:

public class LogErrorsAttribute : Attribute, IServiceBehavior, IOperationBehavior
{
    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (var operation in serviceHostBase.Description.Endpoints.SelectMany(endpoint => endpoint.Contract.Operations))
        {
            if (!operation.Behaviors.Any(b => b is LogErrorsAttribute))
                operation.Behaviors.Add(this);
        }
    }

    public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
    {
        dispatchOperation.Invoker = new ErrorLoggingOperationInvokerFacade(dispatchOperation.Invoker, WcfDependencyManager.ResolveLogger());
    }

    public void Validate(OperationDescription operationDescription) { }
    public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
    public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
}

Previously I was logging the errors in an implementation of the IErrorHandler interface, but at that point the stack trace was already messed up. I tried to modify the operation invoker to throw an exception with the proper stack trace but I never got it to work just right. For some reason, my custom fault exceptions turned into the generic fault exceptions, so I abandoned that approach.

like image 181
Morten Christiansen Avatar answered Oct 24 '22 14:10

Morten Christiansen