在處理WCF異常的時候,有大概幾種方式:
第一種是在配置文件中,將includeExceptionDetailInFaults設置為true
<behavior name="serviceDebuBehavior"><serviceDebug includeExceptionDetailInFaults="true" /></behavior>
但是這種方式會導致敏感信息泄漏的危險,一般我們僅僅在調試的時候才開啟該屬性,如果已經發布,為了安全,我們一般會設置成false。
第二種方法是自定義錯誤,通過FaultException直接指定錯誤信息。
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class CalculatorService : ICalculator
{
throw new FaultException("被除數y不能為零!");
}
但這樣我們還必須為WCF的方法里顯式的去拋出異常,如果能像.NET一樣,在Global里直接捕獲全局的異常,並寫入log4net日志多好,有沒有方法在wcf里如果出現異常,異常日志寫入服務器斷,同時拋給客戶端呢?
要實現這個,需要三步
第一步: 我們需要實現IErrorHandler接口,實現他的兩個方法
bool HandleError(Exception error);
void ProvideFault(Exception error, MessageVersion version, ref Message fault);
其中HandleError是true表示終止當前session
在ProvideFault方法里我們可以寫我們要拋給客戶端的自定義的錯誤,同時也可以在服務器端寫異常日志。
我們實現一個GlobalExceptionHandler ,繼承自IErrorHandler,同時在ProvideFault里通過log4net寫服務器端日志,如下:
namespace WcfService
{
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
/// <summary>
/// GlobalExceptionHandler
/// </summary>
public class GlobalExceptionHandler : IErrorHandler
{
/// <summary>
/// 測試log4net
/// </summary>
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(GlobalExceptionHandler));
#region IErrorHandler Members
/// <summary>
/// HandleError
/// </summary>
/// <param name="ex">ex</param>
/// <returns>true</returns>
public bool HandleError(Exception ex)
{
return true;
}
/// <summary>
/// ProvideFault
/// </summary>
/// <param name="ex">ex</param>
/// <param name="version">version</param>
/// <param name="msg">msg</param>
public void ProvideFault(Exception ex, MessageVersion version, ref Message msg)
{
//// 寫入log4net
log.Error("WCF異常", ex);
var newEx = new FaultException(string.Format("WCF接口出錯 {0}", ex.TargetSite.Name));
MessageFault msgFault = newEx.CreateMessageFault();
msg = Message.CreateMessage(version, msgFault, newEx.Action);
}
#endregion
}
}
第二步:現在我們需要創建一個自定義的Service Behaviour Attribute,讓WCF知道當WCF任何異常發生的時候,我們通過這個自定義的Attribute來處理。實現這個需要繼承IServiceBehavior接口,並在此類的構造函數里,我們獲取到錯誤類型。
一旦ApplyDispatchBehavior行為被調用時,我們通過Activator.CreateInstance創建錯誤handler,並把這個錯誤添加到每個channelDispatcher中。
ApplyDispatchBehavior
channelDispatcher中文叫信道分發器,當我們的ServiceHost調用Open方法,WCF就會創建我們的多個信道分發器(ChannelDispatcher),每個ChannelDispatcher都會擁有一個信道監聽器(ChannelListener),ChannelListener就有一直在固定的端口監聽,等到Message的到來,調用AcceptChannel構建信道形成信道棧,開始對Message的處理。
using System; using System.Collections.ObjectModel; using System.ServiceModel; using System.ServiceModel.Channels; using System.ServiceModel.Description; using System.ServiceModel.Dispatcher; namespace WcfService { public class GlobalExceptionHandlerBehaviourAttribute : Attribute, IServiceBehavior { private readonly Type _errorHandlerType; public GlobalExceptionHandlerBehaviourAttribute(Type errorHandlerType) { _errorHandlerType = errorHandlerType; } #region IServiceBehavior Members 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) { var handler = (IErrorHandler) Activator.CreateInstance(_errorHandlerType); foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers) { var channelDispatcher = dispatcherBase as ChannelDispatcher; if (channelDispatcher != null) channelDispatcher.ErrorHandlers.Add(handler); } } #endregion } }
第三步:在我們的WCF的類上,加上GlobalExceptionHandlerBehaviour
using System; namespace WcfService { [GlobalExceptionHandlerBehaviour(typeof (GlobalExceptionHandler))] public class SomeService : ISomeService { #region ISomeService Members public string SomeFailingOperation() { throw new Exception("Kaboom"); return null; } #endregion } }
這時候,客戶端和服務器端都已經分別能記錄到錯誤的異常日志了。
附:log4net配置
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler, log4net, Version=1.2.10.0, Culture=Neutral, PublicKeyToken=bf100aa01a5c2784" />
</configSections>
<log4net>
<!-- 日志文件部分log輸出格式的設定 -->
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="./Logs\Log_" />
<appendToFile value="true" />
<rollingStyle value="Date" />
<datePattern value="yyyyMMdd'.txt'" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="------------------------------------------------------------
" />
<ConversionPattern value="%date [%thread] %-5level %logger [%ndc] - %message%newline%newline%newline" />
</layout>
</appender>
<appender name="WcfService.Api_Error" type="log4net.Appender.RollingFileAppender" LEVEL="ERROR">
<file value="./Logs\API\logError_" />
<appendToFile value="true" />
<datePattern value="yyyyMMdd'.txt'" />
<rollingStyle value="Date" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="[Header] " />
<footer value="[Footer] " />
<conversionPattern value="%date{dd/MM/yyyy-HH:mm:ss} %m%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="ERROR" />
<param name="LevelMax" value="ERROR" />
</filter>
</appender>
<appender name="WcfService.Api_Info" type="log4net.Appender.RollingFileAppender" LEVEL="INFO">
<file value="./Logs\API\logInfo_" />
<appendToFile value="true" />
<datePattern value="yyyyMMdd'.txt'" />
<rollingStyle value="Date" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<header value="[Header] " />
<footer value="[Footer] " />
<conversionPattern value="%date{dd/MM/yyyy-HH:mm:ss} %m%newline%exception" />
</layout>
<filter type="log4net.Filter.LevelRangeFilter">
<param name="LevelMin" value="INFO" />
<param name="LevelMax" value="INFO" />
</filter>
</appender>
<root>
<level value="All" />
<appender-ref ref="RollingLogFileAppender" />
</root>
<logger name="WcfService" additivity="false">
<appender-ref ref="WcfService.Api_Info" />
<appender-ref ref="WcfService.Api_Error" />
</logger>
</log4net>
</configuration>