2013-07-11 66 views
1

我正在編寫類似於this blog by Pieter de Rycke中的日誌記錄行爲,但對於NLog。我想出了這個代碼:錯誤的NLog呼叫站點WCF日誌記錄行爲

public class NLogLogger : IParameterInspector 
{ 
    private void Log(Type instanceType, string operationName, string msg) 
    { 
     NLog.Logger logger = LogManager.GetLogger(
      instanceType.FullName, instanceType); 
     logger.Info(msg, instanceType); 
    } 

    public object BeforeCall(string operationName, object[] inputs) 
    { 
     // Retrieve the service instance type for the logger then log the call. 
     OperationContext operationContext = OperationContext.Current; 
     Type instanceType = operationContext.InstanceContext 
      .GetServiceInstance().GetType(); 
     Log(instanceType, operationName, "BeforeCall"); 

     return instanceType; 
    } 

    public void AfterCall(
     string operationName, object[] outputs, 
     object returnValue, object correlationState 
    ) 
    { 
     if (correlationState is Type) 
      Log(correlationState as Type, operationName, "AfterCall"); 
    } 
} 

記錄行爲工作正常。我使用Pieter描述的屬性將它注入服務Example.MyService。我在NLOG目標有這樣的佈局:

${longdate} ${callsite} ${level:uppercase=true} ${message} 

不過,對於操作GetContacts調用點是錯誤的:

2013-07-11 13:32:53.1379 Common.NLogLogger.Log INFO BeforeCall 
2013-07-11 13:32:53.7121 Common.NLogLogger.Log INFO AfterCall 

正確的應該是這樣:

2013-07-11 13:32:53.1379 Example.MyService.GetContacts INFO BeforeCall 
2013-07-11 13:32:53.7121 Example.MyService.GetContacts INFO AfterCall 

我有什麼企圖?

NLog爲記錄包裝或外觀提供了一個特殊的調用站點處理,如StackOverflow answer中所述:將調用站點的類傳遞給日誌記錄方法。

事實上,我在Log()方法中使用了logger.Info(msg, instanceType)。但是,這不起作用,因爲當行爲的BeforeCall()方法正在運行時,調用站點尚未處於堆棧跟蹤中。 WCF還沒有開始運行該操作。 NLog在棧跟蹤中找不到該調用站點,並且無法解開堆棧跟蹤。

我該如何僞造一個callsite?或者我如何顯示日誌行爲的「正確」調用點?

+1

您需要使用Log方法。你不能編寫一個包裝器,只是委託給NLog的Info/Debug/Trace/etc方法。如果你再看看你發佈的鏈接(對於我發佈的回覆),你會看到我使用Log方法展示瞭如何編寫一個包裝器(保存呼叫站點信息)。 – wageoghe

+0

我嘗試了不同的東西,其中還有'Log'方法和'LogEventInfo'類。問題是'NLogLogger'不是真正的包裝。請重新閱讀關於堆棧跟蹤的內容。 – nalply

+1

你說得對,我誤解了你正在做的事。我會再添加一條建議。我不認爲這是一個好的,但它可能會有所幫助。 – wageoghe

回答

0

更新:

感謝您的澄清,我更好地理解您正在嘗試做什麼。您希望從IParameterInspector實現記錄的消息反映出Example.MyService是您的服務(如instanceType參數所指示的)的「Example.MyService.GetContacts」的調用站點,並且「GetContacts」是操作。您可以手動合成呼叫站點信息。你仍然會使用NLog的Logger.Log方法,並且你仍然會創建一個LogEventInfo對象。此外,您可以將「類」和「方法」存儲在LogEventInfo.Properties對象中。而不是根據instanceType(即服務)檢索記錄器(來自LogManager),根據參數檢查器的類型(您的情況爲NLogLogger)檢索記錄器。最後,您可以在NLog.config中添加一條附加規則(並將其應用於NLogLogger類型),以便規則具有不同的日誌記錄格式。您將在包含調用站點信息(存儲在LogEventInfo.Properties集合中)的日誌記錄格式中手動添加一個字段,其位置與其他日誌規則配置中的「真實」調用站點LayoutRenderer相同。

接下來我將發佈一個新版本的NLogLogger實現,它可以完成我上面描述的任務。

public class NLogLogger : IParameterInspector 
{ 
    private static readonly NLog.Logger logger = LogManager.GetCurrentClassLogger(); 

