2013-07-11 65 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"); 
    } 
}