細說Java主流日誌工具庫

概述

在項目開發中,爲了跟蹤代碼的運行情況,常常要使用日誌來記錄信息。
在Java世界,有很多的日誌工具庫來實現日誌功能,避免了我們重複造輪子。
我們先來逐一瞭解一下主流日誌工具。

java.util.logging (JUL)

JDK1.4開始,通過java.util.logging提供日誌功能。
它能滿足基本的日誌需要,但是功能沒有Log4j強大,而且使用範圍也沒有Log4j廣泛。

Log4j

Log4j是apache的一個開源項目,創始人Ceki Gulcu。
Log4j應該說是Java領域資格最老,應用最廣的日誌工具。從誕生之日到現在一直廣受業界歡迎。
Log4j是高度可配置的,並可通過在運行時的外部文件配置。它根據記錄的優先級別,並提供機制,以指示記錄信息到許多的目的地,諸如:數據庫,文件,控制檯,UNIX系統日誌等。
Log4j中有三個主要組成部分:

  • loggers: 負責捕獲記錄信息。
  • appenders : 負責發佈日誌信息,以不同的首選目的地。
  • layouts: 負責格式化不同風格的日誌信息。
    官網地址

Logback

Logback是由log4j創始人Ceki Gulcu設計的又一個開源日記組件,目標是替代log4j。
logback當前分成三個模塊:logback-core,logback- classiclogback-access
logback-core是其它兩個模塊的基礎模塊。
logback-classic是log4j的一個 改良版本。此外logback-classic完整實現SLF4J API使你可以很方便地更換成其它日記系統如log4j或JDK14 Logging。
logback-access訪問模塊與Servlet容器集成提供通過Http來訪問日記的功能。 
官網地址

Log4j vs Logback

Logback相比Log4j具有許多好處:
性能提升
logback在log4j基礎上做了優化,使性能提高了近10倍。此外,內存開銷也減少了。
更充足的測試
儘管log4j也做了測試,但是logback的測試更加充分。所以,logback應該更加穩定。
天然支持slf4j
因爲Logback-classic完全實現了slf4j的接口,所以天然支持slf4j。使用slf4j,有利於你切換日誌工具庫,減少工作量。
自動重載配置文件
Logback-classic可以自動重載更新過的配置文件。
自動移除舊日誌
通過配置文件最大數和過期時間,Logback可以控制日誌文件數並自動清除過期的日誌。
更靈活、更精細的配置
Logback在配置中提供更加豐富的功能來幫助你更加精細的去定製你的日誌組件:
<filter>提供比log4j更豐富的過濾條件;
增加<if>, <then><else>這樣的條件控制;
打印異常的調用棧信息
Logback在打印異常時,會打印調用棧的包裝數據。
Logback-access
Logback-access支持Logback-classic的所有特性,並且它可以提供豐富的HTTP-access日誌功能。
總結
以上優點摘自官方推薦理由:Reasons to prefer logback over log4j
由於Logback的作者也是Log4j的作者,所有推薦理由應該比較靠譜。
總之,相比於Log4j,好處多多,你心動了沒?

common-logging

common-logging是apache的一個開源項目。也稱Jakarta Commons Logging,縮寫JCL
common-logging的功能是提供日誌功能的API接口,本身並不提供日誌的具體實現(當然,common-logging內部有一個Simple logger的簡單實現,但是功能很弱,直接忽略),而是在運行時動態的綁定日誌實現組件來工作(如log4j、java.util.loggin)。
官網地址

slf4j

全稱爲Simple Logging Facade for Java,即java簡單日誌門面。
什麼,作者又是Ceki Gulcu!這位大神寫了Log4j、Logback和slf4j,專注日誌組件開發五百年,一直只能超越自己。
類似於Common-Logging,slf4j是對不同日誌框架提供的一個API封裝,可以在部署的時候不修改任何配置即可接入一種日誌實現方案。但是,slf4j在編譯時靜態綁定真正的Log庫。使用SLF4J時,如果你需要使用某一種日誌實現,那麼你必須選擇正確的SLF4J的jar包的集合(各種橋接包)。
官網地址
slf4j工作模型

common-logging vs slf4j

slf4j庫類似於Apache Common-Logging。但是,他在編譯時靜態綁定真正的日誌庫。這點似乎很麻煩,其實也不過是導入橋接jar包而已。
slf4j一大亮點是提供了更方便的日誌記錄方式:
不需要使用logger.isDebugEnabled()來解決日誌因爲字符拼接產生的性能問題。slf4j的方式是使用{}作爲字符串替換符,形式如下:

