使用log4net为.NET项目记录ClassName和MethodName

我一直在寻找一种方法来记录类名和方法名作为我的日志记录基础结构的一部分。 显然,我希望在运行时使用简单快捷。 我已经做了很多关于记录类名和方法名的阅读,但我遇到了2个主题。

  1. log4net使用内部抛出exception来生成堆栈帧,如果通常用于所有日志记录,则会变得昂贵。
  2. 混乱。 那里有很多文献。 我已经尝试了一堆它并没有得到一些有用的东西。

如果你让我幽默一下,我想重置一下。

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

public static class Log { private static Dictionary _loggers = new Dictionary(); 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"); 

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

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

那么,考虑到我的设置和我想要简单快速的最佳方式,最好的方法是什么?

提前感谢任何帮助。

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

下面是一个如何从包装器中通过NLog进行日志记录的示例(log4net类似 – 在logognet(而不是ILog)接口的log4net文档中查看:

  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)declaringType。 堆栈中的下一帧将是实际的呼叫站点。

下面是一些代码,可用于使用“包装”的log4net记录器进行日志记录。 它使用log4net ILogger接口并传递“包装”记录器的类型以保留呼叫站点信息。 您不必使用此方法填充事件类/结构:

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

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

至于使用Typeof(无论如何)污染您的呼叫站点,我认为如果您想使用单个静态“Log”对象,您将坚持使用它。 或者,在“Log”对象的日志记录方法中,您可以获得此post中的接受答案之类的调用方法

如何找到调用当前方法的方法?

该链接显示了如何获得前一个调用者。 如果您需要获取调用日志记录function的方法,但是您的工作正在深入了解几层,则需要向上堆栈一些帧而不是一帧。

把所有这些放在一起,你会写这样的调试方法(再次,这是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上找不到很多人会建议编写这样的日志包装函数(显式获取永远日志调用的调用方法)。 我不能说我会推荐它,但它或多或少地回答了你提出的问题。 如果要使用静态“Log”对象,则必须在每个日志记录调用站点显式传递Type(以获取正确的类记录器),或者您必须在日志记录调用内添加代码以导航堆叠并为自己找出这些信息。 我不认为这些选择中的任何一个都特别有吸引力。

现在,说了所有这些,你可以考虑直接使用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记录器,以便保留调用站点信息(并且可供布局操作员使用)。

我更喜欢以下模式,它适用于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 _ macro那样会很好。

  • 出于三个原因,我会放弃任何获取当前方法名称的尝试。 (1)存在显着的性能开销,并且记录应该很快。 (2)内联意味着您可能无法获得您认为自己获得的方法。 (3)它强制要求UnmanagedCode权限。

我也对此做了一些研究,我认为有效地做到这一点的唯一方法是包装日志记录function,就像我讨厌这样做:

 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周围编写的包装函数,你需要一个围绕其他日志记录级别。
  • 这需要来电信息 ,只能从.Net 4.5开始使用。 另一方面,这些变量在编译时被字符串文字替换,因此它们都非常有效。
  • 为简单起见,我按原样保留了sourceFilePath参数,你可能更喜欢格式化它(修剪它的大部分/全部路径)

我知道你已经有了依赖log4net的代码,但是你有没有考虑过另一个可能更符合你需求的日志框架? 我个人使用NLog作为我自己的应用程序。 它允许这样的代码:

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

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