原文地址
最近公司有個項目,需要解析三種二進制流,我寫其中的兩種,最后用一個 Windos 服務來實時處理。之前的項目都只是簡單記下,主要是數據庫層日志,但針對這個項目,貌似需要點復雜的日志系統,在軟件各個層次上都需要日志,否則,出毛病的話,還真看不出來問題在哪,而且調試 Windows 服務也很麻煩~于是,用了 log4net。
本文只是概述,接下來較具體說明 log4net。
log4net 框架是基于 Apache log4j?,關于 log4j 的更多信息查看 http://logging.apache.org/log4j/。
本文介紹 log4net API,其獨特的功能和設計原理。log4net 是一個基于很多作者的工作的開源項目。log4net 允許開發人員用任意粒度控制日志語句的輸出。log4net 使用外部配置文件在運行時完全可配置。
幾乎每個大型應用程序都包含它自己的日志,或追蹤 API。向代碼中插入日志語句對于調試程序是一個很低級的方法。這也是唯一一個方法,因為調試器不會總是可靠或可用的。通常是在大規模多線程應用程序和分布式應用程序的情況。
應用程序一旦被部署,使用開發或調試工具將不是不可能的。一個管理員可以使用有效的日志系統來診斷和修復很多配置問題。
經驗說明,日志是一個開發周期中很重要的組件。它提供了很多優勢。如提供關于應用程序執行的準確的上下文環境(context )。日志一旦插入到代碼,日志輸出的產生不需要人工干預。此外,日志輸出可以保存在永久介質,以在稍后進行研究。除了用在開發周期中,豐富的日志記錄包也可以被看作一個審計工具。
日志確實也有它的缺點。它可以減慢應用程序。如果太詳細,它可能會導致滾動失敗。為緩解這些問題,log4net 被設計成可靠的,快速的和可擴展的。由于日志很少是一個應用程序的主要焦點,因此,log4net API 致力于易于理解和使用。
Log4net 對很多框架可用。如下所示:
不是所有的框架都是一樣的,而且有些功能已經被排除。更多信息可查看 Framework Support。
Log4net 有三個主要組件:loggers,appenders 和 layouts。這三個組件一起工作使得開發者能夠根據信息類型和等級(Level)記錄信息,以及在運行時控制信息的格式化和信息的寫入位置(如控制臺,文件,內存,數據庫等)。過濾器(filter)幫助這些組件,控制追加器(appender)的行為和把對象轉換成字符串的對象渲染。
普通的 System.Console.WriteLine 任何日志記錄 API 的第一個也是最重要的優點在于,它可以禁用某些日志語句,而讓其他人輕松打印。這個功能假定,日志空間,也就是,所有可能的日志語句的空間,可以根據開發者選擇的標準被分類。
日志器(logger)被命名為實體。日志器名稱是大小寫敏感的,它們遵循以下層次的命名規則:
命名層次
如果后面跟一個點的名字是它后代(descendant )日志器名稱的一個前綴,那么,一個日志器被當做是另一個日志器的祖先(ancestor)。如果自身和后代日志器之間沒有祖先,那么,一個日志器被當做是一個子記錄器的父母。
該層次的工作機制非常類似 .NET 中的命名空間(namespace)和類層次結構(class hierarchy)。我們將會看到,這很方便。
例如,名為“Foo.Bar”的日志器是一個名為“Foo.Bar.Baz”日志器的父。類似的,“System”是“System.Text”的父,以及“System.Text.StringBuilder”的祖先。這種命名方式對很多開發者來說很熟悉。
也就是說,應用程序中每個類,都可能存在一個日志/記錄器~那么,接下來,在類的繼承層次結構中,如果一個類繼承另一個類,那么它們自己的日志器,也會存在一個繼承關系。
root 日志器位于日志器層次的最頂部。有三個有點:
使用 log4net.LogManager 類的靜態方法來檢索日志器。GetLogger 方法采取需要記錄日志的類作為參數。如下所示:
namespace log4net
{
public class LogManager
{
public static ILog GetLogger(string name);
public static ILog GetLogger(Type type);
}
}
GetLogger 方法具有一個參數,或是字符串,或是 Type 對象。log4net 用利用反射獲取需要寫日志的對象。
GetLogger 方法都返回一個 ILog 接口。代表一個傳遞給開發者的 Logger。ILog 接口定義如下:
namespace log4net
{
public interface ILog
{
/* Test if a level is enabled for logging */
bool IsDebugEnabled { get; }
bool IsInfoEnabled { get; }
bool IsWarnEnabled { get; }
bool IsErrorEnabled { get; }
bool IsFatalEnabled { get; }
/* Log a message object */
void Debug(object message);
void Info(object message);
void Warn(object message);
void Error(object message);
void Fatal(object message);
/* Log a message object and exception */
void Debug(object message, Exception t);
void Info(object message, Exception t);
void Warn(object message, Exception t);
void Error(object message, Exception t);
void Fatal(object message, Exception t);
/* Log a message string using the System.String.Format syntax */
void DebugFormat(string format, params object[] args);
void InfoFormat(string format, params object[] args);
void WarnFormat(string format, params object[] args);
void ErrorFormat(string format, params object[] args);
void FatalFormat(string format, params object[] args);
/* Log a message string using the System.String.Format syntax */
void DebugFormat(IFormatProvider provider, string format, params object[] args);
void InfoFormat(IFormatProvider provider, string format, params object[] args);
void WarnFormat(IFormatProvider provider, string format, params object[] args);
void ErrorFormat(IFormatProvider provider, string format, params object[] args);
void FatalFormat(IFormatProvider provider, string format, params object[] args);
}
}
Loggers 會分配一個等級。等級是 log4net.Core.Level 類的一個實例。下面是按優先級遞增的定義的等級:
如果一個給定的記錄器沒有被分配一個級別,那么,它將繼承它最近的、并已分配值的祖先。更正式地說:
等級層次(Level Inheritance)
The inherited level for a given logger X, is equal to the first non-null level in the logger hierarchy, starting at X and proceeding upwards in the hierarchy towards the root logger.
為了確保所有日志器最終都繼承一個等級,root 日志器總是具有一個已分配的等級。root 日志器的默認值是 DEBUG。
根據上面規則,下面四個表是各種分配的值和繼承的值。
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | none | Proot |
X.Y | none | Proot |
X.Y.Z | none | Proot |
上表的例子,只有 root logger 被分配了一個等級,其他三個 X、X.Y 和 X.Y.Z 的繼承值都是 Proot。
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | Px | Px |
X.Y | Pxy | Pxy |
X.Y.Z | Pxyz | Pxyz |
上表的例子,所有的日志器都分配了一個等級。這樣,不需要繼承值。
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | Pxyz | Pxyz |
上表的例子,日志器 root、X 和 X.Y.Z 被分別分配等級值 Proot、Px 和 Pxyz。日志器 X.Y 會從它的父 X 繼承等級值。
Logger name | Assigned level | Inherited level |
root | Proot | Proot |
X | Px | Px |
X.Y | none | Px |
X.Y.Z | none | Px |
上表的例子,日志器 root 和 X 等級值分別分配為 Proot 和 Px。日志器 X.Y 和 X.Y.Z 會從離它最近的,并已分配值的父來繼承。
日志請求是通過調用一個日志實例的打印方法(log4net.ILog)完成。這些打印方法是 Debug、Info、Warn、Error 和 Fatal。
根據定義,打印方法決定日志請求的等級。例如,如果 log 是一個日志器的實例,那么,語句 log.Info("..") 是等級為 INFO 的日志請求。
如果它的等級大于等于它日志器的等級,那么日志請求就被認為已啟用。否則,請求被認為禁用。沒有分配等級的日志器將從層次上繼承。規則如下:
Basic Selection Rule
A log request of level L in a logger with (either assigned or inherited, whichever is appropriate) level K, is enabled if L >= K.
該規則是 log4net 的核心。它假設等級是有序的。對于標準等級,具有 DEBUG < INFO < WARN < ERROR < FATAL。
用相同的參數調用 log4net.LogManager.GetLogger 方法總是返回引用一個完全相同 logger 對象。如下所示:
ILog x = LogManager.GetLogger("wombat");
ILog y = LogManager.GetLogger("wombat");
x 和 y 完全引用一個相同的 logger 對象。
因此,有可能配置一個日志器,然后在代碼中的任何地方都可以檢索到相同的實例。在生物學上,父母總是先于它們的孩子,而 log4net 日志器可以以任何順序創建和配置。具體地說,一個“父”日志器將發現和鏈接到它的后代,即使它在它的后代之后才實例化。
log4net 環境的配置通常是在應用程序初始化。優先的方法是讀取一個配置文件。
基于日志器選擇啟用或禁用日志請求的能力僅僅是其一部分。log4net 允許日志請求打印到多個目的地。按 log4net 的定義,一個輸出目的地被稱為附加器(appender)。追加器必須實現 log4net.Appenders.IAppender 接口。
下面是 log4net 定義的追加器:
類型 | 描述 |
log4net.Appender.AdoNetAppender | 把日志事件寫到數據庫,通過語句或存儲過程。 |
log4net.Appender.AnsiColorTerminalAppender | 把按顏色高亮標記的日志事件寫到 ANSI 終端窗口。 |
log4net.Appender.aspNetTraceAppender | 把日志事件寫到 ASP 跟蹤環境。之后,就可以在 ASP 頁面底部或跟蹤頁面呈現。 |
log4net.Appender.BufferingForwardingAppender | 在把日志轉發給子追加器之前,緩沖區記錄事件。 |
log4net.Appender.ColoredConsoleAppender | 把日志事件寫到應用程序控制臺。可以寫到標準輸出流或 error 流??梢詾槊總€等級定義文本和顏色。 |
log4net.Appender.ConsoleAppender | 把日志事件寫到應用程序控制臺??梢詫懙綐藴瘦敵隽骰?error 流。 |
log4net.Appender.DebugAppender | 把日志事件寫到 .NET 系統。 |
log4net.Appender.EventLogAppender | 把日志事件寫到 Windows Event Log。 |
log4net.Appender.FileAppender | 把日志事件寫到文件系統的一個文件。 |
log4net.Appender.ForwardingAppender | 把日志事件轉發給子追加器。 |
log4net.Appender.LocalSyslogAppender | 把日志事件寫到 local syslog service(僅針對 Unix)。 |
log4net.Appender.MemoryAppender | 把日志事件存儲在內存緩存。 |
log4net.Appender.NetSendAppender | 把日志事件寫到 Windows Messenger service。這些信息顯示在一個用戶終端的對話框。 |
log4net.Appender.OutputDebugStringAppender | 把日志事件寫到調試器。如果應用程序沒有調試器,那么系統調試器會顯示字符串。如果應用程序沒有調試器,并且系統調試器不活躍,那么消息會被忽略。 |
log4net.Appender.RemoteSyslogAppender | 把日志事件通過 UDP 網絡寫到一個 remoting syslog service。 |
log4net.Appender.RemotingAppender | 把日志事件通過 .NET remoting 寫到一個 remoting sink。 |
log4net.Appender.RollingFileAppender | 把日志事件寫到文件系統中的一個文件。RollingFileAppender 可以被配置,基于日期或大小約束寫入到多個文件。 |
log4net.Appender.SmtpAppender | 把日志事件發送到一個 email 地址。 |
log4net.Appender.SmtpPickupDirAppender | 把日志事件寫成 SMTP 消息到一個文件拾取目錄。這些文件可以被 SMTP 代理讀取或發送,如 IIS SMTP 代理。 |
log4net.Appender.TelnetAppender | 客戶端通過 Telnet 連接,來接受日志事件。 |
log4net.Appender.TraceAppender | 把日志事件寫到 .NET trace system。 |
log4net.Appender.UdpAppender | 使用 UdpClient 把日志事件作為無連接 UDP 數據發送到一個遠程主機或多播組。 |
日志器可以采用多個追加器。
對于給定的日志器,每個啟用日志請求都將被轉發到所有追加器,以及層次結構中更高的追加器。也就是說,追加器是從日志器層次結構中相加繼承的(inherited additively)。例如,如果一個 console appender 被添加到 root 日志器,那么,所有啟用日志請求都將至少打印到控制臺上。如果再添加一個文件追加器,那么,對于 X,對 X 和 X 的子,啟用日志請求將打印到一個文件和控制臺上。覆蓋此默認行為也是可以的,通過在日志器中設置相加標識為 false,追加器積累將不再相加。
管理附加器相加(additivity )的規則如下。
Appender Additivity
The output of a log statement of logger X will go to all the appenders in X and its ancestors. This is the meaning of the term "appender additivity".
However, if an ancestor of logger X, say Y, has the additivity flag set to false, then X's output will be directed to all the appenders in X and it's ancestors up to and including Y but not the appenders in any of the ancestors of Y.
Loggers have their additivity flag set to true by default.
下表列出一個例子:
Logger Name | Added Appenders | Additivity Flag | Output Targets | Comment |
root | A1 | not applicable | A1 | There is no default appender attached to root. |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | Appenders of "x" and root. |
x.y | none | true | A1, A-x1, A-x2 | Appenders of "x" and root. |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | Appenders in "x.y.z", "x" and root. |
security | A-sec | false | A-sec | No appender accumulation since the additivity flag is set to false. |
security.access | none | true | A-sec | Only appenders of "security" because the additivity flag in "security" is set to false. |
追加器可以篩選被傳遞給它們的事件。在配置中指定過濾器,允許更好地控制通過不同的追加器記錄的事件。
控制的最簡單形式是在追加器中指定閾值。
更復雜的和自定義事件過濾可以使用每個追加器中定義的過濾器鏈來完成。過濾器必須實現 log4net.Filter.IFilter 接口。
下表列出 log4net 中定義的過濾器:
類型 | 描述 |
log4net.Filter.DenyAllFilter | 丟棄所有日志事件。 |
log4net.Filter.LevelMatchFilter | 準確匹配事件等級。 |
log4net.Filter.LevelRangeFilter | 匹配一個范圍的等級。 |
log4net.Filter.LoggerMatchFilter | 匹配一個日志器名字的開始。 |
log4net.Filter.PropertyFilter | 匹配指定屬性名稱的子字符串。 |
log4net.Filter.StringMatchFilter | 匹配事件消息的子字符串。 |
過濾器可以被配置為根據匹配接受或拒絕事件。
很多時候,用戶不僅希望自定義輸出目的地,還要定義輸出格式。這是通過與追加器相關的布局(layout)來實現的。布局負責根據用戶意愿格式化日志請求,而一個追加器負責發送已格式化的輸出到目的地。 PatternLayout,是標準的 log4net 的一部分,讓用戶根據轉換模式指定數據格式,類似于 C 語言 printf。
例如,帶轉換模式的 PatternLayout "%timestamp [%thread] %-5level %logger - %message%newline" 將會輸出:
176 [main] INFO Com.Foo.Bar - Located nearest gas station.
第一個域是流逝毫秒時間;第二個域是日志請求的線程;第三個域日志語句等級,如 INFO、WARN、ERROR 等;第四個域是與日志請求相關的日志器的名稱。“-”后面的文本是具體消息。
下表是 log4net 中定義的布局:
類型 | 描述 |
log4net.Layout.ExceptionLayout | Renders the exception text from the logging event. |
log4net.Layout.PatternLayout | Formats the logging event according to a flexible set of formatting flags. |
log4net.Layout.RawTimeStampLayout | Extracts the timestamp from the logging event. |
log4net.Layout.RawUtcTimeStampLayout | Extracts the timestamp from the logging event in Universal Time. |
log4net.Layout.SimpleLayout | Formats the logging event very simply: [level] - [message] |
log4net.Layout.xmlLayout | Formats the logging event as an XML element. |
log4net.Layout.XmlLayoutSchemaLog4j | Formats the logging event as an XML element that complies with the log4j event dtd. |
同樣重要的是,log4net 會根據用戶指定的標準呈現日志消息的內容。例如,如果你經常需要記錄 Oranges,它是你項目中使用的一個對象類型,那么,你可以注冊一個 OrangeRenderer,這樣無論何時需要記錄 Orange 就可以調用。
對象渲染按照類的層次結構。例如,假設橙子是水果,如果你注冊一個 FruitRenderer,所有的水果,包括橙子,將按 FruitRenderer 渲染,除非你已經注冊一個橙子的 OrangeRenderer。
對象渲染器必須實現 log4net.ObjectRenderer.IObjectRenderer 接口。
注意,DebugFormat、InfoFormat、WarnFormat、ErrorFormat 和 FatalFormat 方法不使用 ObjectRenderers。
新聞熱點
疑難解答