我正在使用Nlog记录我的Ninjatrader策略。我希望能够将策略ID添加为所有nLog消息的前缀,以便我可以单独过滤与策略中每个帐户相关的条目。
fileTarget.Layout = "${longdate} ${callsite} ${level} ${event-context:item=MyValue} ${message}";`
我目前的布局如上。我尝试使用event-context:item但不知道如何为所有消息打印上下文项。
我尝试如下
Logger log = LogManager.GetCurrentClassLogger();
LogEventInfo theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name);
logger.Log(theEvent);
但是它结束了只在第一行Sim101上打印上下文信息的一行,而不是在其他行上打印。
2012-11-26 15:09:47.9777 NinjaTrader.Strategy.LODHOD.OnStartUp Debug Sim101
2012-11-26 15:09:48.3996 NinjaTrader.Strategy.LODHOD.OnBarUpdate Trace BAR UPDATE
2012-11-26 15:09:49.7902 NinjaTrader.Strategy.LODHOD.EntryOrders Info PLACED ENTRY ORDERS
如何在所有日志行上打印Sim101?
答案 0 :(得分:23)
{event-context}
LayoutRenderer
从LogEventInfo
对象的Properties
属性中写入值。
属性是一个字典,您可以在其中存储您希望NLog添加到每条日志消息的命名值。
如果要使用记录消息时生效的“StrategyId”标记每条日志消息,则应创建类似这样的LogEventInfo对象:
LogEventInfo theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name);
theEvent.Properties["StrategyId"] = "Sim101";
Logger.Log(theEvent);
您的布局如下所示:
fileTarget.Layout = "${longdate} ${callsite} ${level} ${event-context:item=StrategyId} ${message}";
如果您希望您的日志记录呼叫站点不那么详细,可以使用GlobalDiagnosticContext或MappedDiagnosticContext。
private void ApplyStrategyABC()
{
NLog.GlobalDiagnosticContext.Set("StrategyId","ABC");
//Do some stuff
LogEventInfo theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name);
Logger.Log(theEvent);
NLog.GlobalDiagnosticContext.Remove("StrategyId");
}
private void ApplyStrategyDEF()
{
NLog.GlobalDiagnosticContext.Set("StrategyId","DEF");
//Do some stuff
LogEventInfo theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name);
Logger.Log(theEvent);
NLog.GlobalDiagnosticContext.Remove("StrategyId");
}
使用这样的布局:
fileTarget.Layout = "${longdate} ${callsite} ${level} ${gdc:item=StrategyId} ${message}";
将导致每条日志消息都被标记为全局字典中“StrategyId”的当前值。
为了好玩,您还可以使用一种流畅的API扩展方法,将您的属性应用于您创建的LogEventInfo对象。像这样(未经测试):
LogEventInfo WithProperty(this LogEventInfo theEvent, string name, string value)
{
theEvent.Properties[name] = value;
return theEvent;
}
然后您可以像这样使用它:
var theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name).WithProperty("StrategyId", "ABC");
而且:
var theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name).WithProperty("StrategyId", "ABC").WithProperty("SomethingElse", someLocalVariable);
如果您想更明确(并减少可能的拼写错误),您可以制作更具体的扩展方法,如下所示:
LogEventInfo WithStrategy(this LogEventInfo theEvent, string strategy)
{
theEvent.Properties["StrategyId"] = strategy;
return theEvent;
}
LogEventInfo WithCurrency(this LogEventInfo theEvent, string currency)
{
theEvent.Properties["Currency"] = currency;
return theEvent;
}
var theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", this.Account.Name).WithStrategy("ABC").WithCurrency("US dollars");
编辑:
大多数人使用Logger.Info
,Logger.Debug
,Logger.Trace
等方法编写日志消息,而不是创建LogEventInfo
并为每条消息调用Log。如果您明确创建LogEventInfo
对象,可能会有更多灵活性,但这也会使您的工作变得更加复杂。
如果您想使用Logger.Info
,Logger.Debug
等方法并使用其他属性修饰每条日志消息,您仍然可以这样做。
假设您有两种方法(如上所述)应用两种不同的策略:ABC和DEF:
使用这样的布局:
fileTarget.Layout = "${longdate} ${callsite} ${level} ${gdc:item=StrategyId} ${message}";
public class MyClass
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private void ApplyStrategyABC()
{
NLog.GlobalDiagnosticContext.Set("StrategyId","ABC");
//Do some stuff
logger.Debug("Hello from ABC!");
var x = CalculateSomeValue();
logger.Debug("Value = {0}", x);
NLog.GlobalDiagnosticContext.Remove("StrategyId");
}
private void ApplyStrategyDEF()
{
NLog.GlobalDiagnosticContext.Set("StrategyId","DEF");
//Do some stuff
logger.Debug("Hello from DEF");
var x = CalculateSomeValue();
logger.Debug("Value = {0}", x);
NLog.GlobalDiagnosticContext.Remove("StrategyId");
}
}
In you program call your two strategies:
var myClass = new MyClass();
myClass.ApplyStrategyABC();
myClass.ApplyStrategyDEF();
在每种情况下,记录的消息都将使用在相应功能中设置的“StrategyId”进行标记。
如果您想创建和使用LogEventInfo对象来创建消息,那么您必须意识到一个LogEventInfo对象实例的属性仅适用于该实例。如果您创建一个LogEventInfo,设置其属性,记录它,然后使用Logger.Info,Logger.Debug等记录消息,那么您将看不到您在原始LogEventInfo上设置的属性。
例如,
var logger = LogManager.GetCurrentClassLogger();
var theEvent = new LogEventInfo(NLog.LogLevel.Debug, "", "Hello 1");
theEvent.Properties["StrategyId"] = "ABC";
//This message will be tagged with StrategyId = ABC if the layout uses the event-context LayoutRenderer
logger.Log(theEvent);
//This message will NOT be tagged with StrategyId = ABC because that value was only added to the LogEventInfo
//object that was created above. Another way to think about this is that internally
//NLog creates a LogEventInfo for each message that is logged via the Debug, Trace, etc
//methods.
logger.Debug("Hello 2");
我建议您使用Logger.Info
,Logger.Debug
,Logger.Trace
等方法记录您的消息,并使用GlobalDiagnosticsContext
或MappedDiagnosticsContext
来指定附加内容您希望包含在每条日志消息中的信息。
一般来说,我认为我还建议您使用Logger.Info
,Logger.Debug
,Logger.Trace
方法或LogEventInfo
+ Logger.Log
,但不要都。使用两者,特别是如果您尝试添加其他上下文值(StrategyId)可能会变得混乱。
我可以类比安装软件。通常,当您在计算机上安装软件时,您可以选择进行“典型”安装,让安装程序安装要安装的组件,或选择“自定义”,然后选择要安装的组件。我不了解你,但我通常选择“典型”安装。使用Logger.Info
,Logger.Debug
,Logger.Trace
就像“典型”安装一样。这些是最常用的日志记录方法。使用LogEventInfo
+ Logger.Log
更像是选择“自定义”安装。如果您使用LogEventInfo
,则意味着“典型”的日志记录方法无法满足您的需求。
当您使用NLog时,您将更加熟悉其工作原理,其中一些问题对您来说将变得更加明显。
请注意GlobalDiagnosticsContext
是真正的全球性。它是一个静态对象。因此,如果您是多线程,如果两个线程尝试向字典添加具有相同名称的值,则可能会发生冲突。
MappedDiagnosticsContext
是线程本地的(它使用线程静态字典来存储其值),因此在多线程情况下使用它可能更好。
如果您希望获得幻想并自动调整放在GlobalDiagnosticsContext(或MappedDiagnosticsContext)中的值,您可以创建一个这样的类:
public class ScopedGlobalContext : IDisposable
{
private string n;
private string v;
public ScopedGlobalContext(string name, string value)
{
n = name;
v = value;
NLog.GlobalDiagnosticsContext.Set(n, v);
}
public void Dispose()
{
NLog.GlobalDiagnosticsContext.Remove(n);
}
}
你可以像这样使用它:
private void ApplyStrategyDEF()
{
using (new ScopedGlobalContext("StrategyId", "DEF"))
{
//Do some stuff
logger.Debug("Hello from DEF");
var x = CalculateSomeValue();
logger.Debug("Value = {0}", x);
}
}
这将在GlobalDiagnosticsContext
范围开始时将StrategyId,DEF名称值对放在using
字典中,并在using
范围退出时将其删除。