2010-09-22 44 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參數作爲-是,你可能會喜歡格式化(修剪大多數/所有的路徑)