2010-09-22 36 views
9

我一直在寻找一种方法来记录类名和方法名称作为我的日志记录基础结构的一部分。很显然,我希望在运行时使用起来更快,速度更快。我已经做了很多关于日志记录类名和方法名的阅读,但是我已经运行了两个主题。使用log4net为.NET项目记录ClassName和MethodName

  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"); 

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

我试着玩PatternLayout(%C {1}。{M}),但没有奏效(它所做的只是将“Log.Info”写入日志 - 因为一切都是通过Log .X静态方法!)。此外,这应该是缓慢的。

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

提前感谢任何帮助。

回答

1

我知道你已经有了依赖于log4net的代码,但是你有没有想过另一个可能更好地满足你的需求的日志框架?我个人使用NLog为我自己的应用程序。它可以让这样的代码:

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

    // ... 

    void DoStuff() 
    { 
     logger.Info("blah blah"); 
    } 
} 

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

9

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

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

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

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

就使用Typeof(无论什么)污染您的呼叫站点而言,如果您想使用单个静态“Log”对象,我认为您会陷入困境。另外,对“日志”对象的测井方法里,你可以得到在这个岗位

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

该链接显示了如何获得前一个来电者喜欢接受的答案调用方法。如果您需要获取调用日志记录功能的方法,但是您的工作正在进行更深层次的几个层次,则需要将堆栈数量增加到一定数量,而不是仅增加一个框架。

考虑这一切放在一起,你会写你的调试方法是这样的(再次,这是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,而不是增加这个复杂(并且不一定可靠)的代码来获取呼叫站点信息。正如马修指出的那样,NLog提供了一种简单的方法来获取当前课程的记录器。要获得使用log4net的当前类的记录,你会在每类做到这一点:

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

VS这种方式与NLOG:

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

这是一个很常见的用法。

如果你不希望依赖于特定的日志记录实现,你可以使用可用如Common.Logging (NET)Simple Logging Facade (SLF)日志抽象的一个。

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

3

我更喜欢下面的图案,该图案与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++ _ _宏一样,那将会很好。

  • 我会放弃任何试图获取当前方法名称的原因有三个。 (1)存在显着的性能开销开销,并且记录应该是快速的。 (2)内联意味着你可能得不到你认为你得到的方法。 (3)它强制要求UnmanagedCode权限。

+0

需要注意的是,根据这个链接(http://msdn.microsoft .com/en-us/library/system.diagnostics.stacktrace.aspx),.NET中的StackTrace确实需要UnmanagedCode权限。但是,根据此链接,()Silverlight中的StackTrace不具有相同的限制。我的观点是,如果NLog与Silverlight兼容(如即将发布的2.0版本),GetCurrentClassLogger可能在Silverlight中工作得很好(如果正确实施)。正如Joe指出的那样,在“常规”.NET或.NET客户端配置文件中,您确实可以运行UnmanagedCode限制。 – wageoghe 2010-09-23 19:03:45

+0

错过了粘贴上面的第二个链接:.NET中的StackTrace(http://msdn.microsoft.com/zh-cn/library/system.diagnostics.stacktrace.aspx)Silverlight中的StackTrace(http://msdn.microsoft.com /en-us/library/system.diagnostics.stacktrace(VS.95).aspx) – wageoghe 2010-09-23 19:09:53

2

我也做了一些这方面的研究,我相信能够有效地做到这一点的唯一方法就是我很讨厌这样做包的记录功能,尽可能多:

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参数作为-是,你可能会喜欢格式化(修剪大多数/所有的路径)