log4j 配置

 國內文章一大抄。全是 人云亦云,以訛傳訛。 baidu出來的文章 也是千人一面。
結合國外的文章 :  http://logging.apache.org/log4j/1.2/manual.html#additivity

 

好好梳理一下 log4j的理論 和用法。

  • 首先 log4j 是一步步 演化而來的。其次 log 在開發和維護中都是 至關重要的。
  • 寫好 log 代碼調試 甚至是不需要的
  • log缺點: 會減慢程序。


Loggers, Appenders and Layouts
這三位是log4j的主要組成。

  1. loggers : 是命名實體,大小寫敏感,並且各個loggers之間 通過名字可以組成logger 樹。

 

  • 所有的logger 根據繼承性 ,去尋找最終使用的logger.
  • root looger 是 logger 樹的根。
  • 所以說 只要配置了 rootlogger. 任何jar的 logger 都是有可能被打印出來的(還取決於appender)

它有2點特殊性
1.總是存在。
2.不能通過名字獲取 ( Logger.getRootLogger可以獲取)

其他logger 通過 Logger.getLogger(logname)獲取

log等級

  • TRACE,
  • DEBUG,
  • INFO,
  • WARN,
  • ERROR and
  • FATAL

DEBUG < INFO < WARN < ERROR < FATAL.

從低到高 ,他們繼承Leverl.java. 可以自定義Level.

logger如何沒有指定level.他的level是離他最近的ancestor 繼承

To ensure that all loggers can eventually inherit a level, the root logger always has an assigned level.
爲了確保所有的loggers都有level. 所以 rootlogger 最好指定level


Appenders: log輸出目的地

Threshold屬性,是對logger 接受的level .進行二次過濾

additivity flag: 當前logger 打日誌時,會將日誌 傳到 當前 logger 和 當前logger 的 所有ancestor logger 的 appenders去。
這是由additivity 決定的。默認是true.如果不希望累計 appender.可以將 該值設爲false/



Layouts log輸出的格式
格式化日誌信息


Log4J採用類似C語言中的printf函數的打印格式格式化日誌信息,打印參數如下:
%m 輸出代碼中指定的消息
%p 輸出優先級,即DEBUG,INFO,WARN,ERROR,FATAL
%r 輸出自應用啓動到輸出該log信息耗費的毫秒數
%c 輸出所屬的類目,通常就是所在類的全名
%t 輸出產生該日誌事件的線程名
%n 輸出一個回車換行符,Windows平臺爲“rn”,Unix平臺爲“n”
%d 輸出日誌時間點的日期或時間,默認格式爲ISO8601,也可以在其後指定格式,比如:%d{yyyy MMM ddHH:mm:ss,SSS},輸出類似:2002年10月18日 22:10:28,921
%l 輸出日誌事件的發生位置,包括類目名、發生的線程,以及在代碼中的行數。

log的 ndc.多線程 輸出混亂 如何解決


如果 當前 需要的logger 不存在 ,那麼系統會直接去找paranet.一直到 root logger





隨着 Web 應用的複雜化,用戶在網站上的操作過程日益複雜。網站功能的多樣化和交互性的提高爲用戶提供了多種可能的瀏覽路徑。對於一個複雜的站點,用戶在網站上操作的行爲模式和操作習慣的分析,會給網站的優化提供基礎的數據支持。而從技術上要爲這種分析提供支持,就需要記錄下每個用戶在網站上的操作過程。另一方面,這種數據的記錄也有助於解決用戶在使用中出現的問題。我們只要知道用戶遇到問題的時間和一些基本信息,就可以從日誌中查出此用戶遇到問題時的操作過程,從而有助於再現用戶的出錯場景,進而幫助用戶解決問題。此外,網站用戶的安全審計和分析用戶特徵的數據挖掘等工作也需要提供一個方法能對用戶的網站操作進行跟蹤和紀錄。

通常 Web 應用的開發者會在開發過程中設置很多的跟蹤點,在這些跟蹤點向日志系統輸出一些應用程序運行的信息,如果這些信息足夠全面的話,開發者就可以利用他們猜測出程序如何處理的用戶請求,以及可能遇到了什麼問題。但不幸的是,如果一個站點在設計階段沒有把用戶跟蹤作爲系統必須解決的一個問題提出的話,這些日誌很可能就只是開發者爲滿足系統調試的需要而設置的一些信息。當用戶訪問量急劇增加的的時候,就會出現下面的問題。

