Ведение журнала исключений для служб WCF с использованием ELMAH
Вопрос
Мы используем отличный ЭЛЬМА для работы с необработанными исключениями в веб-приложении ASP.NET 3.5.Это работает очень хорошо для всего сайта, за исключением служб WCF, которые используются с использованием функций REST.Когда в методах операции возникает исключение, которое не обрабатывается кодом приложения, WCF обрабатывает его различными способами в зависимости от контрактов служб и параметров конфигурации.Это означает, что исключение не вызывает событие ASP.NET HttpApplication.Error, которое ЭЛЬМА использует.Мне известны два решения для решения этой проблемы:
- Оберните все вызовы методов в попытку { } catch(Exception ex) { Elmah.ErrorSignal.FromCurrentContext().Raise(ex);бросать;} для явного вызова Elmah внутри блока catch.
- Использовать Иеррорхандлер как описано в Уилл Хьюз Сообщение блога Заставить WCF и ELMAH хорошо работать вместе чтобы выделить вызов ELMAH в отдельный ErrorHandler.
Первый вариант чрезвычайно прост, но не совсем СУХОЙ.Второй вариант требует, чтобы вы украсили каждую службу настраиваемым атрибутом только после реализации атрибута и ErrorHandler.Я сделал это на основе Уилла работает, но я хочу убедиться, что это правильный подход прежде чем публиковать код.
Есть ли лучший способ, который я пропустил?
Документация MSDN для Иеррорхандлер говорит, что HandleError метод — это место для ведения журнала, но ЭЛЬМА обращается к HttpContext.Current.Экземпляр приложения, который в этом методе имеет значение null, хотя HttpContext.Current доступен.Вызов Elmah внутри метода ProvideFault — это обходной путь, поскольку установлен ApplicationInstance, но это не соответствует цели, описанной в документации API. Я что-то упускаю? В документации указано, что вы не должны полагаться на вызов метода HandleError в рабочем потоке, поэтому, возможно, ApplicationInstance имеет значение null в этой области.
Решение
Решение из моего сообщения в блоге (на которое есть ссылка в ОП) было основано на существующем решении, которое мы использовали/используем для изменения кодов ответов 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);
}
}
}
}
Пример использования
Украсьте свои службы WCF атрибутом ServiceErrorBehaviour:
[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!
Я сделал это на основе работы Уилла, но я хочу убедиться, что это правильный подход перед публикацией кода.
Я думаю, что это отличный подход (спасибо Уиллу за эту публикацию!).Я не думаю, что Уилл или вы что-то пропустили.Реализация IErrorHandler является предпочтительным способом перехвата всех возможных исключений на стороне сервера, которые в противном случае могли бы привести к сбою (разрушению) канала связи, и поэтому это естественное место для подключения некоторых журналов, таких как ELMAH.
Марк
Для некоторых это может быть очевидно, но я потратил довольно много времени, пытаясь понять, почему мой HttpContext.Current был нулевым, несмотря на то, что я следовал всем превосходным ответам Уилла Хьюза.К моему смущению, я понял, что это произошло потому, что моя служба 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, но стоит рассмотреть еще один вариант — подключиться к WCF с помощью IDispatchMessageInspector вместо IErrorHandler.