使用 ELMAH 记录 WCF 服务的异常

发布于 2024-07-21 03:50:19 字数 1711 浏览 11 评论 0 原文

我们正在使用优秀的 ELMAH 来处理 ASP.NET 3.5 Web 应用程序中未处理的异常。 这对于除使用 REST 功能使用的 WCF 服务之外的所有站点都非常有效。 当操作方法中发生应用程序代码未处理的异常时,WCF 根据服务契约和配置设置以各种方式进行处理。 这意味着该异常最终不会触发 ELMAH 使用的 ASP.NET HttpApplication.Error 事件。 我知道处理这个问题的两个解决方案是:

  • 将所有方法调用包装在 try { } catch(Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); 中。 扔; 在 catch 块中显式调用 Elmah。
  • 按照 IErrorHandler "http://will.hughesfamily.net.au/about/" rel="noreferrer">Will Hughes 博客文章 让 WCF 和 ELMAH 协同工作,将对 ELMAH 的调用分解为单独的 ErrorHandler。

第一个选项非常简单,但并不完全DRY。 第二个选项只需要您在实现属性和 ErrorHandler 后使用自定义属性来装饰每个服务。 我是根据Will 的工作完成此操作的,但我想验证这是否是在发布代码之前正确的方法

我错过了更好的方法吗?

IErrorHandler 表示 HandleError 方法是进行日志记录的地方,但是 ELMAH 访问 HttpContext.Current.ApplicationInstance,即使 HttpContext.Current 可用,该方法在此方法中仍为 null。 在 ProvideFault 方法中调用 Elmah 是一种解决方法,因为已设置 ApplicationInstance,但这与 API 文档中描述的意图不符。 我在这里遗漏了什么吗?文档确实指出您不应该依赖在操作线程上调用的 HandleError 方法,这可能就是为什么 ApplicationInstance 在此范围内为 null 的原因。

We are using the excellent ELMAH to deal with unhandled exceptions in an ASP.NET 3.5 web application. This works extremely well for all of the site apart from WCF services which are being consumed using the REST features. When an exception occurs within the operation methods that is not handled by the application code, WCF handles it in various ways depending on the service contracts and configuration settings. This means that the exception does not end up firing the ASP.NET HttpApplication.Error event that ELMAH uses. The two solutions I am aware of to deal with this are:

  • Wrap all method calls in a try { } catch(Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); throw; } to explicitly call Elmah within the catch block.
  • Use IErrorHandler as described in Will Hughes' blog post Making WCF and ELMAH play nice together to factor out the call to ELMAH to a separate ErrorHandler.

The first option is extremely simple but is not exactly DRY. The second option only requires you to decorate each service with the custom attribute after implementing the attribute and the ErrorHandler. I have done this based on Will's work but I want to verify that this is the correct approach before posting the code.

Is there a better way that I have missed?

The MSDN documenation for IErrorHandler says that the HandleError method is the place to do the logging but ELMAH accesses the HttpContext.Current.ApplicationInstance, which is null within this method even though HttpContext.Current is available. Making the call to Elmah within the ProvideFault method is a workaround as ApplicationInstance is set but this does not match the intent described in the API documentation. Am I missing something here? The documentation does state that you should not rely on the HandleError method being called on the operation thread which may be why ApplicationInstance is null in this scope.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

小嗲 2024-07-28 03:50:20

我的博客文章中的解决方案(在 OP 中引用)基于我们曾经/正在用来在错误状态期间更改 HTTP 响应代码的现有解决方案。

因此,对于我们来说,将异常传递给 ELMAH 只需一行更改。 如果有更好的解决方案,我也很想知道。

对于后代/参考和潜在的改进 - 这是当前解决方案的代码。

HttpErrorHandler 和 ServiceErrorBehaviourAttribute 类

using System;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.Net;
using System.Web;
using Elmah;
namespace YourApplication
{
    /// <summary>
    /// Your handler to actually tell ELMAH about the problem.
    /// </summary>
    public class HttpErrorHandler : IErrorHandler
    {
        public bool HandleError(Exception error)
        {
            return false;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            if (error != null ) // Notify ELMAH of the exception.
            {
                if (System.Web.HttpContext.Current == null)
                    return;
                Elmah.ErrorSignal.FromCurrentContext().Raise(error);
            }
        }
    }
    /// <summary>
    /// So we can decorate Services with the [ServiceErrorBehaviour(typeof(HttpErrorHandler))]
    /// ...and errors reported to ELMAH
    /// </summary>
    public class ServiceErrorBehaviourAttribute : Attribute, IServiceBehavior
    {
        Type errorHandlerType;

        public ServiceErrorBehaviourAttribute(Type errorHandlerType)
        {
            this.errorHandlerType = errorHandlerType;
        }

        public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler;
            errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }
    }
}

用法示例 使用

ServiceErrorBehaviour 属性装饰您的 WCF 服务:

[ServiceContract(Namespace = "http://example.com/api/v1.0/")]
[ServiceErrorBehaviour(typeof(HttpErrorHandler))]
public class MyServiceService
{
  // ...
}

The solution from my blog post (referenced in the OP) was based on an existing solution we were/are using to alter HTTP Response Codes during an error state.

So, for us it was a one-line change to pass the Exception to ELMAH. If there's a better solution, I'd love to know about it too.

For Posterity/Reference, and potential improvement - here's the code from the current solution.

HttpErrorHandler and ServiceErrorBehaviourAttribute Classes

