【问题标题】:Logging ClassName and MethodName using log4net for a .NET project使用 log4net 为 .NET 项目记录 ClassName 和 MethodName
【发布时间】:2010-09-22 15:30:11
【问题描述】:

我一直在寻找一种方法来记录类名和方法名作为我的日志基础设施的一部分。显然,我想让它在运行时易于使用和快速。我已经阅读了大量有关记录类名和方法名的内容,但我遇到了 2 个主题。

  1. log4net 使用内部抛出异常来生成堆栈帧,如果您通常将其用于所有日志记录,则会变得昂贵。
  2. 混乱。那里有很多文学作品。我已经尝试了很多,但没有得到有用的东西。

如果你逗我一会儿,我想重新设置。

我在我的项目中创建了一个这样的类

public static class Log {
    private static Dictionary<Type, ILog> _loggers = new Dictionary<Type, ILog>();
    private static bool _logInitialized = false;
    private static object _lock = new object();

    public static string SerializeException(Exception e) {
        return SerializeException(e, string.Empty);
    }

    private static string SerializeException(Exception e, string exceptionMessage) {
        if (e == null) return string.Empty;

        exceptionMessage = string.Format(
            "{0}{1}{2}\n{3}",
            exceptionMessage,
            (exceptionMessage == string.Empty) ? string.Empty : "\n\n",
            e.Message,
            e.StackTrace);

        if (e.InnerException != null)
            exceptionMessage = SerializeException(e.InnerException, exceptionMessage);

        return exceptionMessage;
    }

    private static ILog getLogger(Type source) {
        lock (_lock) {
            if (_loggers.ContainsKey(source)) {
                return _loggers[source];
            }

            ILog logger = log4net.LogManager.GetLogger(source);
            _loggers.Add(source, logger);
            return logger;
        }
    }

    public static void Debug(object source, object message) {
        Debug(source.GetType(), message);
    }

    public static void Debug(Type source, object message) {
        getLogger(source).Debug(message);
    }

    public static void Info(object source, object message) {
        Info(source.GetType(), message);
    }

    public static void Info(Type source, object message) {
        getLogger(source).Info(message);
    }

...

    private static void initialize() {
        XmlConfigurator.Configure(); 
    }

    public static void EnsureInitialized() {
        if (!_logInitialized) {
            initialize();
            _logInitialized = true;
        }
    }
}

(如果这段代码看起来很熟悉,那是因为它是从示例中借来的!)

无论如何,在我的整个项目中,我都使用这样的行来记录:

        Log.Info(typeof(Program).Name, "System Start");

嗯,这样的作品。最重要的是,我得到了类名但没有方法名。更重要的是,我正在用这种“typeof”垃圾污染我的代码。如果我在文件之间复制并粘贴一段代码,等等,日志框架就会撒谎!

我尝试使用 PatternLayout (%C{1}.{M}) 但这不起作用(它所做的只是将“Log.Info”写入日志——因为一切都通过 Log. X 静态方法!)。此外,这应该很慢。

那么,考虑到我的设置和我希望简单快速的愿望,最好的方法是什么?

提前感谢任何帮助。

