天天看點

Spring Boot 應用系列 5 -- Spring Boot 2 整合logback

上一篇我們梳理了Spring Boot 2 整合log4j2的配置過程,其中講到了Spring Boot 2原裝适配logback,并且在非異步環境下logback和log4j2的性能差别不大,是以對于那些日志量不算太高的項目來說,選擇logback更簡單友善。

1. pom.xml

pom.xml不需要添加任何依賴。

2. logback的配置檔案

系統啟動時,logback按照下列順序加載第一個找到的配置檔案:

(1) classpath: logback-test.xml

(2) classpath: logback.groovy

(3) classpath: logback.xml

(4) 查詢com.qos.logback.classic.spi.Configurator接口的實作

(5) 如果以上均未找到,則加載預設配置。

但是與Spring Boot內建時,上面的配置檔案加載較早,Spring Boot為logback提供了很多擴充(比如<springProfile>節點),這些擴充的加載要晚于上面的配置檔案,是以Spring Boot建議使用logback-spring.xml作為配置檔案。

3. 配置目标

類似上一篇整合log4j2時的情況,我們希望達成以下目标:

(1) 所有級别的日志均可以通過控制台列印;

(2) 日志的存儲目錄格式為“/yyyy-MM/dd/”(“年-月/日/”),日志檔案名稱包含小時;

(3) error級别的日志存儲在“/yyyy-MM/dd/app-error-{HH}.log”中,其中HH是日志發生的小時;

(4) 其他級别的日志存儲在“/yyyy-MM/dd/app-other-{HH}.log”中;

(5) 所有級别的日志同時以html格式存儲備份;

(6) 所有日志檔案按照小時歸檔,一個小時一套檔案(三個具體檔案error,other);

(7) 設定日志檔案的size上限,如果某一小時出現的日志特别多,超過size limit之後自動生成帶數字字尾的檔案。

4. logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <property name="Log_Home" value="logs" />
    <property name="Log_Pattern" value="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger - %msg%n" />

    <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>${Log_Pattern}</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <appender name="RollingFile_Error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <prudent>true</prudent>
        <encoder>
            <pattern>${Log_Pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${Log_Home}/%d{yyyy-MM}/%d{dd}/app-error-%d{HH-mm}.%i.log</FileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>10080</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="RollingFile_Other" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <prudent>true</prudent>
        <encoder>
            <pattern>${Log_Pattern}</pattern>
            <charset>UTF-8</charset>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>DENY</onMatch>
            <onMismatch>NEUTRAL</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${Log_Home}/%d{yyyy-MM}/%d{dd}/app-other-%d{HH-mm}.%i.log</FileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>10080</maxHistory>
        </rollingPolicy>
    </appender>

    <appender name="RollingFile_HTML" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <prudent>true</prudent>
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%relative%thread%mdc%level%logger%msg</pattern>
            </layout>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <FileNamePattern>${Log_Home}/%d{yyyy-MM}/%d{dd}/app-%d{HH-mm}.%i.html</FileNamePattern>
            <maxFileSize>10MB</maxFileSize>
            <maxHistory>10080</maxHistory>
        </rollingPolicy>
    </appender>

    <root level="info">
        <appender-ref ref="Console" />
        <appender-ref ref="RollingFile_Error" />
        <appender-ref ref="RollingFile_Other" />
        <appender-ref ref="RollingFile_HTML" />
    </root>
</configuration>      

(1) logback的日志等級跟slf4j保持一緻包括TRACE, DEBUG, INFO, WARN 和 ERROR;

(2) <configuration>節點設定debug="true"屬性會在系統啟動時列印logback加載的狀态資訊,如果logback加載過程中出現錯誤也會被列印出來,另外一種起同樣作用的配置為在<configuration>節點中配置:

<statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />      

(3) <property>節點屬于xml級别的全局配置變量;

(4) <appender>節點中的<encoder>節點是日志事件的輸出格式化配置,其中<Pattern>屬性存儲格式化規則,其中的符号含義分别為:

[%d{yyyy-MM-dd HH:mm:ss.SSS}]: 時間格式;

[%thread]: 輸出日志的線程;

%-5level: 日志級别;

%logger: 日志調用者;

%msg: 日志内容;

%n: 換行符。

(5) <prudent>節點預設值為false,如果設定為true可確定在多線程下對同一日志檔案的操作的線程安全性,但是打開之後寫日志的性能會下降大約三倍。參考官網描述:

In prudent mode, FileAppender will safely write to the specified file, even in the presence of other FileAppender instances running in different JVMs, potentially running on different hosts. The default value for prudent mode is false.
Prudent mode can be used in conjunction with RollingFileAppender although some restrictions apply.

Prudent mode implies that append property is automatically set to true.

Prudent more relies on exclusive file locks. Experiments show that file locks approximately triple (x3) the cost of writing a logging event. On an "average" PC writing to a file located on a local hard disk, when prudent mode is off, it takes about 10 microseconds to write a single logging event. When prudent mode is on, it takes approximately 30 microseconds to output a single logging event. This translates to logging throughput of 100'000 events per second when prudent mode is off and approximately 33'000 events per second in prudent mode.      

此外,prudent功能還有兩個限制:

a. 日志檔案不能被壓縮;

b. 不能在<FileAppender>節點上設定file屬性。

(6) <filter>節點用來配置過濾器,本例針對兩個appender分别配置了兩個<filter>,第一個filter隻列印ERROR級别的日志,第二個filter隻列印非ERROR級别的日志。此外logback還提供多種過濾器詳見logback官網。

關于onMatch和onMismatch,請看logback源碼:

1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.filter;
15  
16  import ch.qos.logback.core.spi.FilterReply;
17  
18  public abstract class AbstractMatcherFilter<E> extends Filter<E> {
19  
20      protected FilterReply onMatch = FilterReply.NEUTRAL;
21      protected FilterReply onMismatch = FilterReply.NEUTRAL;
22  
23      final public void setOnMatch(FilterReply reply) {
24          this.onMatch = reply;
25      }
26  
27      final public void setOnMismatch(FilterReply reply) {
28          this.onMismatch = reply;
29      }
30  
31      final public FilterReply getOnMatch() {
32          return onMatch;
33      }
34  
35      final public FilterReply getOnMismatch() {
36          return onMismatch;
37      }
38  }      
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core.spi;
15  
16  /**
17   *
18   * This enum represents the possible replies that a filtering component
19   * in logback can return. It is used by implementations of both 
20   * {@link ch.qos.logback.core.filter.Filter Filter} and
21   * ch.qos.logback.classic.turbo.TurboFilter abstract classes.
22   * 
23   * Based on the order that the FilterReply values are declared,
24   * FilterReply.ACCEPT.compareTo(FilterReply.DENY) will return 
25   * a positive value.
26   *
27   * @author S&eacute;bastien Pennec
28   */
29  public enum FilterReply {
30      DENY, NEUTRAL, ACCEPT;
31  }      

簡單來講onMatch和onMismatch的取值都有三個分别是DENY(拒絕),NEUTRAL(進入下一個filter),ACCEPT(接受,跳過所有剩下的filter)。

(7) rollingPolicy節點配置日志檔案的存檔政策,本例後三個appender都是每分鐘(不超過10MB)存儲一個檔案;

注意如果存檔日志檔案數量超過maxHistory值,最老的日子檔案會被删除。

(8) 最後說一下<encoder>裡面的<layout>,它是logback中的一個元件負責将日志事件轉成字元串,先看一下它的接口定義:

public interface Layout<E> extends ContextAware, LifeCycle {
  String doLayout(E event);
  String getFileHeader();
  String getPresentationHeader();
  String getFileFooter();
  String getPresentationFooter();
  String getContentType();
}      

繼承該接口你就可以自定義自己的layout:

package chapters.layouts;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.LayoutBase;
public class MySampleLayout extends LayoutBase<ILoggingEvent> {
  public String doLayout(ILoggingEvent event) {
    StringBuffer sbuf = new StringBuffer(128);
    sbuf.append(event.getTimeStamp() - event.getLoggingContextVO.getBirthTime());
    sbuf.append(" ");
    sbuf.append(event.getLevel());
    sbuf.append(" [");
    sbuf.append(event.getThreadName());
    sbuf.append("] ");
    sbuf.append(event.getLoggerName();
    sbuf.append(" - ");
    sbuf.append(event.getFormattedMessage());
    sbuf.append(CoreConstants.LINE_SEP);
    return sbuf.toString();
  }
}      

然後使用你自定義的layout:

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
      <layout class="chapters.layouts.MySampleLayout" />
    </encoder>
  </appender>
  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>      

本例使用了logback自帶的HTMLLayout。

Spring Boot 應用系列 5 -- Spring Boot 2 整合logback

5. logback不同環境不同配置

有兩種實作方式:

(1) 按環境将配置檔案拆分為logback-production.xml和logback-dev.xml等,然後在application.properties裡面配置:

logging.config: classpath:logback-dev.xml      

(2) 第二種是使用<springProfile>标簽,在logback-spring.xml中配置:

<!-- develop -->
<springProfile name="dev">
    <root level="DEBUG">
        <appender-ref ref="CONSOLE" />
    </root>
</springProfile>
    
<!-- production -->
<springProfile name="prod">
    <root level="INFO">
        <appender-ref ref="STDOUT" />
    </root>
</springProfile>      

同時也有兩種方式指定使用的環境:

a. 在application.properties裡配置:

spring.profiles.active: dev
spring.profiles.active: prod      

b. 啟動時指定:

java -jar xxx.jar --spring.profiles.active=dev      

6. 應用

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

public abstract class BaseController {
    protected static Logger logger = LoggerFactory.getLogger(BaseController.class);
}      
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/log")
public class LogController extends BaseController {
    @RequestMapping("/all")
    public String all(String message) {
        logger.trace(message);
        logger.debug(message);
        logger.info(message);
        logger.warn(message);
        logger.error(message);
        System.out.println(message);

        try {
            System.out.println(3 / 0);
        } catch (Exception e) {
            logger.error(null, e);
        }

        return message;
    }
}      

繼續閱讀