logger.debug("id: {}, name: {} ", id, name);

總結

綜上所述,使用slf4j + Logback可謂是目前最理想的日誌解決方案了。
接下來,就是如何在項目中實施了。

實施日誌解決方案

使用日誌解決方案基本可分爲三步:

  1. 引入jar包
  2. 配置
  3. 使用API
    常見的各種日誌解決方案的第2步和第3步基本一樣,實施上的差別主要在第1步,也就是使用不同的庫。

引入jar包

這裏首選推薦使用slf4j + logback 的組合。
如果你習慣了common-logging,可以選擇common-logging+log4j。
強烈建議不要直接使用日誌實現組件(logback、log4j、java.util.logging),理由前面也說過,就是無法靈活替換日誌庫。
還有一種情況:你的老項目使用了common-logging,或是直接使用日誌實現組件。如果修改老的代碼,工作量太大,需要兼容處理。在下文,都將看到各種應對方法。
注:據我所知,當前仍沒有方法可以將slf4j橋接到common-logging。如果我孤陋寡聞了,請不吝賜教。

slf4j直接綁定日誌組件

slf4j + logback
添加依賴到pom.xml中即可。
logback-classic-1.0.13.jar 會自動將 slf4j-api-1.7.21.jar 和 logback-core-1.0.13.jar 也添加到你的項目中。

<dependency> 
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.0.13</version>
</dependency>

slf4j + log4j
添加依賴到pom.xml中即可。
slf4j-log4j12-1.7.21.jar 會自動將 slf4j-api-1.7.21.jar 和 log4j-1.2.17.jar 也添加到你的項目中。

<dependency> 
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.21</version>
</dependency>

slf4j + java.util.logging
添加依賴到pom.xml中即可。
slf4j-jdk14-1.7.21.jar 會自動將 slf4j-api-1.7.21.jar 也添加到你的項目中。

<dependency> 
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-jdk14</artifactId>
  <version>1.7.21</version>
</dependency>

slf4j兼容非slf4j日誌組件

在介紹解決方案前,先提一個概念——橋接
什麼是橋接呢
假如你正在開發應用程序所調用的組件當中已經使用了common-logging,這時你需要 jcl-over-slf4j.jar把日誌信息輸出重定向到 slf4j-api,slf4j-api再去調用slf4j實際依賴的日誌組件。這個過程稱爲橋接。
下圖是官方的slf4j橋接策略圖:
slf4j橋接策略
從圖中應該可以看出,無論你的老項目中使用的是common-logging或是直接使用log4j、java.util.logging,都可以使用對應的橋接jar包來解決兼容問題。

slf4j兼容common-logging

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.12</version>
</dependency>

slf4j兼容log4j

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.12</version>
</dependency>

slf4j兼容java.util.logging

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.12</version>
</dependency>

spring 集成 slf4j

做java web開發,基本離不開spring框架。很遺憾,spring使用的日誌解決方案是common-logging + log4j。
所以,你需要一個橋接jar包:logback-ext-spring

<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.1.3</version>
</dependency>
<dependency>
  <groupId>org.logback-extensions</groupId>
  <artifactId>logback-ext-spring</artifactId>
  <version>0.1.2</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.12</version>
</dependency>

common-logging綁定日誌組件

common-logging + log4j
添加依賴到pom.xml中即可。

<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.2</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>

配置

日誌配置文件大同小異,需要注意的是:logback.xml和log4j.xml都需要放在classpath路徑下

完整的logback.xml參考示例

在下面的配置文件中,我爲自己的項目代碼(根目錄:org.zp.notes.spring)設置了五種等級:
TRACE、DEBUG、INFO、WARN、ERROR,優先級依次從低到高。
因爲關注spring框架本身的一些信息,我增加了專門打印spring WARN及以上等級的日誌。

<?xml version="1.0" encoding="UTF-8" ?>

