2016-07-28 47 views
4

我試圖記錄所有出站請求轉到服務引用,包括完整的請求和響應正文。我以爲我有一個使用behaviorExtensions的解決方案,但是在部署之後,很明顯擴展是在多個請求之間共享的。IEndpointBehavior生命週期/日誌記錄服務調用

這裏是我當前的代碼:

public class LoggingBehaviorExtender : BehaviorExtensionElement 
{ 
    public override Type BehaviorType => typeof(LoggingRequestExtender); 
    protected override object CreateBehavior() { return new LoggingRequestExtender(); } 
} 

public class LoggingRequestExtender : IClientMessageInspector, IEndpointBehavior 
{ 
    public string Request { get; private set; } 
    public string Response { get; private set; } 

    #region IClientMessageInspector 

    public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) 
    { 
     Request = request.ToString(); 
     Response = null; 
     return null; 
    } 
    public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState) 
    { 
     Response = reply.ToString(); 
    } 

    #endregion 

    #region IEndpointBehavior 

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } 

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) 
    { 
     clientRuntime.MessageInspectors.Add(this); 
    } 

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { } 

    public void Validate(ServiceEndpoint endpoint) { } 

    #endregion 
} 

然後,當我到達記錄點,我提取行爲......

var lre = client.Endpoint.Behaviors.OfType<LoggingRequestExtender>().FirstOrDefault(); 
var req = lre?.Request; 
var resp = lre?.Response; 

添加調試記錄到LoggingRequestExtender,我發現它僅針對多個請求實例化一次。

有沒有辦法確保這個行爲類爲每個線程新鮮實例化?或者在進行服務呼叫時有更好的方式獲得完整的請求/響應主體?

編輯/部分答案:

因爲寫這個我已經發現,通過BeforeSendRequest返回的值被傳遞到AfterReceiveReply作爲correlationState這樣我就可以使用GUID連接請求和響應:

public virtual object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel) 
{ 
    var guid = Guid.NewGuid(); 
    WebServiceLog.LogCallStart(guid, channel.RemoteAddress.ToString(), request.ToString()); 
    return guid; 
} 

public virtual void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState) 
{ 
    Guid guid = (Guid)correlationState; 
    WebServiceLog.LogCallEnd(guid, reply.ToString()); 
} 

我發現這種方法有兩個缺陷。其中一個是宜人的,就是這需要一個日誌插入,然後更新而不是單個插入。

第二個問題更多:在發生異常(例如超時)的情況下,我們從未打過AfterRecieveSupply,因此日誌不知道發生了什麼。我可以單獨記錄異常...

try 
{ 
    response = client.SomeFunction(request); 
} 
catch (Exception ex) 
{ 
    AppLog.Error("Some function failed", ex); 
} 

...但我看不到的訪問的GUID BeforeSendRequest之外的方式/ AfterReceiveReply所以我沒有什麼異常日誌綁到服務請求日誌。

回答

0

這有幾種方法。

1,您所描述的不必單獨記錄呼叫的情況不必如此。如果您的WCF服務位於非負載均衡服務器中,則只需使用Guid作爲密鑰將請求添加到MemoryCache。當請求進入時,請拔出請求並登錄。爲了捕獲超時的調用,你可以在一個線程上運行一個進程,每隔x分鐘檢查一次MemoryCache,以便拔出並記錄(使用足夠的鎖來確保線程安全)。

如果WCF服務處於負載平衡環境中,那麼您所做的全部操作與上述操作相同,但存儲到無sql類型的數據存儲。

2,代碼是否在您的作用域內進行呼出呼叫以進行更改?如果是這樣,您可以放棄創建行爲擴展並改爲創建定製消息記錄器。然後使用實現IDisposable你可以寫漂亮的代碼是這樣一類..

RequestMessage request = new RequestMessage(); 
ResponseMessage response = null; 

using (_messageLogger.LogMessage(request,() => response, CallContextHelper.GetContextId(), enabled)) 
{ 
    response = _outboundService.DoSomething(request); 
} 

這不會需要另一個進程捕捉到任何超時將在Dispose方法來處理線程。

如果你需要更清晰,然後讓我知道,希望這可以幫助你...