【问题标题】:How to retain callsite information when wrapping NLog包装 NLog 时如何保留调用点信息
【发布时间】:2011-11-16 18:00:03
【问题描述】:

我有一个包装 NLog 的类(称为 NLogger)。我的日志被保存到我的数据库中。 我遇到的问题是如何显示日志记录发生的位置。 我有这个

<parameter name="@Logger" layout="${callsite}"/>  

但这只是显示 Core.Logging.Loggers.NLogLogger.Log 这是我的 NlogWrapper 而不是调用我的包装器的类。

这是我的包装方法

        public void Log(LogType messageType, Type context, string message, Exception exception)
        {
            NLog.Logger logger = NLog.LogManager.GetLogger(context.Name);
            LogLevel logLevel = LogLevel.Info; // Default level to info

            switch (messageType)
            {
                case LogType.Debug:
                    logLevel = LogLevel.Debug;
                    break;
                case LogType.Info:
                    logLevel = LogLevel.Info;
                    break;
                case LogType.Warning:
                    logLevel = LogLevel.Warn;
                    break;
                case LogType.Error:
                    logLevel = LogLevel.Error;
                    break;
                case LogType.Fatal:
                    logLevel = LogLevel.Fatal;
                    break;
                default:
                    throw new ArgumentException("Log message type is not supported");                    
            }

            logger.Log(logLevel, message, exception);
        }

