星期一, 5月 26, 2014

[Java] 快快樂樂學會log4j

最近弄log4j的同事離職,趁記憶還在的時候,
自已親身再run一遍,本文大多取自Google來的各文章的筆記。

log4j使用版本:1.2.x


log4j組成

Logger - 由編程人員在程式中使用,進行 logging 的元件
Appender - 負責將 log message 輸出到各種裝置上
Layout - 決定 log message 的格式

Logger的等級

定義在 org.apache.log4j.Level 這個類別,找到一張還不錯的圖,可以清楚的劃分出每個LEVEL所含蓋的層級



REFERENCE: http://javapracticalinfo.blogspot.tw/2012/11/log4j.html

範例:
假設一個程式的log被設定為 WARN => 則印出WARN,ERROR,FATAL
Logger之間以名稱區分,所以在程式中任何地方,呼叫 Logger.getLogger(),並傳入同一個 Logger名稱,則會得到同一個Logger的reference。

Appender



一個logger會透過appender將訊息輸出至指定的裝置上,因此一個logger可以攡有多個appender
  • org.apache.log4j.ConsoleAppender(輸出在控制台)
  • org.apache.log4j.FileAppender(輸出到文件檔案)
  • org.apache.log4j.DailyRollingFileAppender(每天產生一個文件檔案)
  • org.apache.log4j.RollingFileAppender(文件大小到達指定尺寸的时候產生一個新的文件)
  • org.apache.log4j.WriterAppender(將log信息以串流格式發送到任意指定的地方)
  • org.apache.log4j.JdbcAppender (將log信息寫入資料庫)

Layout

