使用wrapper时Nlog Callsite错误

问题描述 投票:0回答:4

我使用 NLog 进行日志记录,我使用包装器来调用日志方法,我的问题是:如果我尝试打印有关调用站点的信息(

${callsite}
),它会打印包装器方法而不是导致的原始方法要记录的记录器。

有什么方法可以获取调用包装方法的原始方法吗?

c# logging nlog
4个回答
21
投票

请参阅我对这个问题的回答:

匹配特定 NLog 记录器名称的问题

我已从此处复制了该答案中的示例代码(用于缩写的 NLog 包装器),以节省一些麻烦:

  class NLogLogger : ILogger
  {
    private NLog.Logger logger;

    //The Type that is passed in is ultimately the type of the current object that
    //Ninject is creating.  In the case of my example, it is Class1 and Class1 is
    //dependent on ILogger.
    public NLogLogger(Type t)
    {
      logger = NLog.LogManager.GetLogger(t.FullName);
    }

    //Trace, Warn, Error, Fatal eliminated for brevity

    public bool IsInfoEnabled
    {
      get { return logger.IsInfoEnabled; }
    }

    public bool IsDebugEnabled
    {
      get { return logger.IsDebugEnabled; }
    }

    public void Info(string format, params object [] args)
    {
      if (logger.IsInfoEnabled)
      {
        Write(LogLevel.Info, format, args);
      }
    }

    public void Debug(string format, params object [] args)
    {
      if (logger.IsDebugEnabled)
      {
        Write(LogLevel.Debug, format, args);
      }
    }

    private void Write(LogLevel level, string format, params object [] args)
    {
      LogEventInfo le = new LogEventInfo(level, logger.Name, null, format, args);
      logger.Log(typeof(NLogLogger), le);
    }
  }

请注意,这个答案是在 NInject 的上下文中给出的。即使您不使用 NInject,同样的原则也适用于包装 NLog。关键是与 NLog 沟通你的包装器的类型。

这是如何正确编写 NLog 包装器的示例(即维护调用站点信息)。关键在于Write方法。注意它如何使用 NLog 的 Log 方法。另请注意,它将包装类的类型作为第一个参数传递。 NLog 使用类型信息在调用堆栈中向上导航。一旦它看到 DeclaringType 是传入类型(即包装器的类型)的方法,它就知道堆栈上的下一帧是调用方法。

另请参阅此链接(到 NLog 的源存储库)以获取另外两个“扩展”Logger 的示例。一种是包装,一种是继承:

https://github.com/jkowalski/NLog/tree/master/examples/ExtendingLoggers

我不是100%确定,但我认为你不能简单地包装NLog并将Info、Debug、Warn等方法委托给NLog,如下所示:

class MyNLogWrapper
{
  private readonly Logger logger = LogManager.GetCurrentClassLogger();

  public void Info(string msg)
  {
    logger.Info(msg);
  }
}

你需要一种方法来告诉 NLog 你的包装器的类型,我认为你只能通过 Logger.Log 方法(重载)调用 NLog 来做到这一点。

如果这还不够有用,请发布你的包装以获得更多帮助。


0
投票

您还可以将其添加到 NLogLogger 类并在 Write 方法的第一行中调用它。

    protected void GetCurrentClassLogger()
    {
        //This should take you back to the previous frame and context of the Log call
        StackTrace trace = new StackTrace();

        if (trace.FrameCount > 1)
        {
            logger = LogManager.GetLogger(trace.GetFrame(1).GetMethod().ReflectedType.FullName);
        }
        else //This would go back to the stated problem
        {
            logger = LogManager.GetCurrentClassLogger();
        }
    }


    private void Write(LogLevel level, string format, params object[] args)
    {
        //added code
        GetCurrentClassLogger();

        LogEventInfo le = new LogEventInfo(level, logger.Name, null, format, args);
        logger.Log(typeof(NLogLogger), le);
    }

0
投票

