用于WCF日志记录行为的错误NLog呼叫站点

时间:2013-07-11 11:51:58

标签: wcf behavior nlog

我正在编写类似于this blog by Pieter de Rycke的日志记录行为,但是对于NLog。我想出了这段代码:

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为日志包装器或外观提供了一个特殊的callsite处理,如StackOverflow answer所述:将调用点的类传递给日志记录方法。

事实上,我在logger.Info(msg, instanceType)方法中使用上面的Log()执行了此操作。但是这不起作用,因为当行为的BeforeCall()方法正在运行时,调用点尚未进入堆栈跟踪。 WCF甚至还没有开始运行该操作。 NLog在堆栈跟踪中找不到调用点,也无法打开堆栈跟踪。

如何伪造呼叫网站?或者如何显示记录行为的“正确”调用点?

1 个答案:

答案 0 :(得分:0)

更新:

感谢您的澄清,我更了解您的目标。您希望从IParameterInspector实现记录的消息反映“Example.MyService.GetContacts”的调用站点,其中Example.MyService是您的服务(由instanceType参数指示),“GetContacts”是操作。您可以手动合成呼叫站点信息。你仍然会使用NLog的Logger.Log方法,你仍然会创建一个LogEventInfo对象。此外,您可以在LogEventInfo.Properties对象中存储“class”和“method”。而不是基于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");
    }
}