使用 ELMAH 记录 WCF 服务的异常
题
我们正在使用优秀的 埃尔玛 处理 ASP.NET 3.5 Web 应用程序中未处理的异常。这对于除使用 REST 功能使用的 WCF 服务之外的所有站点都非常有效。当操作方法中发生应用程序代码未处理的异常时,WCF 根据服务契约和配置设置以各种方式进行处理。这意味着异常最终不会触发 ASP.NET HttpApplication.Error 事件 埃尔玛 用途。我知道处理这个问题的两个解决方案是:
- 将所有方法调用包装在 try { } catch(Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex); 中扔;在 catch 块中显式调用 Elmah。
- 使用 错误处理程序 如中所述 威尔·休斯的 博客文章 让 WCF 和 ELMAH 和谐相处 将对 ELMAH 的调用分解为单独的 ErrorHandler。
第一个选项非常简单,但并不完全是 干燥. 。第二个选项只需要您在实现属性和 ErrorHandler 后使用自定义属性来装饰每个服务。我这样做是基于 威尔的 工作,但我想验证这是 正确的做法 在发布代码之前。
有没有更好的方法我错过了?
MSDN 文档 错误处理程序 说 处理错误 方法是进行日志记录的地方,但是 埃尔玛 访问 HttpContext.Current。应用程序实例, ,即使 HttpContext.Current 可用,该方法中的值仍为 null。在 ProvideFault 方法中调用 Elmah 是一种解决方法,因为已设置 ApplicationInstance,但这与 API 文档中描述的意图不符。 我在这里错过了什么吗? 该文档确实指出您不应依赖在操作线程上调用的 HandleError 方法,这可能就是 ApplicationInstance 在此范围内为 null 的原因。
解决方案
我的博客文章中的解决方案(在 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
{
// ...
}
其他提示
创建BehaviorExtensionElement时,甚至可以使用配置激活行为:
public class ErrorBehaviorExtensionElement : BehaviorExtensionElement
{
public override Type BehaviorType
{
get { return typeof(ServiceErrorBehaviourAttribute); }
}
protected override object CreateBehavior()
{
return new ServiceErrorBehaviourAttribute(typeof(HttpErrorHandler));
}
}
配置:
<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 服务结合使用!
我已经根据Will的工作来完成此操作,但我想验证这是发布代码之前的正确方法。
我认为这是一个很好的方法(感谢威尔的这篇文章!)。我想威尔或者你都没有错过任何事情。实现 IErrorHandler 是捕获所有可能的服务器端异常的首选方法,否则可能会导致通信通道出现故障(被破坏),因此它是挂钩某些日志记录(如 ELMAH)的自然位置。
马克
这对某些人来说可能是显而易见的,但我花了相当长的时间试图弄清楚为什么我的 HttpContext.Current 为空,尽管遵循了 Will Hughes 的所有出色答案。尴尬的是,我意识到这是因为我的 WCF 服务是由 MSMQ 消息激活的。
我最终重写了 ProvideFault()
方法:
if (HttpContext.Current == null)
{
ErrorLog.GetDefault(null).Log(new Error(error));
}
else
{
ErrorSignal.FromCurrentContext().Raise(error);
}
我无法使用 WCF 数据服务获得建议的答案。我连接了行为属性等,但仍然没有记录任何错误。相反,我最终将以下内容添加到服务实现中:
protected override void HandleException(HandleExceptionArgs args)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(args.Exception);
base.HandleException(args);
}
我还没有尝试使用 REST 明确地执行此操作,并且自己也没有使用 ELMAH,但值得研究的另一个选项可能是使用 IDispatchMessageInspector 而不是 IErrorHandler。