在一個高訪問量的 Web 應用中,經常要在同一時刻處理大量的用戶請求。Web 服務器會爲每一個請求分配一個線程,每一個線程都會向日志系統輸入一些信息,通常日誌系統都是按照時間順序而不是用戶順序排列這些信息的,這些線程的交替運行會讓所有用戶的處理信息交錯在一起,讓人很難分辨出那些記錄是同一個用戶產生的。另外,高可用性的網站經常會使用負載均衡系統平衡網絡流量,這樣一個用戶的操作記錄很可能會分佈在多個 Web 服務器上,如果我們沒有一種方法來標示一條記錄是哪個用戶產生的,從這衆多的日誌信息中篩選出對我們有用的東西將是一項艱鉅的工作。

本文試圖探討的解決方案是建立在 Log4J 的基礎上的,如果你的 Web 站點已經使用了 Log4J 作爲日誌系統的 API 接口,根據本文所介紹的方法,就可以很容易的在每一條日誌上保存用戶上下文信息,爲用戶跟蹤保存基本的訪問數據。爲了更清晰地介紹這種方法,我們先對 Log4J 以及 NDC/MDC 做個簡單的介紹。

Log4J 簡介

Log4J 主要構件

Log4J 是 Apache 組織提供的一個日誌組件, 它設計了靈活的配置文件,利用它可以在不更改程序的情況下,通過修改配置文件來調控日誌的輸出。下面是 Log4J 最主要的三大基本構件:

    記錄器(Loger)

    對日誌信息進行分類篩選。通過指定優先級,控制程序中日誌信息的輸出:高於優先級的日誌可以被輸出,低於優先級的日誌則被忽略。
    輸出源(Appenders)

    指定日誌信息的輸出設備。Log4J 目前支持的輸出設備有以下幾種:
        org.apache.log4j.ConsoleAppender(控制檯)
        org.apache.log4j.FileAppender(文件)
        org.apache.log4j.DailyRollingFileAppender(每天產生一個日誌文件)
        org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的時候產生一個新的文件)
        org.apache.log4j.WriterAppender(將日誌信息以流格式發送到任意指定的地方)
        org.apache.log4j.SocketAppender (Socket)
        org.apache.log4j.NtEventLogAppender (NT的Event Log)
        org.apache.log4j.JMSAppender (電子郵件)

    程序員也可以根據自己的需要定製 Appenders,實現更復雜和更爲方便實用的日誌管理,比如把日誌輸入數據庫,或者傳輸到統一的日誌服務器,等等。
    佈局(Layouts)

    指定日誌輸出的格式。Log4J 提供的 Layout 有以下幾種:
        org.apache.log4j.HTMLLayout(以 HTML 表格形式佈局)
        org.apache.log4j.PatternLayout(可以靈活地指定佈局模式)
        org.apache.log4j.SimpleLayout(包含日誌信息的級別和信息字符串)
        org.apache.log4j.TTCCLayout(包含日誌產生的時間、線程、類別等等信息)

軟件開發人員可以通過這三大構件,根據日誌的類型和優先級進行記錄,並且可以在程序運行時去控制日誌信息輸出的格式和往什麼地方輸出(控制檯、日誌文件)。

Log4J 使用示例

下面是一個在 Web 應用中使用 Log4J 的簡單例子。

第 1 步:修改 Web 應用的 web.xml 文件

在 Web 應用的 web.xml 中指明 Log4J 的配置文件名稱。


代碼 1. 在 web.xml 中配置 Log4J

    ……
        <servlet>
            <servlet-name>log4j-init</servlet-name>
            <servlet-class>is.dsw.common.base.log4jInit</servlet-class>
            <init-param>
                 <!—下面的初始化參數指定 log4j 的配置文件爲 log4j.properties -->
                <param-name>log4j</param-name>
                <param-value>/WEB-INF/log4j.properties</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet>
            <servlet-name>log4jServlet</servlet-name>
            <servlet-class>is.dsw.common.base.log4jServlet</servlet-class>
            <load-on-startup>0</load-on-startup>
        </servlet>
    ……

第 2 步:對 Log4J 進行配置

