我正在编写类似于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在堆栈跟踪中找不到调用点,也无法打开堆栈跟踪。
如何伪造呼叫网站?或者如何显示记录行为的“正确”调用点?
答案 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");
}
}