<!-- logback中一共有5種有效級別,分別是TRACE、DEBUG、INFO、WARN、ERROR,優先級依次從低到高 -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">

  <property name="DIR_NAME" value="spring-helloworld"/>

  <!-- 將記錄日誌打印到控制檯 -->
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <!-- RollingFileAppender begin -->
  <appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/all.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>30MB</maxFileSize>
    </triggeringPolicy>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>ERROR</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/warn.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>WARN</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>INFO</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/debug.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>DEBUG</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="TRACE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/trace.%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>TRACE</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>

  <appender name="SPRING" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 根據時間來制定滾動策略 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>${user.dir}/logs/${DIR_NAME}/springframework.%d{yyyy-MM-dd}.log
      </fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <!-- 根據文件大小來制定滾動策略 -->
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
      <maxFileSize>10MB</maxFileSize>
    </triggeringPolicy>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] [%-5p] %c{36}.%M - %m%n</pattern>
    </encoder>
  </appender>
  <!-- RollingFileAppender end -->

  <!-- logger begin -->
  <!-- 本項目的日誌記錄,分級打印 -->
  <logger name="org.zp.notes.spring" level="TRACE" additivity="false">
    <appender-ref ref="STDOUT"/>
    <appender-ref ref="ERROR"/>
    <appender-ref ref="WARN"/>
    <appender-ref ref="INFO"/>
    <appender-ref ref="DEBUG"/>
    <appender-ref ref="TRACE"/>
  </logger>

  <!-- SPRING框架日誌 -->
  <logger name="org.springframework" level="WARN" additivity="false">
    <appender-ref ref="SPRING"/>
  </logger>

  <root level="TRACE">
    <appender-ref ref="ALL"/>
  </root>
  <!-- logger end -->

</configuration>

完整的log4j.xml參考示例

log4j的配置文件一般有xml格式或properties格式。這裏爲了和logback.xml做個對比,就不介紹properties了,其實也沒太大差別。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'>

  <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
             value="%d{yyyy-MM-dd HH:mm:ss,SSS\} [%-5p] [%t] %c{36\}.%M - %m%n"/>
    </layout>

    <!--過濾器設置輸出的級別-->
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
      <param name="levelMin" value="debug"/>
      <param name="levelMax" value="fatal"/>
      <param name="AcceptOnMatch" value="true"/>
    </filter>
  </appender>


  <appender name="ALL" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="File" value="${user.dir}/logs/spring-common/jcl/all"/>
    <param name="Append" value="true"/>
    <!-- 每天重新生成日誌文件 -->
    <param name="DatePattern" value="'-'yyyy-MM-dd'.log'"/>
    <!-- 每小時重新生成日誌文件 -->
    <!--<param name="DatePattern" value="'-'yyyy-MM-dd-HH'.log'"/>-->
    <layout class="org.apache.log4j.PatternLayout">
      <param name="ConversionPattern"
             value="%d{yyyy-MM-dd HH:mm:ss,SSS\} [%-5p] [%t] %c{36\}.%M - %m%n"/>
    </layout>
  </appender>

  <!-- 指定logger的設置,additivity指示是否遵循缺省的繼承機制-->
  <logger name="org.zp.notes.spring" additivity="false">
    <level value="error"/>
    <appender-ref ref="STDOUT"/>
    <appender-ref ref="ALL"/>
  </logger>

  <!-- 根logger的設置-->
  <root>
    <level value="warn"/>
    <appender-ref ref="STDOUT"/>
  </root>
</log4j:configuration>

logback配置參數說明

logback基本兼容log4j的配置,並提供更多的功能。
這裏奉獻一張本人整理的logback配置思維導圖,高清無碼。
logback配置

使用API

slf4j用法

使用slf4j的API很簡單。使用LoggerFactory初始化一個Logger實例,然後調用Logger對應的打印等級函數就行了。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class App {
    private static final Logger log = LoggerFactory.getLogger(App.class);
    public static void main(String[] args) {
        String msg = "print log, current level: {}";
        log.trace(msg, "trace");
        log.debug(msg, "debug");
        log.info(msg, "info");
        log.warn(msg, "warn");
        log.error(msg, "error");
    }
}

common-logging用法

common-logging用法和slf4j幾乎一樣,但是支持的打印等級多了一個更高級別的:fatal
此外,common-logging不支持{}替換參數,你只能選擇拼接字符串這種方式了。

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class JclTest {
    private static final Log log = LogFactory.getLog(JclTest.class);

    public static void main(String[] args) {
        String msg = "print log, current level: ";
        log.trace(msg + "trace");
        log.debug(msg + "debug");
        log.info(msg + "info");
        log.warn(msg + "warn");
        log.error(msg + "error");
        log.fatal(msg + "fatal");
    }
}

參考

slf4官方文檔
logback官方文檔
log4j官方文檔
commons-logging官方文檔
http://blog.csdn.net/yycdaizi/article/details/8276265

發佈了182 篇原創文章 · 獲贊 32 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章