【问题讨论】:

    标签: c# logging nlog


    【解决方案1】:

    现在修复呼叫站点的一种更简单的方法是使用LogManager.AddHiddenAssembly(Assembly)

    例如

    LogManager.AddHiddenAssembly(yourAssembly);
    

    这将修复调用站点,无需进行手动堆栈遍历等。

    【讨论】:

    • 这是否涵盖了整个程序集,或者如果从 Func/delegate 中调用 Trace() 也会有帮助吗?
    • 它过滤来自该程序集的堆栈帧。
    【解决方案2】:

    有一种简单的方法可以实现这一点。只需将这些属性添加到您的日志包装方法签名中:

    void Log(LogSeverity severity, string message, [CallerFilePath] string fileName = null, [CallerMemberName] string member = null, [CallerLineNumber] int? lineNumber = null);
    

    并将这些传递给包装好的 NLog 方法。

    有关 .NET 中 System.Runtime.CompilerServices 属性的更多信息,请参阅 https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.callerfilepathattribute?view=netframework-4.7.2

    【讨论】:

      【解决方案3】:

      要跳过几帧并深入到包装调用者上下文中,请在 App.config 或程序中设置著名的修饰符:

      skipFrames=1

      示例: 见this page${callsite:skipFrames=Integer}this page${callsite-linenumber:skipFrames=Integer}

      我建议您在包装器中使用这种格式:

      ${callsite:fileName=true:includeSourcePath=false:skipFrames=1}
      

      此设置的输出如下:

      ... {LicenseServer.LSCore.MainThreadFunction(LSCore.cs:220)} ...

      【讨论】:

      • 不确定哪个是 NLog 上支持 skipFrames=Integer 功能的最低版本。必须检查他们的文档。
      • 查看此链接以获取布局渲染器列表:github.com/nlog/nlog/wiki/Layout-Renderers
      • 赢家鸡肉晚餐,这对我来说非常适合。
      • 截至今天,这是泛型类唯一可行的解​​决方案。我试过接受的答案无济于事,但这就像一个魅力。
      【解决方案4】:

      伙计们 经过几天的努力和搜索。最后,我只使用一个简单的类构建了 Nlog Wrapper,它可以保留 ${callsite} 并在创建 Nlog Wrapper 实例时获取正确的记录器名称。我将把代码放在后面加上简单的注释。如您所见,我使用 Stacktrace 来获取正确的记录器名称。使用write和writewithex注册logevnet,这样就可以保留callsite了。

        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
      
          }
      

      【讨论】:

      • 当你在你的项目中使用它时。只需这样做: NlogWrapper mylog= new NlogWrapper( ); mylog.Error("这是关于:", ex).然后包装类将处理记录器名称和调用站点信息。当您使用 ${logger} ${callsite}。您将获得正确的信息,而不是 logwrapper 类调用站点的信息
      【解决方案5】:

      或者,您可以从 NLog 设置中避免本机解决方案,并检索文件 |方法 |包装代码中的行信息:

      using System.Diagnostics;
      ...
      static private string GetCallsite()
      {
        StackFrame sf = new StackTrace(2/*Skip two frames - dive to the callers context*/, true/*Yes I want the file info !*/).GetFrame(0);
        return "{" + sf.GetFileName() + " | " + sf.GetMethod().Name + "-" + sf.GetFileLineNumber() + "} ";
      }
      

      然后你只需调用你的静态方法并在消息之前添加调用站点:

      LogManager.GetCurrentClassLogger().Trace(GetCallsite() + "My Trace Message.");
      

      【讨论】:

      • 注意:如果您使用 GetCallsite() 方法,则需要跳过 2 帧,因为这将比包装器本身中的调用方法高一个堆栈级别。然后注意不要从较低级别调用它。
      • 此代码无法很好地扩展。从性能方面来看,使用 StackFrame 的成本很高
      【解决方案6】:

      问题是您的包装器没有正确包装。这是一个如何正确包装 NLog 的示例,直接取自source tree of NLog

      using System;
      using System.Text;
      using NLog;
      
      namespace LoggerWrapper
      {    
        /// <summary>    
        /// Provides methods to write messages with event IDs - useful for the Event Log target.    
        /// Wraps a Logger instance.    
        /// </summary>    
        class MyLogger    
        {        
          private Logger _logger;        
      
          public MyLogger(string name)        
          {            
            _logger = LogManager.GetLogger(name);        
          }        
      
          public void WriteMessage(string eventID, string message)           
          {            
            ///            
            /// create log event from the passed message            
            ///             
            LogEventInfo logEvent = new LogEventInfo(LogLevel.Info, _logger.Name, message);
      
      
            //
            // set event-specific context parameter            
            // this context parameter can be retrieved using ${event-context:EventID}            
            //            
            logEvent.Context["EventID"] = eventID;            
            //             
            // Call the Log() method. It is important to pass typeof(MyLogger) as the            
            // first parameter. If you don't, ${callsite} and other callstack-related             
            // layout renderers will not work properly.            
            //            
            _logger.Log(typeof(MyLogger), logEvent);        
          }    
        }
      }
      

      关键是将记录器包装器的类型传递给对 Log 的调用。当 NLog 试图找到调用点时,它会向上堆栈,直到第一个调用方法的声明类型不是传递给 Log 调用的类型。这将是实际调用包装器的代码。

      在您的情况下,您的记录器看起来像这样:

          public void Log(LogType messageType, Type context, string message, Exception exception)
          {
              NLog.Logger logger = NLog.LogManager.GetLogger(context.Name);
              LogLevel logLevel = LogLevel.Info; // Default level to info
      
              switch (messageType)
              {
                  case LogType.Debug:
                      logLevel = LogLevel.Debug;
                      break;
                  case LogType.Info:
                      logLevel = LogLevel.Info;
                      break;
                  case LogType.Warning:
                      logLevel = LogLevel.Warn;
                      break;
                  case LogType.Error:
                      logLevel = LogLevel.Error;
                      break;
                  case LogType.Fatal:
                      logLevel = LogLevel.Fatal;
                      break;
                  default:
                      throw new ArgumentException("Log message type is not supported");                    
              }
      
              //
              // Build LogEvent here...
              //
              LogEventInfo logEvent = new LogEventInfo(logLevel, context.Name, message);
              logEvent.Exception = exception;
      
              //
              // Pass the type of your wrapper class here...
              //
              logger.Log(typeof(YourWrapperClass), logEvent);
          }
      

      【讨论】:

      • 我已经根据 github.com/NLog/NLog/tree/master/examples/ExtendingLoggers 的 LoggerWrapper 示例扩展了 NLog,以便我可以填充自定义事件属性,例如 logEvent.Properties["EventID"] = eventID;,如示例中所示。我想将调用站点添加为属性之一 - 即logEvent.Properties["callsite"] = *caller_method_name*。如何以编程方式获取呼叫站点信息?
      【解决方案7】:

      我一直在努力解决这个问题。

      真正重要的是日志文件中的调用站点(FullyQualified Namespace)。

      首先,我尝试从 Stacktrace 中获取正确的记录器:

          [MethodImpl(MethodImplOptions.NoInlining)]
          private static NLog.Logger GetLogger()
          {
              var stackTrace = new StackTrace(false);
              StackFrame[] frames = stackTrace.GetFrames();
              if (null == frames) throw new ArgumentException("Stack frame array is null.");
              StackFrame stackFrame;
              switch (frames.Length)
              {
                  case 0:
                      throw new ArgumentException("Length of stack frames is 0.");
                  case 1:
                  case 2:
                      stackFrame = frames[frames.Length - 1];
                      break;
                  default:
                      stackFrame = stackTrace.GetFrame(2);
                      break;
              }
      
              Type declaringType = stackFrame.GetMethod()
                                             .DeclaringType;
      
              return declaringType == null ? LogManager.GetCurrentClassLogger() :                 LogManager.GetLogger(declaringType.FullName);
          }
      

      但遗憾的是,带有 MEF 的 Stacktrace 非常长,我无法清楚地识别 ILogger 请求者的正确调用者。

      所以,我没有通过构造函数注入注入 ILogger 接口,而是创建了一个 ILogFactory 接口,它可以通过构造函数注入注入,然后调用工厂上的创建方法

          public interface ILogFactory
          {
              #region Public Methods and Operators
      
              /// <summary>
              ///     Creates a logger with the Callsite of the given Type
              /// </summary>
              /// <example>
              ///     factory.Create(GetType());
              /// </example>
              /// <param name="type">The type.</param>
              /// <returns></returns>
              ILogger Create(Type type);
      
              #endregion
          }
      

      并实现它:

          using System;
          using System.ComponentModel.Composition;
      
          [Export(typeof(ILogFactory))]
          [PartCreationPolicy(CreationPolicy.Shared)]
          public class LogFactory : ILogFactory
          {
              #region Public Methods and Operators
      
              public ILogger Create(Type type)
              {
                  var logger = new Logger().CreateLogger(type);
                  return logger;
              }
      
              #endregion
          }
      

      使用 ILogger:

          public interface ILogger
          {
              #region Public Properties
      
              bool IsDebugEnabled { get; }
      
              bool IsErrorEnabled { get; }
      
              bool IsFatalEnabled { get; }
      
              bool IsInfoEnabled { get; }
      
              bool IsTraceEnabled { get; }
      
              bool IsWarnEnabled { get; }
      
              #endregion
      
              #region Public Methods and Operators
      
              void Debug(Exception exception);
              void Debug(string format, params object[] args);
              void Debug(Exception exception, string format, params object[] args);
              void Error(Exception exception);
              void Error(string format, params object[] args);
              void Error(Exception exception, string format, params object[] args);
              void Fatal(Exception exception);
              void Fatal(string format, params object[] args);
              void Fatal(Exception exception, string format, params object[] args);
              void Info(Exception exception);
              void Info(string format, params object[] args);
              void Info(Exception exception, string format, params object[] args);
              void Trace(Exception exception);
              void Trace(string format, params object[] args);
              void Trace(Exception exception, string format, params object[] args);
              void Warn(Exception exception);
              void Warn(string format, params object[] args);
              void Warn(Exception exception, string format, params object[] args);
      
              #endregion
          }
      

      和实现:

          using System;
      
            using NLog;
            using NLog.Config;
      
            /// <summary>
            ///     The logging service.
            /// </summary>
            public class Logger : NLog.Logger, ILogger
            {
                #region Fields
      
                private string _loggerName;
      
                #endregion
      
                #region Public Methods and Operators
      
                /// <summary>
                ///     The get logging service.
                /// </summary>
                /// <returns>
                ///     The <see cref="ILogger" />.
                /// </returns>
                public ILogger CreateLogger(Type type)
                {
                    if (type == null) throw new ArgumentNullException("type");               
      
                    _loggerName = type.FullName;
      
                    var logger = (ILogger)LogManager.GetLogger(_loggerName, typeof(Logger));
      
                    return logger;
                }
      

      要使用它...只需注入 ILogFactory 并在 Mefed 导入构造函数中调用 Create 方法:

            [ImportingConstructor]
            public MyConstructor(          
              ILogFactory logFactory)
             {
              _logger = logFactory.Create(GetType());
              }
      

      希望对你有帮助

      【讨论】:

        【解决方案8】:
        internal string GetCallingMethodName()
        {
          string result = "unknown";
          StackTrace trace = new StackTrace(false);
          for (int i = 0; i < trace.FrameCount; i++)
          {
            StackFrame frame = trace.GetFrame(i);
            MethodBase method = frame.GetMethod();
            Type dt = method.DeclaringType;
            if (!typeof(ILogger).IsAssignableFrom(dt) && method.DeclaringType.Namespace != "DiagnosticsLibrary")
            {
              result = string.Concat(method.DeclaringType.FullName, ".", method.Name);
              break;
            }
          }
          return result;
        }
        

        来源:http://slf.codeplex.com/discussions/210075

        我使用上面发布的代码来简单地提取调用方法名称并将其作为“消息”参数的一部分传递给布局。这让我可以将调用日志包装器的原始方法名称写入日志文件(而不是日志包装器的类名)。

        【讨论】:

        • 此代码无法很好地扩展。从性能方面来看,使用 StackFrame 的成本很高。
        猜你喜欢
        • 2012-09-14
        • 1970-01-01
        • 2018-05-20
        • 1970-01-01
        • 1970-01-01
        • 2016-05-14
        • 2018-11-02
        • 2015-04-29
        • 1970-01-01
        相关资源
        最近更新 更多