LogBack入門實踐

1、簡介

LogBack是一個日誌框架,它是Log4j做者Ceki的又一個日誌組件。html

LogBack,Slf4j,Log4j之間的關係java

slf4j是The Simple Logging Facade for Java的簡稱,是一個簡單日誌門面抽象框架,它自己只提供了日誌Facade API和一個簡單的日誌類實現,通常常配合Log4jLogBackjava.util.logging使用。Slf4j做爲應用層的Log接入時,程序能夠根據實際應用場景動態調整底層的日誌實現框架(Log4j/LogBack/JdkLog...);shell

LogBack和Log4j都是開源日記工具庫,LogBack是Log4j的改良版本,比Log4j擁有更多的特性,同時也帶來很大性能提高。數據庫

LogBack官方建議配合Slf4j使用,這樣能夠靈活地替換底層日誌框架。api

LogBack的結構
LogBack分爲3個組件,logback-core, logback-classic 和 logback-access。
其中logback-core提供了LogBack的核心功能,是另外兩個組件的基礎。
logback-classic則實現了Slf4j的API,因此當想配合Slf4j使用時,則須要引入這個包。
logback-access是爲了集成Servlet環境而準備的,可提供HTTP-access的日誌接口。服務器

Log的行爲級別:app

OFF
FATAL
ERROR
WARN
INFO
DEBUG
ALL
從下向上,當選擇了其中一個級別,則該級別向下的行爲是不會被打印出來。
舉個例子,當選擇了INFO級別,則INFO如下的行爲則不會被打印出來。框架

2、slf4j與logback結合使用原理

咱們從java代碼最簡單的獲取logger開始ide

Logger logger = LoggerFactory.getLogger(xxx.class.getName());

LoggerFactory是slf4j的日誌工廠,獲取logger方法就來自這裏。工具

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

這個方法裏面有分爲兩個過程。第一個過程是獲取ILoggerFactory,就是真正的日誌工廠。第二個過程就是從真正的日誌工廠中獲取logger。
第一個過程又分爲三個部分。

第一個部分加載org/slf4j/impl/StaticLoggerBinder.class文件

paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);//STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"

第二部分隨機選取一個StaticLoggerBinder.class來建立一個單例
當項目中存在多個StaticLoggerBinder.class文件時,運行項目會出現如下日誌:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/jiangmitiao/.m2/repository/ch/qos/logback/logback-classic/1.1.3/logback-classic-1.1.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/jiangmitiao/.m2/repository/org/slf4j/slf4j-log4j12/1.7.12/slf4j-log4j12-1.7.12.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

最後會隨機選擇一個StaticLoggerBinder.class來建立一個單例

StaticLoggerBinder.getSingleton()

第三部分返回一個ILoggerFactory實例

StaticLoggerBinder.getSingleton().getLoggerFactory();

因此slf4j與其餘實際的日誌框架的集成jar包中,都會含有這樣的一個org/slf4j/impl/StaticLoggerBinder.class類文件,而且提供一個ILoggerFactory的實現。

第二個過程就是每個和slf4j集成的日誌框架中實現ILoggerFactory方法getLogger()的實例所作的事了。

3、slf4j與logback結合使用實踐

第一步引入jar包
slf4j-api
logback-core
logback-classic(含有對slf4j的集成包)

<!-- slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.12</version>
</dependency>
<!-- logback -->
<dependency> 
    <groupId>ch.qos.logback</groupId> 
    <artifactId>logback-core</artifactId> 
    <version>1.1.3</version> 
</dependency> 
<dependency> 
    <groupId>ch.qos.logback</groupId> 
    <artifactId>logback-classic</artifactId> 
    <version>1.1.3</version> 
</dependency>

第二步編寫簡單的logback配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>
  <root level="DEBUG">          
    <appender-ref ref="STDOUT" />
  </root>  
</configuration>

文件位置位於src/main/resources下,名字默認爲logback.xml
固然,logback也支持groovy格式的配置文件,若是你會用那更好。
接下來,本身隨便寫一個類調用一下logger