我們看到,在上面的配置中指定了 Log4J 的配置文件名爲 log4j.properties。在這個文件中,我們可以通過對前文所述的 Log4J 的三大控件進行配置:

代碼 2. log4j.properties 配置文件

    #此項指定 log4j 本身不輸出調試信息
    log4j.debug=false
       # 設置記錄器(logger)的輸出信息的級別,並指定信息源appender,此例中,輸出到JADEA and A1.
       log4j.rootCategory=DEBUG, OPAL, A1
       ……
    #信息源appender爲每天產生一個日誌文件
    log4j.appender.OPAL= org.apache.log4j.DailyRollingFileAppender
    #日誌格式爲靈活佈局模式
    log4j.appender.OPAL.layout=org.apache.log4j.PatternLayout
    #指定靈活佈局模式下日誌的格式
       #%c 輸出所屬類的全名
       #%d 輸出日誌時間其格式爲 可指定格式 如 %d{HH:mm:ss}等
       #%n 換行符
       #%m 輸出代碼指定信息,如info(“message”),輸出message
       #%p 輸出日誌的優先級,即 FATAL ,ERROR,INFO 等
    log4j.appender.OPAL.layout.ConversionPattern=%d %p %c - %m%n

第 3 步:在 Web 應用的 Java 代碼中使用 Log4J

進行完上面兩步的配置,我們就可以在 Java 程序中使用 Log4J 進行日誌的輸出。

首先讀取 Log4J 配置信息,初始化 Logger。

代碼 3. 初始化 Log4J

    public class log4jInit extends HttpServlet { 
        public void init() {
            /*找到在web.xml中指定的log4j.properties文件並讀取配置信息*/
            String prefix =  getServletContext().getRealPath("/");
            String file = getInitParameter("log4j");
            System.out.println("................log4j start");
            if(file != null) {
                PropertyConfigurator.configure(prefix+file);
            }
        }
    }

    然後在 Java 代碼中就可以任意地使用 Log4J 進行日誌輸出。
    代碼 4. 在 Web 應用的 Java 代碼中使用 Log4J 進行日誌輸出

    public class log4jServlet extends HttpServlet { 
        public void doGet(HttpServletRequest req, HttpServletResponse res)
        throws IOException {
            Logger logger = Logger.getLogger(log4jServlet.class); 
            /*輸出日誌*/
            logger.info("Entering doGet@Log4jServlet.");
            /*更多的代碼和日誌輸出*/
            ........
            ........
            logger.info("Exiting doGet@Log4jServlet.");
        }
    }

運行該 Web 應用後,當有客戶機訪問該 Web 應用時,就會得到應用程序輸出的日誌信息。下面是以三個併發用戶訪問該 Web 應用的情況下,從打印出的日誌中挑選出的上述相關日誌信息:

圖 1. 運行 Web 應用後輸出的日誌
Log4J日誌1

在上面的例子中,我們看到,利用 Log4J 提供的功能,我們很方便地輸出了程序的日誌,但是同時也看到,這樣的日誌在多用戶併發訪問的情況下,特別是應用程序複雜且擁有大量併發訪問用戶的情況下,根本無法區分哪些日誌是屬於哪個用戶的,這樣就使得進行日誌分析和用戶跟蹤變成一件非常困難的事情。

NDC 介紹

NDC(Nested Diagnostic Context)是 Neil Harrison 在名爲《Patterns for Logging Diagnostic Messages》的書中提出的嵌套診斷環境的機制。這種機制的提出,主要爲了減少多線程的系統爲每個客戶單獨記錄日誌的系統開銷。在過去,區分兩個客戶的日誌輸出的常用方法是單獨爲每個客戶機實例化新類別,但該方法會增加類別數量,並增加日誌記錄的管理開銷。Neil Harrison 提出的方法就是把用戶的上下文信息放到嵌套式診斷環境 (NDC) 中。

NDC 爲每一個線程管理一個堆棧。開發人員可以在代碼中合適的位置嵌入簡單的 push 和 pop 方法,用來維護堆棧。通常 push 進堆棧的是可以唯一標示客戶的上下文信息,如 SessionID 或者客戶名稱,IP 地址等。因爲每個客戶請求都會有單獨的 NDC 堆棧,因此日誌系統在輸出的時候會根據每個線程找到對應的堆棧,並在輸出日誌的時候附加上堆棧內的信息。開發人員就可以很容易的在日誌中區分出各個不同客戶所產生的日誌條目。

