<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-314  評論-209  文章-0  trackbacks-0
    solo L
    發布日期:2006年07月30日,更新日期:2006年07月30日
    1996年初,歐洲安全電子市場(EU SEMPER)項目組決定編寫自己的日志記錄API,后來這個API演變成了Log4j。Log4j是一個開放源碼項目,一個非常流行的Java日志記錄包。它允許開發者向代碼中插入日志記錄語句,還允許在不修改應用程序源碼的情況下修改記錄日志的行為。

    1996年初,歐洲安全電子市場(EU SEMPER)項目組決定編寫自己的日志記錄API,后來這個API演變成了Log4j。Log4j是一個開放源碼項目,一個非常流行的Java日志記錄包。它允許開發者向代碼中插入日志記錄語句,還允許在不修改應用程序源碼的情況下修改日志記錄的行為。

    幾乎每一個項目都會使用日志記錄,但是由于日志記錄不是項目的核心,因此受重視的程度一般不是很高。我們認為使用日志記錄是一件非常嚴肅的事情,而且做好使用日志記錄的規劃比單純記錄日志本身更加重要。

    本文將比較全面的闡述Log4j的設計原理和使用方法。

    回頁首

    日志記錄

    日志記錄記錄的是應用程序運行的軌跡。我們可以通過查看這些軌跡來調試應用程序,這可能也是日志記錄最為流行的用法了。但是我們必須意識到規劃良好的日志記錄中還含有豐富的信息,通過手工的方式或借助一些工具(大多數時候需要自己來書寫這些工具)來分析挖掘這些信息。

    例如,如果我們在規劃中指出必須記錄用戶的每一次操作,記錄的樣式為 [日志信息]-[操作開始的時間]-[日志級別]-[日志類別]-[用戶名]-[操作名]-[消息],這只是我們假設的一種樣式,實際的日志中一般會含有比這更加豐富的信息。為了更好的理解,我們根據該樣式構造了一些日志記錄(其中日志類別org.solol.Main、org.solol.Parser和org.solol.UserOperator使用了不同的樣式):

    [日志信息]-[2006-07-30 08:54:20]-[INFO]-[org.solol.Main]-[具體的消息]
    [日志信息]-[2006-07-30 08:55:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報表1]-[具體的消息]
    [日志信息]-[2006-07-30 08:55:30]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報表2]-[具體的消息]
    [日志信息]-[2006-07-30 08:56:01]-[INFO]-[org.solol.Parser]-[具體的消息]
    [日志信息]-[2006-07-30 08:57:26]-[INFO]-[org.solol.UserOperator]-[User2]-[添加用戶User3]-[具體的消息]
    [日志信息]-[2006-07-30 08:58:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報表3]-[具體的消息]
    [日志信息]-[2006-07-30 08:59:38]-[INFO]-[org.solol.UserOperator]-[User3]-[查詢報表1]-[具體的消息]
    [日志信息]-[2006-07-30 08:59:39]-[INFO]-[org.solol.UserOperator]-[User2]-[退出系統]-[具體的消息]
    

    從上面的日志記錄中我們很容易抽取出某一用戶的操作列表,如對于用戶User1我們的結果為:

    [日志信息]-[2006-07-30 08:55:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報表1]-[具體的消息]
    [日志信息]-[2006-07-30 08:55:30]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報表2]-[具體的消息]
    [日志信息]-[2006-07-30 08:58:20]-[INFO]-[org.solol.UserOperator]-[User1]-[查詢報表3]-[具體的消息]
    

    這樣我們就得到了某一時間段中User1的操作列表,可以利用這一列表來進行安全分析。

    我們還可以從另外的角度來分析上面的日志記錄,如我們很容易統計出操作(日志類別為org.solol.UserOperator)發生的總次數(6次),其中操作[查詢報表1]為2次,[查詢報表2]為1次,[查詢報表3]為1次,[添加用戶User3]為1次,[退出系統]為1次。這樣我們就可以得出系統中的那些操作用戶使用的比較頻繁。

    以上我們從兩個角度對日記記錄中的信息進行了簡單的挖掘,實際中待挖掘的方面要豐富的多,這取決于您的意圖和您的想象力。

    這里我們還要特別強調一下:所有這一切都需要有使用日志記錄的良好規劃。如果規劃不好(即日志記錄沒有規律性),那么我們挖掘時的任務就會非常繁重或者使挖掘成為一個不可能的任務。

    文章到了這里我們要來描述日志記錄的最為流行的用法了,即調試應用程序。我們在調試應用程序時一般會使用兩種方法,除了日志記錄之外,還有debugger調試器。

    我們不想把他們放到一起來描述,因為這是兩個完全不同的問題,雖然他們都用來調試應用程序。使用debugger調試器我們可以清楚的知道引發錯誤的上下文及其相關信息,也可以使用單步執行、設置斷點、檢查變量以及暫掛和恢復線程等等比較高級的能力,但是盡管這樣它也不能替代日志記錄,同樣日志記錄也不能替代debugger調試器。我們要結合使用這兩種方法,不同的場景使用不同的方法會有更好的效果。

    我們認為使用日志記錄來調試應用程也應該充分考慮軟件的開發周期。這里我們只考慮軟件開發周期中的與日志記錄有關的兩個階段:

    • 開發階段,用來記錄應用程序的方方面面和各種細節,非常詳細,使得一看到它就知道那里出了問題,出了什么樣的問題。
    • 出品階段,要能夠記錄各種級別的錯誤和警告,同時也要適度記錄應用程序正常運行的關鍵信息,這些信息可以給相關人員(開發人員、測試人員、用戶等)極大的信心,使他們可以毫不猶豫的告訴您--瞧我們的軟件在正常的運行。如一個好的web服務器的啟動日志記錄不僅要包含錯誤和警告,還要包含服務器正在啟動,正在加載某某組件等等,最后還要提示啟動是成功還是失敗。

    閱讀到這里我們就應該著手實現我們的日志記錄了。比較幸運的是我們有好多日志記錄軟件包可選,這就使我們不必關心日志記錄的細節,只要把主要的精力放到日志記錄的規劃上就好了。我們選擇的是Log4j,文章的余下部分將主要介紹這個Java日志記錄軟件包。

    回頁首

    log4j的特性

    log4j的特性列表:

    • 在運行速度方面進行了優化
    • 使用基于名稱的日志(logger)層次結構
    • 是fail-stop的
    • 是線程安全的
    • 不受限于預定義的實用工具集
    • 可以在運行時使用property和xml兩種格式的文件來配置日志記錄的行為
    • 在一開始就設計為能夠處理Java異常
    • 能夠定向輸出到文件(file)、控制臺(console)、java.io.OutputStream、java.io.Writer、遠程服務器、遠程Unix Syslog守護者、遠程JMS監聽者、NT EventLog或者發送e-mail
    • 使用DEBUG、INFO、WARN、ERROR和FATAL五5個級別
    • 可以容易的改變日志記錄的布局(Layout)
    • 輸出日志記錄的目的地和寫策略可以通過實現Appender接口來改變
    • 支持為每個日志(logger)附加多個目的地(appender)
    • 提供國際化支持
    回頁首

    log4j的設計原理

    Log4j有三個主要的組件:Logger、Appender和Layout。這三個組件相互配合使得我們可以獲得非常強大的日志記錄的能力。

    Logger

    Logger的名稱是區分大小寫的,依據名稱可以確定其層次結構(即父子關系),規則如下:

    • 如果Logger A的名稱后跟一個點(.)是Logger B的名稱的前綴就認為Logger A是Logger B的祖先。
    • 如果在Logger A和Logger B之間,Logger B沒有任何其它的祖先就認為Logger A是Logger B的父親。

    在Logger的層次結構的最頂層是root logger,它會永遠存在,而且不能通過名字取到。

    上面文字的描述可能不好的理解,為此我們給出了一張圖,Logger的層次結構圖,從中可以非常直觀的看出三種主要組件的關系和各自所起的作用。

    圖示 1. Logger的層次結構圖
    Logger的層次結構圖

     

    Loger x.y是Logger x.y.z的祖先,因為x.y.是x.y.z的前綴,這符合規則的前一條。另外在Logger x.y和Logger x.y.z之間,Logger x.y.z沒有其它的祖先,因此Logger x.y是Logger x.y.z的父親,這符合規則的后一條。這樣我們依據上面的規則就可以構造出如圖1所示的Logger的層次結構。

    從圖1中我們還可以看到每一個Logger都有一個Level,根據該Level的值Logger決定是否處理對應的日志請求。如果Level沒有被設置,就象圖1中的Logger x.y一樣,又該怎么辦呢?答案是可以從祖先那里繼承。

    如果Logger C沒有被設置Level,那么它將沿著它的層次結構向上查找,如果找到就繼承并結束,否則會一直查找到root logger結束。因為log4j在設計時保證root logger會被設置一個默認的Level,所以任何logger都可以繼承到Level。

    圖1中的Logger x.y沒有被設置Level,但是根據上面的繼承規則,Logger x.y繼承了root logger的Level。

    我們在來看看Logger選擇日志記錄請求(log request)的規則:

    假設Logger M具有q級的Level,這個Level可能是設置的也可能是繼承到的。

    如果向Logger M發出一個Level為p的日志記錄請求,那么只有滿足p>=q時這個日志記錄請求才會被處理。

    org.apache.log4j.Logger中的不同方法發出不同Level的日志記錄請求,如下:

    • public void debug(Object message),發出Level為DEBUG的日志記錄請求
    • public void info(Object message),發出Level為INFO的日志記錄請求
    • public void warn(Object message),發出Level為WARN的日志記錄請求
    • public void error(Object message),發出Level為ERROR日志記錄請求
    • public void fatal(Object message),發出Level為FATAL的日志請求
    • public void log(Level l, Object message),發出指定Level的日志記錄請求

    其中的靜態常量DEBUG、INFO、WARN、ERROR、FATAL是在org.apache.log4j.Level中定義的,除了使用這些預定義的Level之外,Log4j還支持自定義Level。

    注:org.apache.log4j.Level中還預定義了一些其它的Level。

    Appender

    在Log4j中,Appender指的是日志記錄輸出的目的地。當前支持的Appender(目的地)有文件(file)、控制臺(console)、java.io.OutputStream、java.io.Writer、遠程服務器、遠程Unix Syslog守護者、遠程JMS監聽者、NT EventLog或者發送e-mail。如果您在上面沒有找到適合的Appender,那就需要考慮實現自己的自定義Appender了。

    每個Logger可以有多個Appender,但是相同的Appender只會被添加一次。

    Appender的附加性意味著Logger C會將日志記錄發給它的和它祖先的所有Appender。在圖1中Logger a會將日志記錄發給它自己的JDBCAppender和它的祖先root logger的ConsoleAppender和FileAppender。Logger x.y.z自己沒有Appender,它將把日志記錄發給它的祖先root logger的ConsoleAppender和FileAppender,如果Logger x.y也含有Appender,那么它們也會包括在內。

    Appender的附加性是可以被中斷的。假設Logger C的一個祖先為Logger P,如果Logger P的附加性標志(additivity flag)設置為假,那么Logger C會將日志記錄只發給它的和在它和Logger P之間的祖先(包括Logger P)的Appender,而不會發給Logger P的祖先的Appender。Logger的附加性標志(additivity flag)默認值為ture。

    在圖1中如果沒有設置Logger a的附加性標志(additivity flag),而是使用默認值true,那么Logger a會將日志記錄發給它自己的JDBCAppender和它祖先root logger的ConsoleAppender和FileAppender,這和上面的描述相同。如果設置Logger a的附加性標志(additivity flag)的值false,那么Logger a會將日志記錄發給它自己的JDBCAppender而不會在發給它祖先root logger的ConsoleAppender和FileAppender了。

    Layout

    Appender定制了輸出目的地,通常我們還需要定制日志記錄的輸出格式,在Log4j中是通過將Layout和Appender關聯到一起來實現的。Layout依據用戶的要求來格式化日志記錄。PatternLayout(標準Log4j組件)讓用戶依據類似于C語言printf函數的轉換模式來指定輸出格式。

    例如,轉換模式(conversion pattern)為"%r [%t] %-5p %c - %m%n"的PatternLayout將生成類似于以下內容的輸出:

    176 [main] INFO  org.foo.Bar - Located nearest gas station.
    

    在上面的輸出中:

    • 第一個字段表示自程序開始到發出日志記錄請求時所消耗的毫秒數
    • 第二個字段表示發出日志記錄請求的線程
    • 第三個字段表示日志記錄請求的Level
    • 第四個字段表示發出日志記錄請求的Logger的名稱
    • 第五個字段(-后的文本)表示日志記錄請求的消息

    Log4j中還提到了一些其它的Layout,包括HTMLLayout、SimpleLayout、XMLLayout、TTCCLayout和DateLayout。如果這些不能滿足您的要求,還可以自定義自己的Layout。

    回頁首

    log4j的配置

    依據既有的經驗顯示用于日志記錄的代碼大約是全部代碼量的4%。如果應用程序具有一定的規模,日志記錄語句的數量還是比較巨大的,因此必須有效的管理這些語句。

    在Log4j中我們可以通過配置Log4j環境來有效的管理日志記錄。配置的方式有三種:

    • 通過程序配置
    • 通過Property文件配置
    • 通過XML文件配置

    程序配置

    通過程序配置Log4j環境實際上就是在應用程序的代碼中改變Logger的Level或增加減少Appender等等。

    Log4j提供了BasicConfigurator,它只是為root logger添加Appender。其中,

    • BasicConfigurator.configure()為root logger添加一個關聯著PatternLayout.TTCC_CONVERSION_PATTERN的ConsoleAppender
    • BasicConfigurator.configure(Appender appender)為root logger添加指定的Appender

    我們可以把BasicConfigurator看成是一個簡單的使用程序配置Log4j環境的示例。例如,要給root logger添加兩個Appender(A和B),下面的代碼分別完成了這個要求。

    不使用BasicConfigurator:

    //示例代碼,不能直接使用
    Logger root = Logger.getRootLogger();
    root.addAppender(A);
    root.addAppender(B);
    

    使用BasicConfigurator:

    //示例代碼,不能直接使用
    BasicConfigurator.configure(A);
    BasicConfigurator.configure(B);
    

    Property文件配置

    這里要使用PropertyConfigurator來分析配置文件并設置日志記錄,但是要注意日志記錄先前的配置不會被清除和重設。

    Property文件是由key=value這樣的鍵值對所組成的,可以使用#或!作為注釋行的開始。下面給出了兩個簡單的示例:

    非常簡單的示例1:

    log4j.rootLogger=DEBUG, A1
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%-4r %-5p [%t] %37c %3x - %m%n
    

    稍顯復雜的示例2:

    log4j.rootLogger=, A1, A2
    log4j.appender.A1=org.apache.log4j.ConsoleAppender
    log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    log4j.appender.A1.layout.ConversionPattern=%d %-5p [%t] %-17c{2} (%13F:%L) %3x - %m%n
    log4j.appender.A2=org.apache.log4j.FileAppender
    log4j.appender.A2.File=filename.log
    log4j.appender.A2.Append=false
    log4j.appender.A2.layout=org.apache.log4j.PatternLayout
    log4j.appender.A2.layout.ConversionPattern=%-5r %-5p [%t] %c{2} - %m%n
    

    上面的兩個示例只是讓您對配置文件的格式有一個大體的認識,我們將在后面詳細的描述各個配置元素的語法。

    Repository-wide threshold:

    Repository-wide threshold指定的Level的優先級高于Logger本身的Level。語法為log4j.threshold=[level],level可以為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。也可以使用自定義Level,這時的語法為log4j.threshold=[level#classname]。默認為ALL。

    依據上面的規則,我們有這樣的結論:如果log4j.threshold=ERROR,Logger C的Level=DEBUG,這時只有高于等于ERROR的日志記錄請求會被Logger C處理。

    Appender的配置:

    Appender的配置語法為

    # For appender named appenderName, set its class.
    # Note: The appender name can contain dots.
    log4j.appender.appenderName=fully.qualified.name.of.appender.class
    # Set appender specific options.
    log4j.appender.appenderName.option1=value1
    ...
    log4j.appender.appenderName.optionN=valueN
    #For each named appender you can configure its Layout.
    #The syntax for configuring an appender's layout is:
    log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
    log4j.appender.appenderName.layout.option1=value1
    ....
    log4j.appender.appenderName.layout.optionN=valueN
    

    Logger的配置:

    root logger的配置語法:

    log4j.rootLogger=[level], appenderName, appenderName, ...,其中level可以為OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。也可以使用自定義Level,這時的語法為[level#classname]。

    如果Level被指定那么root logger的Level將被配置為指定值。如果Level沒有被指定那么root logger的Level不會被修改。從上面的語法中我們可以看出通過用,分隔的列表可以為root logger指定多個Appender。

    對于root logger之外的logger語法是相似的,為log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...

    上面只有INHERITED和NULL需要說明一下,其它部分和root logger相同。INHERITED和NULL的意義是相同的。如果我們使用了它們,意味著這個logger將不在使用自己的Level而是從它的祖先那里繼承。

    Logger的附加性標志(additivity flag)可以使用log4j.additivity.logger_name=[false|true]來配置。

    ObjectRenderer配置:

    我們可以通過ObjectRenderer來定義將消息對象轉換成字符串的方式。語法為log4j.renderer.fully.qualified.name.of.rendered.class=fully.qualified.name.of.rendering.class。如:

    //my.Fruit類型的消息對象將由my.FruitRenderer轉換成字符串
    log4j.renderer.my.Fruit=my.FruitRenderer
    

    對上面的各個配置元素的語法理解之后,在來看示例1和2就很容易了。

    PropertyConfigurator不支持Filter的配置。如果要支持Filter您可以使用DOMConfigurator,即使用XML文件的方式配置。

    XML文件配置

    要使用DOMConfigurator.configure()來讀取XML格式的配置文件。XML文件格式的定義是通過org/apache/log4j/xml/log4j.dtd來完成的,各個配置元素的嵌套關系如下:

    <!ELEMENT log4j:configuration (renderer*, appender*,(category|logger)*,root?,categoryFactory?)>
    

    這里沒有給出更為詳細的內容,要了解詳細的內容需要查閱log4j.dtd。

    下面這個簡單的示例可以使您對XML配置文件的格式有一個基本的認識:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j SYSTEM "log4j.dtd">
    <log4j>
    <appender name="A1" class="org.apache.log4j.FileAppender">
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %c{2} - %m\n"/>
    </layout>
    </appender>
    <appender name="A2" class="org.apache.log4j.FileAppender">
    <layout class="org.apache.log4j.TTCCLayout">
    <param name="DateFormat" value="ISO8601" />
    </layout>
    <param name="File" value="warning.log" />
    <param name="Append" value="false" />
    </appender>
    <category name="org.apache.log4j.xml" priority="debug">
    <appender-ref ref="A1" />
    </category>
    <root priority="debug">
    <appender-ref ref="A1" />
    <appender-ref ref="A2" />
    </root>
    </log4j>
    
    回頁首

    默認初始化過程

    默認初始化過程在LogManager類的靜態初始化器中完成。具體步驟如下:

    • 檢查系統屬性log4j.defaultInitOverride,如果值為false則執行初始化過程,否則跳過初始化過程。
    • 將系統屬性log4j.configuration的值賦給變量resource。如果log4j.configuration沒有被定義則使用默認值log4j.properties。
    • 試圖轉換變量resource到一個url。
    • 如果變量resource不能轉換成一個url,那么將使用org.apache.log4j.helpers.Loader.getResource(resource, Logger.class)得到一個url。
    • 如果還是得不到url,將忽略默認初始化過程。如果得到url將使用PropertyConfigurator或DOMConfigurator來配置,也可以使用自定義的XXXConfigurator。
    參考資料
    關于作者
    solo L 一位有些理想主義的軟件工程師,創建了solol.org。他常常在這里發表一些對技術的見解。
    posted on 2008-05-21 10:20 xzc 閱讀(1969) 評論(0)  編輯  收藏 所屬分類: Log4j
    主站蜘蛛池模板: 中文字幕久久亚洲一区| 久久久无码精品亚洲日韩软件| 久久香蕉国产线看观看亚洲片| 亚洲精品视频免费观看| 青青青国产色视频在线观看国产亚洲欧洲国产综合 | 亚洲综合无码精品一区二区三区 | 亚洲国产精品无码专区| 中国毛片免费观看| 亚洲成av人片在线观看无码不卡| 国内永久免费crm系统z在线| 亚洲国产精品va在线播放| 在线观看肉片AV网站免费| 亚洲国产天堂在线观看| 1000部拍拍拍18免费网站| 亚洲综合中文字幕无线码| 啦啦啦在线免费视频| 亚洲JIZZJIZZ妇女| 亚洲日韩中文在线精品第一 | 亚洲色图黄色小说| 少妇高潮太爽了在线观看免费| 在线观看亚洲AV日韩AV| 四虎影在线永久免费四虎地址8848aa| 野花视频在线官网免费1| 亚洲精品无码永久在线观看| 国产午夜精品理论片免费观看| 久久久久亚洲精品无码蜜桃| 精品久久久久久久久免费影院| 色窝窝亚洲AV网在线观看| 亚洲自偷自偷在线制服| 99久久99热精品免费观看国产| 美女视频黄免费亚洲| 亚洲人成无码网WWW| 在线观看www日本免费网站| 亚洲a∨无码男人的天堂| mm1313亚洲国产精品美女| 久久久久成人片免费观看蜜芽| 亚洲va在线va天堂成人| 亚洲日韩中文字幕日韩在线 | 日韩电影免费在线观看视频| 一级特黄录像免费播放肥| 亚洲国产av高清无码|