using System;
using System.ServiceModel;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Collections.ObjectModel;
using System.Net;
using System.Web;
using Elmah;
namespace YourApplication
{
    /// <summary>
    /// Your handler to actually tell ELMAH about the problem.
    /// </summary>
    public class HttpErrorHandler : IErrorHandler
    {
        public bool HandleError(Exception error)
        {
            return false;
        }

        public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
        {
            if (error != null ) // Notify ELMAH of the exception.
            {
                if (System.Web.HttpContext.Current == null)
                    return;
                Elmah.ErrorSignal.FromCurrentContext().Raise(error);
            }
        }
    }
    /// <summary>
    /// So we can decorate Services with the [ServiceErrorBehaviour(typeof(HttpErrorHandler))]
    /// ...and errors reported to ELMAH
    /// </summary>
    public class ServiceErrorBehaviourAttribute : Attribute, IServiceBehavior
    {
        Type errorHandlerType;

        public ServiceErrorBehaviourAttribute(Type errorHandlerType)
        {
            this.errorHandlerType = errorHandlerType;
        }

        public void Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        public void AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            IErrorHandler errorHandler;
            errorHandler = (IErrorHandler)Activator.CreateInstance(errorHandlerType);
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(errorHandler);
            }
        }
    }
}

Usage Example

Decorate your WCF Services with the ServiceErrorBehaviour Attribute:

[ServiceContract(Namespace = "http://example.com/api/v1.0/")]
[ServiceErrorBehaviour(typeof(HttpErrorHandler))]
public class MyServiceService
{
  // ...
}
源来凯始玺欢你 2024-07-28 03:50:20

创建BehaviorExtensionElement时,甚至可以使用配置激活行为:

public class ErrorBehaviorExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(ServiceErrorBehaviourAttribute); }
    }

    protected override object CreateBehavior()
    {
        return new ServiceErrorBehaviourAttribute(typeof(HttpErrorHandler));
    }
}

Config:

<system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="elmah" type="Namespace.ErrorBehaviorExtensionElement, YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <elmah />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

这样也可以将ELMAH与RIA服务结合使用!

When creating a BehaviorExtensionElement it is even possible to activate the behavior using config:

public class ErrorBehaviorExtensionElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(ServiceErrorBehaviourAttribute); }
    }

    protected override object CreateBehavior()
    {
        return new ServiceErrorBehaviourAttribute(typeof(HttpErrorHandler));
    }
}

Config:

<system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="elmah" type="Namespace.ErrorBehaviorExtensionElement, YourAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <elmah />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

That way it is also possible to use ELMAH in combination with RIA services!

誰認得朕 2024-07-28 03:50:20

这对某些人来说可能是显而易见的,但我花了相当长的时间试图弄清楚为什么我的 HttpContext.Current 为空,尽管遵循了 Will Hughes 的所有出色答案。 尴尬的是,我意识到这是因为我的 WCF 服务是由 MSMQ 消息激活的。

我最终重写了 ProvideFault() 方法:

if (HttpContext.Current == null)
{
    ErrorLog.GetDefault(null).Log(new Error(error));
}
else
{
    ErrorSignal.FromCurrentContext().Raise(error);
}

This may well be obvious to some people but I just spent quite a while trying to figure out why my HttpContext.Current was null despite following all of Will Hughes' excellent answer. Embarassingly, I realised that this was because my WCF service is activated by a MSMQ message.

I ended up rewriting the ProvideFault() method:

if (HttpContext.Current == null)
{
    ErrorLog.GetDefault(null).Log(new Error(error));
}
else
{
    ErrorSignal.FromCurrentContext().Raise(error);
}
长发绾君心 2024-07-28 03:50:20

我根据 Will 的工作完成了这个
但我想验证一下这是
发布前的正确做法
代码。

我认为这是一个很好的方法(感谢威尔的这篇文章!)。 我想威尔或者你都没有错过任何事情。 实现 IErrorHandler 是捕获所有可能的服务器端异常的首选方法,否则可能会导致通信通道出现故障(被破坏),因此它是挂钩某些日志记录(如 ELMAH)的自然位置。

马克

I have done this based on Will's work
but I want to verify that this is the
correct approach before posting the
code.

I think this is a great approach (kudos to Will for this posting!). I don't think Will or you have missed anything here. Implementing IErrorHandler is the preferred way of capturing all possible server-side exceptions that could otherwise cause the communication channel to be faulted (torn down) and thus it's a natural place to hook in some logging like ELMAH.

Marc

哽咽笑 2024-07-28 03:50:20

我无法使用 WCF 数据服务获得建议的答案。 我连接了行为属性等,但仍然没有记录任何错误。 相反,我最终将以下内容添加到服务实现中:

protected override void HandleException(HandleExceptionArgs args)
{
    Elmah.ErrorSignal.FromCurrentContext().Raise(args.Exception);
    base.HandleException(args);
}

I was unable to get the proposed answer working with a WCF Data Service. I wired up the behavior attribute, etc, but still did not get any errors logged. Instead, I ended up adding the following to the service implementation:

protected override void HandleException(HandleExceptionArgs args)
{
    Elmah.ErrorSignal.FromCurrentContext().Raise(args.Exception);
    base.HandleException(args);
}
小忆控 2024-07-28 03:50:20

我还没有尝试使用 REST 明确地执行此操作,并且自己也没有使用 ELMAH,但值得研究的另一个选项可能是使用 IDispatchMessageInspector 而不是 IErrorHandler。

I haven't tried doing this explicitly with the REST stuff, and haven't used ELMAH myself, but another option worth looking into might be to hook into WCF using an IDispatchMessageInspector instead of an IErrorHandler.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文