Spring Boot 使用 Log4j2 & Logback 輸出日誌到 EKL

1、ELK 介紹

ELK 是 Elasticsearch , Logstash, Kibana 的縮寫,Elasticsearch 是開源分佈式搜索引擎,提供蒐集、分析、存儲數據等功能,Logstash 主要是用來日誌的蒐集、分析、過濾日誌的工具,Kibana 爲 Elasticsearch 提供分析和可視化的 Web 平臺,可以在 Elasticsearch 的索引中查找,交互數據,並生成各種維度的表圖。一句話:日誌收集(Logstash),數據存儲(Elasticsearch),日誌可視化(Kibana),三者有機結合起來,非常方便集中式處理日誌。

2、環境、軟件準備

本次演示環境,我是在本機 MAC OS 上操作,以下是安裝的軟件及版本:

  • Java: 1.8.0_211
  • Elasticsearch: 7.1.0
  • Logstash: 7.1.0
  • Kibana: 7.1.0
  • Spring Boot: 2.1.4.RELEASE

注意:本次主要演示如何在 Spring-Boot 項目中配置 Log4j2 以及 Logback 輸出日誌到 ELK 中,並能夠在 Kibana 中可以正確檢索出來,Elasticsearch 及 Spring-Boot 項目底層需要 Java 環境,所以需要提前本地安裝好 Java 環境,這裏忽略 Java 安裝過程。

3、ELK 環境搭建

我們可以去 官網 分別下載系統對應最新版 ElasticsearchLogstashKibana,截止目前已更新到 7.1.0 版本,可通過以下鏈接獲取安裝包並提供默認配置啓動步驟。

這裏我先按照默認配置啓動 Elasticsearch 服務,啓動完畢,本地可以通過 http://127.0.0.1:9200 地址訪問服務是否啓動正常。注意:先不啓動 LogstashKibana,因爲他們的配置需要更改,下邊會講到。
spring-elk

4、Spring Boot 配置示例

使用 Idea 創建一個 Spring Boot 項目,我們先添加 Log4j2支持,演示如何使用 Log4j2 將日誌直接輸出到本地的 ELK 中,然後演示下通過 Logback 動態輸出索引名稱到日誌中,方便分類檢索日誌。

4.1、Log4j2 方式配置

首先修改 pom.xml 增加 Log4j2 日誌框架支持,注意 spring-boot-starter 默認使用 Logback 作爲日誌框架,所以需要先移除默認日誌配置 spring-boot-starter-logging

pom.xml 增加如下配置:

	 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
	 <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>
    <dependency>
        <groupId>com.lmax</groupId>
        <artifactId>disruptor</artifactId>
        <version>3.4.2</version>
    </dependency>

注意:disruptor 是一個輕量的高性能併發框架,Log4j2 包含了基於 LMAX Disruptor(高性能線程間消息通信庫)的下一代 Asynchronous Loggers。在多線程環境下 Asynchronous Loggers 的吞吐量是 Log4j1Logback 的 18 倍,而延遲時間也要低一個數量級。如果使用異步日誌時,添加 disruptor 支持,會大大提高效率,當然不添加也是沒有問題的。