Log4J 從 1.2 起開始支持 NDC,org.apache.log4j.NDC 聲明如下:

代碼 5. NDC 聲明代碼

    public class NDC {
        // 返回診斷堆棧的內容
        public static String get();
        // 從堆棧的頂端刪除一個元素
        public static String pop();
        //在堆棧頂端加入一個元素
        public static void push(String message);
        //察看這個堆棧最頂層的元素,但不刪除它
        public static String peek()
        // 刪除這個線程的堆棧內容
        public static void remove();
    }

要注意的是,org.apache.log4j.NDC 類中所有的方法都是靜態的。假設 NDC 日誌輸出功能被打開,每一次的日誌請求,Log4J 組件都會把當前線程的整個 NDC 堆棧內容輸出在日誌條目中。這樣的過程不需要開發人員寫過多的代碼,程序員只需要在代碼中合適的地方通過 push 和 pop 方法將正確的信息放到 NDC 的堆棧中,然後通過修改 Log4J 的配置文件,指定用戶標誌信息輸出的位置和格式,而原來 Java 代碼中輸出日誌的代碼不需要任何修改,就能夠輸出帶有用戶標誌信息的日誌。

在前面的 Log4J 使用示例 部分,我們曾經講過 Log4J 配置文件中相應的配置信息,其中 PatternLayout 的 ConversionPattern 用於程序員指定日誌輸出的格式。要使用 NDC 的方式輸出用戶標誌信息,只需要在 PatternLayout 的格式定義 ConversionPattern 中使用 %x,就能在相應的位置上輸出 NDC 存儲的上下文信息。具體的使用方法我們將在後面的 在 Web 應用中添加用戶跟蹤 部分進行介紹。

MDC介紹

MDC 和 NDC 相似,也可以減少多線程的系統爲每個客戶單獨記錄日誌的系統開銷。它同樣是爲每個線程建立一個獨立的存儲空間,開發人員可以根據需要把信息存入其中。不同的是 MDC 使用 Map 的機制來存儲信息,信息以 key/value 對的形式存儲在 Map 中。

Log4J 從 1.3 alpha 版本開始提供對 MDC 的支持,org.apache.log4j.MDC 聲明如下:


代碼 6. MDC 聲明代碼

    public class MDC {
        // 清空map所有的條目。
        public static void clear();
        // 根據key值返回相應的對象
        public static object get(String key);
        //返回所有的key值.
        public static Enumeration getKeys();
        //把key值和關聯的對象,插入map中
        public static void put(String key, Object val),
        //刪除key對應的對象
        public static  remove(String key)
    }

MDC 和 NDC 的使用方法也類似,區別只是在 Log4J配置文件中,在通過 PatternLayout 的 ConversionPattern 來配置日誌的格式的時候,需要使用 %x{key} 來輸出相應的用戶標誌信息對象。

下面,我們通過具體的例子來說明如何在使用 Log4J 的 Web 應用中增加用戶標誌信息,達到進行用戶跟蹤的目的。在開發中,對於使用 NDC 還是 MDC 的機制,要看具體的應用在處理上下文信息的時候,是採用堆棧式的還是 Map 式的方便。下面我們以 NDC 爲例進行說明。

在 Web 應用中添加用戶跟蹤

通常,開發人員會在系統的很多地方設置輸出點,輸出日誌。如果要全面的修改這些輸出點使日誌條目附加上所需的信息,是一件繁重的工作。我們可以利用 Servlet 的 filter 來簡化這項工作。Servlets Filter 是 Servlet 2.3 規範中出現的,它能截取用戶從客戶端提交的請求,並在請求沒有到達真正需要訪問的資源前運行一個指定的類。如果我們在這個類中實現 NDC 或 MDC 的功能,就可以大量簡化日誌修改的工作。下面的清單顯示了實現這種修改所需的三個步驟。在這裏,我們使用的是 NDC,您也可以使用 MDC 實現相同的功能。