伙计们 经过几天的努力和搜索,最后,我只使用一个简单的类构建了 Nlog Wrapper,它可以保留 ${callsite} 并在创建 Nlog Wrapper 实例时获取正确的记录器名称。我将把代码如下,并附上简单的注释。如您所见,我使用 Stacktrace 来获取正确的记录器名称。使用 write 和 writewithex 注册 logevnet,以便保留调用点。如果您有任何疑问,请告诉我。

 public  class NlogWrapper 
    {  
        private  readonly NLog.Logger _logger; //NLog logger

    /// <summary>
    /// This is the construtor, which get the correct logger name when instance created  
    /// </summary>

    public NlogWrapper()
        {
        StackTrace trace = new StackTrace();

        if (trace.FrameCount > 1)
        {
            _logger = LogManager.GetLogger(trace.GetFrame(1).GetMethod().ReflectedType.FullName);
        }
        else //This would go back to the stated problem
        {
            _logger = LogManager.GetCurrentClassLogger();
        }
    }
    /// <summary>
    /// These two method are used to retain the ${callsite} for all the Nlog method  
    /// </summary>
    /// <param name="level">LogLevel.</param>
    ///  <param name="format">Passed message.</param>
    ///  <param name="ex">Exception.</param>
    private void Write(LogLevel level, string format, params object[] args)
    {
        LogEventInfo le = new LogEventInfo(level, _logger.Name, null, format, args);
        _logger.Log(typeof(NlogWrapper), le);
    }
    private void WriteWithEx(LogLevel level, string format,Exception ex, params object[] args)
    {
        LogEventInfo le = new LogEventInfo(level, _logger.Name, null, format, args);
        le.Exception = ex;
        _logger.Log(typeof(NlogWrapper), le);
    }


    #region  Methods
    /// <summary>
    /// This method writes the Debug information to trace file
    /// </summary>
    /// <param name="message">The message.</param>
    public  void Debug(String message)
        {
            if (!_logger.IsDebugEnabled) return;

        Write(LogLevel.Debug, message);
    }  

    public  void Debug(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Debug, message, exception);
    }

    /// <summary>
    /// This method writes the Information to trace file
    /// </summary>
    /// <param name="message">The message.</param>
    public  void Info(String message)
        {
            if (!_logger.IsInfoEnabled) return;
        Write(LogLevel.Info, message);
    }

    public  void Info(string message, Exception exception, params object[] args) 
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Info, message, exception);
    }
    /// <summary>
    /// This method writes the Warning information to trace file
    /// </summary>
    /// <param name="message">The message.</param>
    public  void Warn(String message)
        {
            if (!_logger.IsWarnEnabled) return;
          Write(LogLevel.Warn, message); 
        }

    public  void Warn(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Warn, message, exception);
    }

    /// <summary>
    /// This method writes the Error Information to trace file
    /// </summary>
    /// <param name="error">The error.</param>
    /// <param name="exception">The exception.</param>
    //   public static void Error( string message)
    //  {
    //    if (!_logger.IsErrorEnabled) return;
    //  _logger.Error(message);
    //}

    public  void Error(String message) 
    {
        if (!_logger.IsWarnEnabled) return;
        //_logger.Warn(message);
        Write(LogLevel.Error, message);
    }
    public void Error(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Error, message, exception);
    }  


    /// <summary>
    /// This method writes the Fatal exception information to trace target
    /// </summary>
    /// <param name="message">The message.</param>
    public void Fatal(String message)
        {
            if (!_logger.IsFatalEnabled) return;
         Write(LogLevel.Fatal, message);
    }

    public void Fatal(string message, Exception exception, params object[] args)
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Fatal, message, exception);
    }

    /// <summary>
    /// This method writes the trace information to trace target
    /// </summary>
    /// <param name="message">The message.</param>
    /// 
    public  void Trace(string message, Exception exception, params object[] args)  
    {
        if (!_logger.IsFatalEnabled) return;
        WriteWithEx(LogLevel.Trace, message, exception);
    }
    public  void Trace(String message)
        {
            if (!_logger.IsTraceEnabled) return;
            Write(LogLevel.Trace, message);
    }

        #endregion

    }

0
投票

使用现代 NLog,对于通过包装器方法(甚至是扩展方法)调用的任何记录器,这可以完全在配置中实现,使用调用站点布局渲染器的 SkipFrame 参数。

这是一个扩展方法的示例,用于在需要时包含自定义属性,该方法在日志中保留正确的调用站点信息:

public static class NLogExtensions
{
    public static void Info( this Logger logger, int propertyValue, [StructuredMessageTemplate] string messageFormatString, params object[] messageParameters )
    {
        logger.WithProperty("MyCustomProperty", propertyValue).Info(messageFormatString,messageParameters);
    }
}

在您的 nlog 配置 JSON 或 appsettings.json 的“NLog”部分中:

{
    /* ... */
    "targets": {
        "myTarget": {
        /* ... */
        "layout": "${whateverOtherStuffYouNeed}|${event-properties:MyCustomProperty:whenEmpty='-'}|${callsite:skipFrames=1}"
    }
/* ... */
}

仅此而已。神奇之处在于skipFrames=1,它告诉它忽略堆栈的第一帧,这是其中一些答案手动执行的操作,但 NLog 本身就能够执行此操作。

如果您的包装器比实际调用点的间接层深超过 1 层,只需调整skipFrames 参数以匹配它的深度。

上面的示例允许您调用普通的 NLog 方法,该方法将表现正常并为自定义属性输出

-
,或者在消息前使用 int 参数调用 Info,这将导致在记录之前添加自定义属性该行,并将在日志输出中包含其实际值,使您只需调用另一个重载即可无缝使用您的扩展。

你可以按照你喜欢的方式包装它 - 这只是我在网上看到的一个常见用例的一个例子,而且我自己也有过。

© www.soinside.com 2019 - 2024. All rights reserved.