增加 log4j2-spring.xml 配置輸出到 ELK 中,大概配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="OFF" monitorInterval="60">
    <Appenders>
        <!-- Console 日誌,只輸出 level 及以上級別的信息,並配置各級別日誌輸出顏色 -->
        <Console name="Console" target="SYSTEM_OUT">
            <!--控制檯只輸出level及以上級別的信息(onMatch),其他的直接拒絕(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="%highlight{%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %M() @%L - %msg%n}{FATAL=Bright Red, ERROR=Bright Magenta, WARN=Bright Yellow, INFO=Bright Green, DEBUG=Bright Cyan, TRACE=Bright White}"/>
        </Console>
		 <!-- socket 日誌,輸出日誌到 Logstash 中做日誌收集 -->
        <Socket name="Socket" host="127.0.0.1" port="4560" protocol="TCP">
            <JsonLayout properties="true" compact="true" eventEol="true" />
            <PatternLayout pattern="%d{yyyy.MM.dd 'at' HH:mm:ss z} %-5level %class{36} %M() @%L - %msg%n"/>
        </Socket>
    </Appenders>
    <Loggers>
        <Root level="INFO">
            <appender-ref ref="Socket"/>
            <appender-ref ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

注意:這裏配置 Socket 輸出方式,輸出日誌到本地 Logstash 做日誌收集,格式化處理後自動輸出到本地 Elasticsearch 中存儲,最後通過 Kibana 檢索索引通過 Web 頁面展示出來。需要指定 hostportprotocol,這裏就配置成本地 Logstash 指定配置的端口和地址即可。

同時可以在 application.properties 中配置日誌輸出級別,注意這裏可以不指定加載 log4j2-spring.xml 文件,Spring Boot 會默認加載該配置文件。

logging.level.root=info

最後,代碼中在 Controller 寫入一些特定日誌和異常信息,方便在 Kibana 中查看驗證。

@RestController
@RequestMapping("/test")
public class LogController {

    private Logger logger = LogManager.getLogger(LogController.class);
    
    @RequestMapping(value = "/log4j2", method = RequestMethod.GET)
    public String testLog(){
        try {
            logger.info("Hello 這是 info message. 信息");
            logger.error("Hello 這是 error message. 報警");
            logger.warn("Hello 這是 warn message. 警告");
            logger.debug("Hello 這是 debug message. 調試");
            logger.fatal("Hello 這是 fatal message. 嚴重");
            List<String> list = new ArrayList<>();
            System.out.println(list.get(2));
        } catch (Exception e) {
            logger.error("testLog", e);
        }
        return "";
    }
}

OK,Spring Boot 工程添加 Log4j2 支持配置完畢,接下來,我們需要配置 LogstashKibana,在 Logstash 安裝目錄下 config 目錄,新建 test-log4j2.conf 配置文件,配置如下:

input {
  tcp {
    host => "127.0.0.1"
    port => "4560"
    mode => "server"
    type = json
  }
  stdin {}
}
filter {

}
output {
  stdout {
    codec => rubydebug
  }
  elasticsearch {
    hosts => ["127.0.0.1:9200"]
    action => "index"
	 codec => rubydebug
    index => "log4j2-%{+YYYY.MM.dd}"
  }
}

注意:這裏配置 tcp 的 host 和 port 要跟上邊 log4j2-spring.xml 中配置一致,否則無法進行日誌收集, output 下的 elasticsearch 的 host 配置要跟上邊啓動的本地 Elasticsearch 配置一致,index 指定爲固定的 log4j2-yyyy.MM.dd 格式,方便在 Kibana 中檢索索引使用。使用該配置文件啓動 Logstash,命令如下:

$ cd <Logstash_path>/bin
$ ./logstash -f ../config/test-log4j2.conf

最後,啓動 Kibana,可以使用默認配置,不過這裏我稍微修改一些配置如下:

$ vim <Kibana_path>/config/kibana.conf

server.port: 5601
server.host: "127.0.0.1"
elasticsearch.hosts: ["http://127.0.0.1:9200"]
i18n.locale: "zh-CN"

同理,這裏 elasticsearch.hosts 配置要跟上邊一致,同時指定 Kibana 啓動端口爲 5601,並且修改顯示默認英文爲中文,方便查看,啓動 Kibana 直到日誌輸出顯示狀態爲 Green 即啓動完畢。

一切都準備完畢,最後啓動 Spring Boot 工程,並觸發 /test/log4j2 接口,製造各類日誌,在 Kibana Web 頁面查看是否正確加載過來吧!

瀏覽器訪問 http://127.0.0.1:4560 即可打開 Kibana 頁面,首先我們查看下 Elasticsearch 索引管理裏面,是否已存在上邊配置的 log4j2-yyyy.MM.dd 格式索引。
spring-elk
OK,顯示已存在,那麼接下來我們在 Kibana 索引模式下創建索引模式,輸入 log4j2-* 即可正確匹配到 Elasticsearch 中的指定的索引,接着在時間篩選字段名稱處選擇 @timestamp,方便我們後邊按照時間段篩選數據,創建過程如下:
spring-elk
spring-elk
spring-elk
創建完畢,我們就可以在 Kibana 中篩選並顯示日誌了,比如我增加了 message 字段,過濾完後,就顯示出來上邊工程示例代碼中的各種類型日誌以及異常日誌了,非常直觀方便!
spring-elk

4.2、Logback 方式配置

上邊使用 Log4j2 日誌框架可以正確輸出日誌到 ELK,但是有一個地方需要我們注意,就是啓動 Logstash 時指定 Elasticsearch 的 index 索引爲固定值(log4j2-*)了,如果有多個 project 同時往 ELK 中輸出日誌,那麼使用同一個索引名稱的話,會造成日誌混亂,不方便區分排查各個項目的日誌,所以,我們希望能夠通過動態輸出索引名稱到 Elasticsearch 中,例如 Project A 輸出到索引 projectA-* 下,Project B 輸出到索引 projectB-* 下,這樣就方便我們在 Kibana 下根據項目匹配對應的索引值了,當然使用 Spring Boot 默認日誌框架 Logback 可以很輕鬆的辦到。

那麼改造項目來支持 Logback 日誌框架,首先修改 pom.xml 配置文件如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
<!--        <exclusions>-->
<!--            <exclusion>-->
<!--                <groupId>org.springframework.boot</groupId>-->
<!--                <artifactId>spring-boot-starter-logging</artifactId>-->
<!--            </exclusion>-->
<!--        </exclusions>-->
    </dependency>


    <dependency>
        <groupId>net.logstash.logback</groupId>
        <artifactId>logstash-logback-encoder</artifactId>
        <version>5.3</version>
    </dependency>

注意:這裏將移除的 spring-boot-starter-logging 重新添加進來,同時依賴 logstash-logback-encoder 該插件,該插件起到 Socket 通過 TCP 方式向 Logstash 進行日誌輸送的作用。

接着增加 logback-spring.xml 配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <property name="LOG_HOME" value="logs/demo.log" />
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
        <destination>127.0.0.1:4560</destination>
        <encoder class="net.logstash.logback.encoder.LogstashEncoder" >
            <customFields>{"appname": "demo-elk"}</customFields>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="logstash" />
    </root>
</configuration>

注意:這裏的 destination 依舊要配置對應上本地 Logstash 配置,着重說下 <customFields>{"appname": "demo-elk"}</customFields> 字段配置,該自定義字段配置, Logstash 收集日誌時,每條日誌記錄均會帶上該字段,而且在 Logstash 配置文件中可以通過變量的方式獲取到字段,這樣就能達到我們說的動態輸出索引名稱到 Elasticsearch 中的功能了。同樣,application.properties 可以不指定加載 logback-spring.xml 文件,Spring Boot 會默認加載該配置文件。接下來,去 Logstash config 下增加 test-logback.conf 配置文件,配置如下:

input {
  tcp {
    host => "127.0.0.1"
    port => "4560"
    mode => "server"
    type => json
  }
  stdin {}
}
filter {

}
output {
  stdout {
    codec => rubydebug
  }
  elasticsearch {
    hosts => ["127.0.0.1:9200"]
    action => "index"
    codec => rubydebug
    index => "%{[appname]}-%{+YYYY.MM.dd}"
  }
}

注意:這裏的 %{[appname]} 就是獲取上邊的 <customFields> 字段中的 json 串 key 值,我們只傳了一個 appname 值,當讓還可以傳遞其他值,例如 IP、Hostname 等關鍵信息,方便在 Kibana 中檢索索引時區分。重啓一下 Logstash 指定該配置文件。

$ cd <Logstash_path>/bin
$ ./logstash -f ../config/test-logback.conf

ElasticsearchKibana 不需要重啓,再次啓動 Spring Boot 工程,去 Kibana 下查看 !查看下 Elasticsearch 索引管理裏面,是否已存在上邊配置的 demo-elk-yyyy.MM.dd 格式索引。
spring-elk
What? 怎麼沒有獲取到傳遞過去的 appname 值呢?原樣配置到 Elasticsearch 索引中去了,但是我在後臺 Logstash 控制檯日誌中可以明顯看到,打印的每條 Json 串中是有該字段的呀!各種搜索,發現大家也是這麼配置的呢!即使將 type => json 改爲 codec => json 依舊不行!百思不得解的時候,查看了下 logstash-logback-encoder 文檔說明 這裏明確指出要使用 codec => json_lines 方式,好吧! test-logbash.conf 配置修改如下並重啓 Logstash

input {
  tcp {
    host => "127.0.0.1"
    port => "4560"
    mode => "server"
    codec => json_lines
  }
  stdin {}
}
filter {

}
output {
  stdout {
    codec => rubydebug
  }
  elasticsearch {
    hosts => ["127.0.0.1:9200"]
    action => "index"
    index => "%{[appname]}-%{+YYYY.MM.dd}"
  }
}

這下妥妥沒有問題了,在去查看下 Elasticsearch 索引管理,這下就有了。
spring-elk
那麼接着建一個索引模式名稱爲 demo-elk-*,查看下日誌記錄,是否能夠正常加載的項目日誌,也是妥妥沒有問題的。
spring-elk
spring-elk
參考資料

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