    private void Log(Type instanceType, string operationName, string msg) 
    { 
     NLog.Logger serviceLogger = LogManager.GetLogger(
      instanceType.FullName, instanceType); 

     //Create LogEventInfo with the Logger.Name from the logger associated with the service 
     LogEventInfo le = new LogEventInfo(LogLevel.Info, serviceLogger.Name, msg); 
     le.Properties.Add("fakecallsite", string.Format("{0}.{1}",instanceType.ToString(),operationName); 

     //Log the message using the parameter inspector's logger. 
     logger.Log(typeof(NLogLogger), le); 
    } 

    public object BeforeCall(string operationName, object[] inputs) 
    { 
     // Retrieve the service instance type for the logger then log the call. 
     OperationContext operationContext = OperationContext.Current; 
     Type instanceType = operationContext.InstanceContext 
      .GetServiceInstance().GetType(); 
     Log(instanceType, operationName, "BeforeCall"); 

     return instanceType; 
    } 

    public void AfterCall(
     string operationName, object[] outputs, 
     object returnValue, object correlationState 
    ) 
    { 
     if (correlationState is Type) 
      Log(correlationState, operationName, "AfterCall"); 
    } 
} 

你的NLog.config將有類似這樣的規則。一條規則專門用於您的NLogLogger參數檢查器。它記錄到「f1」並且是「最終」規則,這意味着來自參數檢查器的記錄消息不會被任何其他規則記錄。另一個規則適用於所有其他記錄器。每個都記錄到不同的文件目標,但是兩個文件目標都寫入同一個文件(我認爲這是有效的)。關鍵是每個文件都有自己的佈局。

<logger name="Your.Full.NameSpace.NLogLogger" minlevel="*" writeTo="f1" final="true" /> 
<logger name="*" minlevel="*" writeTo="f2" /> 

你的目標和佈局看起來像這樣。我們正在定義一個變量,其值是EventPropertiesLayoutRenderer的值,它是我們存儲在LogEventInfo.Properties [「fakecallsite」]中的假呼叫站點。

<variable name="fakecallsite" value="${event-properties:fakecallsite}"/> 
    <variable name="f1layout" value="${longdate} | ${level} | ${logger} | ${fakecallsite} | ${message}"/> 
    <variable name="f2layout" value="${longdate} | ${level} | ${logger} | ${callsite} | ${message}"/> 
    <targets> 
    <target name="f1" xsi:type="File" layout="${f1layout}" fileName="${basedir}/${shortdate}.log" /> 
    <target name="f2" xsi:type="File" layout="${f2layout}" fileName="${basedir}/${shortdate}.log"  /> 
    </targets> 

請注意,我沒有試過,但我認爲它應該工作(或者應該是足夠接近,你可以得到它的工作)。一個限制是,由於我們正在計算假呼叫站點,因此我們不能使用真實呼叫站點LayoutRenderer來處理輸出中的fakecallsite字段的內容。如果這一點很重要,可以通過單獨存儲類和方法(在LogEventInfo.Properties中),然後在NLog.config中設置「fakecallsite」變量來包含類和/或方法來模擬。

END UPDATE

你的包裝應該使用Log方法。另外,您傳遞給NLog Logger.Log方法的類型應該是NLog Logger包裝器的類型,而不是服務實例類型的類型。您仍然可以使用您的服務實例的類型來檢索正確的Logger實例。它應該看起來像這樣:

public class NLogLogger : IParameterInspector 
{ 
    private void Log(Type instanceType, string operationName, string msg) 
    { 
     NLog.Logger logger = LogManager.GetLogger(
      instanceType.FullName, instanceType); 

     //This is the key to preserving the call site in a wrapper. Create a LogEventInfo 
     //then use NLog's Logger.Log method to log the message, passing the type of your 
     //wrapper as the first argument. 

     LogEventInfo le = new LogEventInfo(LogLevel.Info, logger.Name, msg); 
     logger.Log(typeof(NLogLogger), le); 
    } 

    public object BeforeCall(string operationName, object[] inputs) 
    { 
     // Retrieve the service instance type for the logger then log the call. 
     OperationContext operationContext = OperationContext.Current; 
     Type instanceType = operationContext.InstanceContext 
      .GetServiceInstance().GetType(); 
     Log(instanceType, operationName, "BeforeCall"); 

     return instanceType; 
    } 

    public void AfterCall(
     string operationName, object[] outputs, 
     object returnValue, object correlationState 
    ) 
    { 
     if (correlationState is Type) 
      Log(correlationState, operationName, "AfterCall"); 
    } 
}