package log.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * @author jiangmitiao
 * @date 2016/3/24
 * @description TODO
 */
public class Foo {
    public static void doIt(){
        Logger logger = LoggerFactory.getLogger(Foo.class.getName());
        logger.debug("let`s do it");
    }
}

package log.test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * @author jiangmitiao
 * @date 2016/3/24
 * @description TODO
 */
public class MyApp1 {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(MyApp1.class.getName());
        logger.info("before");
        Foo.doIt();
        logger.info("after");

        try {
            int i = 10 / 0;
        } catch (Exception e) {
            logger.error("errorTest",e);
        }
    }
}

最後的結果是:

16:22:13.459 [main] INFO  log.test.MyApp1 - before
16:22:13.463 [main] DEBUG log.test.Foo - let`s do it
16:22:13.463 [main] INFO  log.test.MyApp1 - after
16:22:13.466 [main] ERROR log.test.MyApp1 - errorTest
java.lang.ArithmeticException: / by zero
    at log.test.MyApp1.main(MyApp1.java:19) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.6.0_25]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[na:1.6.0_25]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[na:1.6.0_25]
    at java.lang.reflect.Method.invoke(Method.java:597) ~[na:1.6.0_25]
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140) [idea_rt.jar:na]

這麼簡單的配置固然是沒有用的,下面這個就可以說明logback配置文件的編寫規則了。

<!-- scan 是否認期掃描xml文件, scanPeriod是說掃描週期是30秒-->
<configuration scan="true" scanPeriod="30 seconds" debug="false" packagingData="true">
    <!-- 項目名稱 -->
    <contextName>myApp1 contextName</contextName>
    <!-- 屬性 -->
    <property name="USER_HOME" value="./log"/>

    <!-- Insert the current time formatted as "yyyyMMdd'T'HHmmss" under
       the key "bySecond" into the logger context. This value will be
       available to all subsequent configuration elements. -->
    <timestamp key="bySecond" datePattern="yyyyMMdd" timeReference="contextBirth"/>
    
    <!-- appender很重要,一個配置文件會有多個appender -->
    <!-- ConsoleApperder意思是從console中打印出來 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- 過濾器,一個appender能夠有多個 -->
        <!-- 閾值過濾,就是log行爲級別過濾,debug及debug以上的信息會被打印出來 -->
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>

        <!-- encoders are assigned the type
             ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <!-- encoder編碼規則 -->
        <encoder>
            <!--<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>-->
            <!--<pattern>%d %contextName %msg%n</pattern>-->
            <!-- pattern模式 %d時間 %thread 線程名 %level行爲級別 %logger logger名稱 %method 方法名稱 %message 調用方法的入參消息 -->
            <pattern>%-4d [%thread] %highlight%-5level %cyan%logger.%-10method - %message%n</pattern>
        </encoder>
    </appender>
    
    <!-- FileAppender 輸出到文件 -->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <!-- 文件存放位置 %{xxx} 就是以前定義的屬性xxx -->
        <file>${USER_HOME}/myApp1log-${bySecond}.log</file>
        
        <encoder>
            <!-- %date和%d是一個意思 %file是所在文件 %line是所在行 -->
            <pattern>%date %level [%thread] %logger{30} [%file:%line] %msg%n</pattern>
        </encoder>
    </appender>
    
    <!-- 輸出到HTML格式的文件 -->
    <appender name="HTMLFILE" class="ch.qos.logback.core.FileAppender">
        <!-- 過濾器,這個過濾器是行爲過濾器,直接過濾掉了除debug外全部的行爲信息 -->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debug</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>

        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <!-- HTML輸出格式 能夠和上邊差很少 -->
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%relative%thread%mdc%level%logger%msg</pattern>
            </layout>
        </encoder>
        <file>${USER_HOME}/test.html</file>
    </appender>

    <!-- 滾動日誌文件,這個比較經常使用 -->
    <appender name="ROLLINGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 當project等於true的時候file就不會起效果-->
        <prudent>true</prudent>
        <!--<file>${USER_HOME}/logFile.log</file>-->
        <!-- 按天新建log日誌 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- daily rollover -->
            <fileNamePattern>${USER_HOME}/logFile.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
            <!-- 保留30天的歷史日誌 -->
            <maxHistory>30</maxHistory>
            
            <!-- 基於大小和時間,這個能夠有,能夠沒有 -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- or whenever the file size reaches 100MB -->
                <!-- 當一個日誌大小大於10KB,則換一個新的日誌。日誌名的%i從0開始,自動遞增 -->
                <maxFileSize>10KB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>

        <encoder>
            <!-- %ex就是指拋出的異常,full是顯示所有,若是在{}中寫入數字,則表示展現多少行 -->
            <pattern>%-4date [%thread] %-5level %logger{35} - %msg%n%ex{full, DISPLAY_EX_EVAL}</pattern>
        </encoder>
    </appender>

    <!-- 重點來了,上邊都是appender輸出源。這裏開始就是looger了 -->
    <!-- name意思是這個logger管的哪一片,像下面這個管的就是log/test包下的全部文件 level是隻展現什麼行爲信息級別以上的,相似閾值過濾器 additivity表示是否再拋出事件,就是說若是有一個logger的name是log,若是這個屬性是true,另外一個logger就會在這個logger處理完後接着繼續處理 -->
    <logger name="log.test" level="INFO" additivity="false">
        <!-- 鏈接輸出源,也就是上邊那幾個輸出源 ,你能夠隨便選幾個appender-->
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ROLLINGFILE"/>
        <appender-ref ref="HTMLFILE"/>
    </logger>
    <!-- 這個logger詳細到了類 -->
    <logger name="log.test.Foo" level="debug" additivity="false">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="ROLLINGFILE"/>
        <appender-ref ref="HTMLFILE"/>
    </logger>

    <!-- Strictly speaking, the level attribute is not necessary since -->
    <!-- the level of the root level is set to DEBUG by default.       -->
    <!-- 這就是上邊logger沒有管到的狀況下 root默認接管全部logger -->
    <root level="debug">
        <appender-ref ref="STDOUT"/>
    </root>
</configuration>

4、過濾器的一些疑問

Logback的過濾器基於三值邏輯,容許把它們組裝或成鏈,從而組成任意的複合過濾策略。過濾器很大程度上受到Linux的iptables啓發。這裏的所謂三值邏輯是說,過濾器的返回值只能是ACCEPT、DENY和NEUTRAL的其中一個。
若是返回DENY,那麼記錄事件當即被拋棄,再也不通過剩餘過濾器;
若是返回NEUTRAL,那麼有序列表裏的下一個過濾器會接着處理記錄事件;
若是返回ACCEPT,那麼記錄事件被當即處理,再也不通過剩餘過濾器。
寫一個簡單的過濾器你們就明白了。

package log.test;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.filter.Filter;
import ch.qos.logback.core.spi.FilterReply;

public class SampleFilter extends Filter<ILoggingEvent> {
  @Override
  public FilterReply decide(ILoggingEvent event) {    
    if (event.getMessage().contains("let")) {
      return FilterReply.ACCEPT;
    } else {
      return FilterReply.DENY;
    }
  }
}

能夠選擇任意幾個輸出源加入這個filter

<filter class="log.test.SampleFilter" />

最後的結果是,加入該filter的輸出源只能輸出Foo.doIt()中的日誌了。

5、總結

LogBack配置比較簡單,官網手冊也是比較容易看懂的。除上邊幾種輸出源以外,logback還支持輸出到遠程套接字服務器、 MySQL、 PostreSQL、Oracle和其餘數據庫、 JMS和遠程UNIX Syslog守護進程等等。
第一次學習log方面的知識,若有錯誤,請不吝賜教。
相關資源:
官方手冊
LogBack簡易教程
實際的xml配置
Logback淺析
logback 配置詳解(一)

更多文章:http://blog.gavinzh.com

相關文章
相關標籤/搜索