【问题讨论】:

    标签: c# logging log4net


    【解决方案1】:

    log4net(和 NLog)都公开了一种日志记录方法,可以“包装”他们的记录器并仍然获得正确的调用站点信息。本质上,log4net(或 NLog)记录器需要被告知形成记录代码和应用程​​序代码之间“边界”的类型。我认为他们称之为“记录器类型”或类似的东西。当库获得调用站点信息时,它们会向上导航调用堆栈,直到 MethodBase.DeclaringType 等于(或者可能是 AssignableFrom)“记录器类型”。下一个堆栈帧将是应用程序调用代码。

    这是一个如何从包装器内通过 NLog 记录的示例(log4net 类似 - 查看 log4net 文档中的 ILogger(不是 ILog)接口:

      LogEventInfo logEvent = new LogEventInfo(level, _logger.Name, null, "{0}", new object[] { message }, exception);
    
      _logger.Log(declaringType, logEvent);
    

    其中 declaringType 是一个成员变量,设置如下:

      private readonly static Type declaringType = typeof(AbstractLogger);
    

    “AbstractLogger”是您的记录器包装器的类型。在你的情况下,它可能看起来像这样:

      private readonly static Type declaringType = typeof(Log);
    

    如果 NLog 需要获取调用站点信息(因为布局中的调用站点运算符),它将向上导航堆栈,直到当前帧的 MethodBase.DeclaringType 等于(或 AssignableFrom)声明类型。堆栈中的下一帧将是实际的调用站点。

    这里有一些代码可用于使用“包装”的 log4net 记录器进行记录。它使用 log4net ILogger 接口并传递“包装”记录器的类型以保存呼叫站点信息。您不必使用此方法填写事件类/结构:

      _logger.Log(declaringType, level, message, exception);
    

    同样,“declaringType”是您的包装器的类型。 _logger 为 log4net 记录器,Level 为 log4net.LogLevel 值,message 为消息,exception 为异常(如果有,则为 null)。

    就使用 Typeof(whatever) 污染您的呼叫站点而言,如果您想使用单个静态“Log”对象,我认为您会遇到这种情况。或者,在“日志”对象的日志记录方法中,您可以获得调用方法,如本文中接受的答案

    How can I find the method that called the current method?

    该链接显示了如何获取前一个调用者。如果您需要获取调用日志记录函数的方法,但您的工作需要更深几层,您将需要在堆栈上增加一些帧数,而不仅仅是一帧。

    综合所有这些,您会编写类似这样的 Debug 方法(同样,这是在 NLog 方面,因为这是我面前的内容):

    public static void Debug(object message)
    {
      MethodBase mb = GetCallingMethod();
      Type t = mb.DeclaringType;
      LogEventInfo logEvent = new LogEventInfo(LogLevel.Debug, t.Name, null, "{0}", new object [] message, null);
      ILogger logger = getLogger(t) As ILogger;
      logger.Log(declaringType, logEvent)
    }
    

    请注意,您可能不会在 StackOverflow 上找到很多人会建议编写这样的日志记录包装器函数(显式获取日志调用的调用方法)。我也不能说我会推荐它,但它或多或少地回答了您提出的问题。如果要使用静态“日志”对象,则必须在每个日志调用站点显式传递类型(以获取正确的类记录器),或者必须在日志调用中添加代码以导航自己堆叠并找出这些信息。我认为这两种选择都不是特别有吸引力。

    现在,说了这么多,您可能会考虑直接使用 log4net 或 NLog,而不是添加这种复杂(且不一定可靠)的代码来获取呼叫站点信息。正如 Matthew 所指出的,NLog 提供了一种简单的方法来获取当前类的记录器。要使用 log4net 获取当前类的记录器,您可以在每个类中执行此操作:

    private static readonly log4net.ILog log = log4net.LogManager.GetLogger( 
            System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); 
    

    与使用 NLog 的这种方式相比:

      private static readonly NLog.logger log = NLog.LogManager.GetCurrentClassLogger();
    

    这是一个很常见的用法。

    如果您不想依赖特定的日志记录实现,可以使用可用的日志记录抽象之一,例如 Common.Logging (NET)Simple Logging Facade (SLF)

    即使您不使用这些抽象之一,也请下载 Common.Logging 的源代码并查看 log4net 的抽象。它将准确展示如何包装 log4net 记录器,以便保留调用站点信息(并且可供布局操作员使用)。

    【讨论】:

      【解决方案2】:

      我更喜欢下面这样的模式,它适用于 Log4Net 和类似的 API:

      class MyClass
      {
          private static readonly ILog logger = LogManager.GetLogger(typeof(MyClass));
      
          void SomeMethod(...)
          {
              logger.Info("some message");
      
              ...
      
              if (logger.IsInfoEnabled)
              {
                  logger.Info(... something that is expensive to generate ...);
              }
          }
      
      }
      

      一些备注:

      • 使用此模式,您只评估一次typeof(MyClass) - 与您在每次日志记录调用时调用 object.GetType() 的示例相比,无论是否启用了相应的日志记录级别。没什么大不了的,但通常希望日志记录的开销最小。

      • 您仍然需要使用 typeof,并确保在使用复制/粘贴时没有得到错误的类名。我更喜欢这样,因为替代方案(例如 Matthew Ferreira 的回复中描述的 NLog 的 LogManager.GetCurrentClassLogger)需要获取具有性能开销的 StackFrame,并且要求调用代码具有 UnmanagedCode 权限。顺便说一句,我认为如果 C# 提供一些编译时语法来引用当前类会很好——比如 C++ _class_ 宏。 p>

      • 出于三个原因,我将放弃任何获取当前方法名称的尝试。 (1) 存在显着的性能开销开销,并且日志记录应该很快。 (2) 内联意味着你可能得不到你认为你得到的方法。 (3) 强加了对UnmanagedCode权限的要求。

      【讨论】:

      • 请注意,根据此链接 (msdn.microsoft.com/en-us/library/…),.NET 中的 StackTrace 确实需要 UnmanagedCode 权限。但是,根据此链接,Silverlight 中的 () StackTrace 没有相同的限制。我的观点是,如果 NLog 与 Silverlight 兼容(正如即将发布的 2.0 版那样),GetCurrentClassLogger 可能在 Silverlight 中工作得很好(如果实施正确)。正如 Joe 指出的那样,在“常规”.NET 或 .NET 客户端配置文件中,您确实可能会遇到 UnmanagedCode 限制。
      • 错过粘贴上面的第二个链接:.NET 中的 StackTrace (msdn.microsoft.com/en-us/library/…) Silverlight 中的 StackTrace (msdn.microsoft.com/en-us/library/…)
      【解决方案3】:

      我也对此进行了一些研究,我相信有效地做到这一点的唯一方法是包装日志功能,尽管我讨厌这样做:

      public static void InfoWithCallerInfo(this ILog logger, 
          object message, Exception e = null, [CallerMemberName] string memberName = "",
          [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
      {
          if (!logger.IsInfoEnabled)
              return;
          if (e == null)
              logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, 
                  memberName, sourceLineNumber, message));
          else
              logger.Info(string.Format("{0}:{1}:{2} {3}", sourceFilePath, 
                  memberName, sourceLineNumber, message), e);
      }
      

      注意事项:

      • 这是我围绕 ILog::Info 编写的包装函数,您还需要一个围绕其他日志记录级别的函数。
      • 这需要Caller Information,它仅在.Net 4.5 开始可用。好处是这些变量在编译时被字符串文字替换,因此非常高效。
      • 为简单起见,我保留了 sourceFilePath 参数原样,您可能更愿意对其进行格式化(修剪大部分/全部路径)

      【讨论】:

      • 这真的很好,我会考虑在官方仓库中提出拉取请求。
      【解决方案4】:

      我知道您已经有了依赖于 log4net 的代码,但是您是否考虑过另一个可以更好地满足您要求的日志框架?我个人将NLog 用于我自己的应用程序。它允许这样的代码:

      class Stuff
      {
          private static readonly Logger logger = LogManager.GetCurrentClassLogger();
      
          // ...
      
          void DoStuff()
          {
              logger.Info("blah blah");
          }
      }
      

      默认情况下,NLog 会将类名和方法名添加到其记录的消息中。它有一个与 log4net 非常相似的 API,包括 XML 和编程配置。这可能值得您花时间。

      【讨论】:

        猜你喜欢
        • 2010-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-23
        相关资源
        最近更新 更多