在下面的例子中,介紹如何在前面已有的 Log4J 使用示例 的 Web 應用代碼的基礎上,通過爲 Web 應用的 Servlet 增加一個 filter 的方法,將用戶標誌信息在 filter 中壓入/彈出 NDC 堆棧,而不需要修改任何原來的 Java 程序中的輸出日誌的代碼,使用起來非常簡便。

第 1 步:增加一個處理 NDC 堆棧信息的 filter 類

本例通過在 filter 中取得訪問該 Web 應用的客戶機的IP地址,用以唯一地標識客戶。您也可以和實際的應用程序代碼相配合,使用更加人性化的方式來唯一標識客戶,如取得 Session 中存儲的客戶名稱等。


代碼 7. 在 filter 中增加將用戶標誌信息放入 NDC 堆棧

    package is.dsw.base.filter;
    import javax.servlet.Filter;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import org.apache.log4j.NDC;
    public class Log4jNdcFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
        // 獲得客戶的網絡地址
        String address = request.getRemoteAddr();
        // 把網絡地址放入NDC中. 那麼在在layout pattern 中通過使用 %x,就可在每條日之中增加網絡地址的信息.
        NDC.push(address);
        //繼續處理其他的filter鏈.
        chain.doFilter(req, res);
         // 從NDC的堆棧中刪除網絡地址.
        NDC.pop();
        }
    }

第 2 步:修改 Web 應用的 web.xml 文件

我們需要修改 Web 應用的 web.xml 文件,對 filter 進行設置。在 <web-app< 元素下面增加下列的代碼行:

代碼 8. 修改 web.xml 文件,增加 filter 配置

    ……
    <filter>
      <filter-name>NdcFilter</filter-name>
      <filter-class> is.dsw.base.filter.Log4jNdcFilter</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>NdcFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    ……

上述 XML 中的 url-pattern 元素與這個 Web 應用中的所有 URL 匹配。如果只想應用於部分 URL,可以相應地調整模式。

第 3 步:修改 Log4J 的配置文件

需要對 Log4J.properties 文件的配置做一些改變,以便看到 NDC 上下文。在 NDC 簡介部分,我們曾經說過,%x 表示會在每個日誌行上打印當前 NDC 上下文。我們對 Log4J使用示例 中的 Log4J.properties配置文件 進行如下修改,在 PatternLayout 的格式定義 ConversionPattern 中增加 %x, 將 NDC 堆棧中的信息在 %x 指定的位置上進行輸出。如下:

代碼 9. Log4J 配置文件中修改 PatternLayout 的輸出格式

    log4j.appender.A1.layout.ConversionPattern=%d %p %c %x - %m%n

完成以上修改之後,每一條記錄都會把我們在 Filter 中 push 進 NDC 堆棧的內容打印出來。仍然以 Log4J 使用示例 中的三個併發用戶訪問爲例,我們可以得到如下的日誌信息,和前面不使用 NDC 的方式下 打印的日誌信息相比較,可以看到在原來日誌的基礎上增加了客戶機 IP 地址,這樣可以很容易地區分不同的用戶的信息,爲我們進行日誌分析和用戶跟蹤打下了很好的基礎。


圖 2. 應用 NDC 之後,運行 Web 應用後輸出的日誌
Log4J 日誌 2

或許以上的描述已經讓你瞭解到如何利用 NDC 和 MDC 的機制來記錄某個用戶的唯一標示,或者跟蹤其他特定於應用的數據。一旦這些用戶標示的數據記入日誌,則能很容易地利用其他工具將他們抽取出來,如 grep。如果我們自定義一個數據庫的 Appenders,把日誌信息插入數據庫的話,還能很容易的對這些數據進行統計和分析。


結論

Log4J 提供了對 NDC 和 MDC 機制的支持,開發人員可以利用此機制爲每條日誌記錄增加我們需要的內容。本文通過在 servlet 的 filter 中合適的位置應用很少的代碼,就可以修改整個應用的日誌策略。合理的善用本文所提到的機制,可以節省我們的工作量,也簡化程序中日誌的維護。最終,客戶可以獲得更好的網絡應用程序,遇到問題也能從技術支持團隊得到迅速有效的響應。

值得注意的是,我們並不能保證本文所提出的用戶跟蹤方案可以完美地解決所有的問題。如果讀者希望採用本方案,請參考 Apache Log4J 的文檔以瞭解更多的信息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章