7

我正在使用 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?

4

1 回答 1

24

从对象的属性中{event-context} LayoutRenderer写入一个值。LogEventInfoProperties

属性是一个字典,您可以在其中存储您希望 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, etc 方法并使用附加属性装饰每条日志消息,您仍然可以这样做。

假设您有两种方法(如我上面描述的)来应用两种不同的策略: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, 等方法来记录您的消息,并使用GlobalDiagnosticsContextMappedDiagnosticsContext指定您希望包含在每条日志消息中的附加信息。

一般来说,我认为我还建议您使用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 名称值对放入字典中,并在作用域退出时将其删除。usingusing

于 2012-11-27T18:49:14.240 回答