logger可透過layout將訊息顯示成不同的佈局,一般常用為PatternLayout


  • org.apache.log4j.HTMLLayout(以HTML表格形式佈局
  • org.apache.log4j.PatternLayout(可以任意地指定佈局
  • org.apache.log4j.SimpleLayout(包含log信息的級別和信息字符串)
  • org.apache.log4j.TTCCLayout(包含log產生的时間、線程、類别等等信息)


PatternLayout 的 格式字元列表如下

# %c 輸出日誌訊息所屬的類別的全名
# %d 輸出日誌時間點的日期或時間,指定格式的方式:%d{yyy-MM-dd HH:mm:ss }。
# %l 輸出日誌事件的發生位置,即輸出日誌訊息的語句處於它所在的類別的第幾行。
# %m 輸出訊息,如log(message)中的message。
# %n 輸出一個列尾符號。
# %p 輸出優先階層,即DEBUG,INFO,WARN,ERROR,FATAL。如果是調用debug()輸出的,則為DEBUG,依此類推。
# %r 輸出自應用啟動到輸出該日誌訊息所耗費的毫秒數。
# %t 輸出產生該日誌事件的線程名。
# %r 輸出自應用啟動到輸出該日誌訊息所耗費的毫秒數。
# %f 輸出日誌訊息所屬的類別的類別名。

開始使用

接下來我們來記錄一下如何在你的web應用程式啟用log4j,包含log4j jar與log4j.xml的設定


配置log4j.jar

  1. 一個專案的話,你可以將jar放在 WEB-INF/lib。
  2. 多個專案的話,通常只要放置tomcat安裝目錄之下的lib即可。
ex: C:\Program Files\Apache Software Foundation\Tomcat 7.0\lib

配置log4j.xml

一般log4j.xml如下所示
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/xml/doc-files/log4j.dtd">
<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">

 <appender name="RestfulRollingFileAppender" class="org.apache.log4j.RollingFileAppender">
  <!-- Only FATAL>ERROR>WARN>INFO>DEBUG will be record -->
  <param name="Threshold" value="DEBUG" />
  <!--<param name="File" value="/tmp/COSA-debug.log" /> -->
  <param name="MaxFileSize" value="1MB" />
  <param name="MaxBackupIndex" value="5" />
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value='%d{yyyy.MM.dd HH:mm:ss.SSS} %5p (%c.%M) - %m%n' />
  </layout>
 </appender>

 <appender name="RestfulConsoleAppender" class="org.apache.log4j.ConsoleAppender">
  <layout class="org.apache.log4j.PatternLayout">
   <param name="ConversionPattern" value='%d{yyyy.MM.dd HH:mm:ss.SSS} %5p (%c.%M) - %m%n' />
  </layout>
 </appender>

 <logger name="test.JVMConfigServices" additivity="false">
  <level value="DEBUG" />
  <appender-ref ref="RestfulConsoleAppender" />
 </logger>

 <root>
  <!-- Above ERROR level logs will be recorded -->
  <level value="ERROR" />
  <appender-ref ref="RestfulConsoleAppender" />
 </root>

</log4j:configuration>

自行透過DOMConfigurator載入log4j.xml

透過DOMConfigurator.configure來載入log4j xml的組態檔,不過此方法只適合demo測試,
因為當web應用程式啟動時,我們不需要在每個要使用log4j的地方載入log4j組態檔


package test;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

public class HelloLog4jServices{
 
     static Logger logger = Logger.getLogger(HelloLog4j.class);
  
    public static void main(String[] args)
    {
     //DOMConfigurator is used to configure logger from xml configuration file
        DOMConfigurator.configure("D:\\資策會\\workspace\\JerseySample\\WebContent\\WEB-INF\\log4j.xml");
        
        //Log in console in and log file
        logger.debug("Log4j appender configuration is successful !!");
    }
}


自動載入log4j.xml (Tomcat官方設定)


一般來說如果要自動載入log4j.xml組態檔的話,
你可以將log4j.xml放在WEB-INF/classes之下,如下圖所示。


把它丟在src目錄下也可以。

另外找到一些人有談到在log4j.xml裡面設定debug="true"
<log4j:configuration debug="false" xmlns:log4j="http://jakarta.apache.org/log4j/">

透過Serverlet載入log4j.xml (自行配置log4j.xml的路徑)

如果想要在web應用程式啟動時,載入log4j組態檔的話,
需要使用extends HttpServelt來實作。

Step1: 實作Log4jInit 
package test;

import javax.servlet.http.HttpServlet;
import org.apache.log4j.xml.DOMConfigurator;

public class Log4jInit extends HttpServlet {
 
 public void init() {
  
  String prefix = getServletContext().getRealPath("/");
  String file = getInitParameter("log4jConfigLocation");
  if (file != null) {
   String configLocation = prefix + file;
   DOMConfigurator.configure(configLocation);
   System.out.println("Log4J Logging started: " + configLocation);
  } else {
   System.out.println("Log4J Is not configured for your Application: "
     + prefix + file);
   
  }
 }
}

Step2: 修改 web.xml

將Log4jInit Servlet加入你的web.xml組態檔,log4jConfigLocation參數請指定log4j.xml組態檔設定路徑
  <servlet> 
      <servlet-name>log4jInit</servlet-name>
      <servlet-class>test.Log4jInit</servlet-class> 
      <init-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>WEB-INF/log4j.xml</param-value>
      </init-param>
      <load-on-startup>1</load-on-startup>
 </servlet>

Step3: 重新啟動TOMCAT


在Runtime的時候變更log4j.xml組態設定

當我們在除錯時,會自行加入不同的package來做log4j的監控,
因此這時候我們並不會想要重新啟動機器,
這時候該怎麼處理呢?以下是擷至StackFlow的討論:d。
一般來說,查到的資料大多使用第一個方法File Watchdog,


  • File Watchdog


Log4j is able to watch the log4j.xml file for configuration changes. If you change the log4j file, log4j will automatically refresh the log levels according to your changes. See the documentation of org.apache.log4j.xml.DOMConfigurator.configureAndWatch(String,long) for details. The default wait time between checks is 60 seconds. These changes would be persistent, since you directly change the configuration file on the filesystem. All you need to do is to invoke DOMConfigurator.configureAndWatch() once.

Caution: configureAndWatch method is unsafe for use in J2EE environments due to a Thread leak

有找到二個範例程式,使用ServletContextListener與LifecycleListener來解決這個問題,
也可以取代先前使用的Serverlet的方法

https://gist.github.com/iambigd/f10ad403eaa8a06c2c64 順便將作者的程式丟到gist了



  • JMX


Another way to set the log level (or reconfiguring in general) log4j is by using JMX. Log4j registers its loggers as JMX MBeans. Using the application servers MBeanServer consoles (or JDK's jconsole.exe) you can reconfigure each individual loggers. These changes are not persistent and would be reset to the config as set in the configuration file after you restart your application (server).


  • Self-Made


As described by Aaron, you can set the log level programmatically. You can implement it in your application in the way you would like it to happen. For example, you could have a GUI where the user or admin changes the log level and then call the setLevel() methods on the logger. Whether you persist the settings somewhere or not is up to you.


其他框架的整合


Spring設定Log4j組態檔更新載入

如果你使用Spring框架的話,就更輕鬆了,只要把web.xml加入以下的參數設定即可,當runtime時可以自動reload

<!-- Spring Log4j Config Setting -->
 <context-param>
  <param-name>log4jConfigLocation</param-name>
  <param-value>/WEB-INF/Log4j/log4j.xml</param-value>
 </context-param>
 <context-param>
  <param-name>log4jRefreshInterval</param-name>
  <param-value>60000</param-value>
 </context-param>
 <listener>
  <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
 </listener>

相關錯誤Exception

如果沒初始化成功,就使用logger的話噴以下的錯誤
log4j:WARN No appenders could be found for logger (test.JVMConfigServices).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

參考資料

How to configure log4j using xml configuration
How to configure log4j using properties file
log4j基本觀念
dynamically-changing-log4j-log-level
Automatically reload log4j configuration in tomcat
http://howtodoinjava.com/2013/05/28/how-to-reload-log4j-levels-on-runtime/
http://howtodoinjava.com/2012/10/10/auto-reload-of-configuration-when-any-change-happen/
http://janvanbesien.blogspot.tw/2010/02/reload-log4j-configuration-in-tomcat.html
http://stackoverflow.com/questions/4598702/dynamically-changing-log4j-log-level

沒有留言:

張貼留言

留個話吧:)