spring-boot下使用LogBack,使用HTTP協議將日誌推送到日誌服務器

當項目上線發生錯誤或是異常後,咱們老是指望可以在第一時間內收到用戶的詳細反饋。固然,這也無疑會是一個很是好的提高軟件質量的方法。但若是用戶不肯意反饋呢?此時,咱們即可以藉助日誌系統,好比:每隔一小時,服務器自動向咱們報告一下當前的服務狀況。當有錯誤或是警告或是異常信息時,及時向咱們的報告等。html

在基於上述的需求上,咱們結合spring-boot內置的LogBack,來給出將warn,error信息發送到遠程服務器的示例。java

項目地址

https://github.com/mengyunzhi/springBootSampleCode/tree/master/log-back
開發環境: java1.8 + spring-boot:2.1.2git

實現步驟

引入相關的依賴

pom.xmlgithub

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mengyunzhi.sample</groupId>
    <artifactId>log-back</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>log-back</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

啓動項目

控制檯打印信息以下:web

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.2.RELEASE)

2019-01-16 10:35:04.999  INFO 1571 --- [           main] c.m.sample.logback.LogBackApplication    : Starting LogBackApplication on panjiedeMac-Pro.local with PID 1571 (/Users/panjie/github/mengyunzhi/sample/spring-boot/log-back/target/classes started by panjie in /Users/panjie/github/mengyunzhi/sample/spring-boot/log-back)
2019-01-16 10:35:05.002  INFO 1571 --- [           main] c.m.sample.logback.LogBackApplication    : No active profile set, falling back to default profiles: default
2019-01-16 10:35:05.913  INFO 1571 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-01-16 10:35:05.934  INFO 1571 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-01-16 10:35:05.935  INFO 1571 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.14]
2019-01-16 10:35:05.940  INFO 1571 --- [           main] o.a.catalina.core.AprLifecycleListener   : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [/Users/panjie/Library/Java/Extensions:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java:.]
2019-01-16 10:35:06.008  INFO 1571 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-01-16 10:35:06.008  INFO 1571 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 968 ms
2019-01-16 10:35:06.183  INFO 1571 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2019-01-16 10:35:06.335  INFO 1571 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-01-16 10:35:06.338  INFO 1571 --- [           main] c.m.sample.logback.LogBackApplication    : Started LogBackApplication in 1.616 seconds (JVM running for 2.093)

配置logback

新建resources/logback-spring.xml,初始化如下信息:spring

<?xml version="1.0" encoding="UTF-8"?>
<!--開啓debug模式-->
<configuration debug="true">
</configuration>

啓動項目,控制檯打印信息以下:apache

10:33:41,053 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration.
10:33:41,054 |-INFO in org.springframework.boot.logging.logback.SpringBootJoranConfigurator@55a1c291 - Registering current configuration as safe fallback point
10:33:41,067 |-WARN in Logger[org.springframework.boot.context.logging.ClasspathLoggingApplicationListener] - No appenders present in context [default] for logger [org.springframework.boot.context.logging.ClasspathLoggingApplicationListener].

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.2.RELEASE)

如何判斷配置成功了?

咱們比較上面兩個日誌,第一個是沒有配置logback-spring.xml,第二個是配置logback-spring.xml了。是的,若是咱們發現spring 大LOG打印前,在控制檯中打印了ch.qos...輸出的日誌信息,則說明logback-spring.xml。同時,若是logback-spring.xml起做用的話,咱們還發現spring 大LOG下面,一行日誌也沒有了。json

是的,因爲logback-spring.xml對日誌輸出進行了控制,而配置信息中,咱們又沒有寫任何的信息,爲空。因此spring 大LOG後面固然就不顯示任何日誌了信息了。segmentfault

查看spring-boot的默認配置

咱們使用IDEA的打開文件快捷鍵commod+shift+o,輸入base.xml,而後再使用查看文件位置快捷鍵option+F1來查看文件位置。更來到了spring-boot的默認配置。瀏覽器

clipboard.png

上述文件,即爲spring-boot的默認配置。下面,咱們將以上配置引入到咱們的logback-spring.xml中,來實現spring-boot的默認日誌效果。

實現默認效果

複製相應的代碼至logback-spring.xml中:

<?xml version="1.0" encoding="UTF-8"?>
<!--啓用debug模式後,將在`spring-boot 大LOG`上方打印中logBack的配置信息-->
<configuration debug="true">
    <!--包含配置文件 org/springframework/boot/logging/logback/defaults.xml-->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />

    <!--定義變量LOG_FILE,值爲${LO...}-->
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>

    <!--包含配置文件,該配置文件中,定義了 控制檯日誌是按什麼規則,什麼形式輸出的-->
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <!--包含配置文件,該配置文件中,定義了 文件日誌是按什麼規則,什麼形式輸出的-->
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />

    <!--定義日誌等級-->
    <root level="INFO">
        <!--啓用第一個appender爲CONSOLE, 該名稱定義於org/springframework/boot/logging/logback/console-appender.xml中-->
        <appender-ref ref="CONSOLE" />

        <!--啓用第二個appender爲FILE, 該名稱定義於org/springframework/boot/logging/logback/file-appender.xml中-->
        <appender-ref ref="FILE" />
    </root>
</configuration>

而後咱們再次啓動項目,會發現與原spring-boot相比較,在spring 大LOGO前多一些日誌相關的配置信息輸出,其它的信息是一致的。

實現http日誌appender

appender

經過上面的註釋,咱們猜想:appender這個東西,可以把日誌處理成咱們想要的樣子。

在進行官方文檔的學習中,咱們發現了不少已經存在的appender。與咱們的需求比較相近的是SyslogAppender

liunx有標準的syslog服務,用於接收syslog日誌。

經過查詢相關資料,咱們獲悉,此syslog服務,一身做用於514端口上。直接使用UDPTCP協議發送MESSAGE。而咱們此時想用更熟悉的http協議。因此暫時放棄。

小於1024的均爲已知端口,能夠經過端口號來查詢對應的協議或服務名稱。

第三方http appender

除了按官方的教程來寫本身的http appender,還有一些比較好的第三方appender能夠使用,好比:LogglyAppender。找到官方文檔,並引入:

pom.xml

<dependency>
            <groupId>org.logback-extensions</groupId>
            <artifactId>logback-ext-loggly</artifactId>
            <version>0.1.5</version>
        </dependency>

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--啓用debug模式後,將在`spring-boot 大LOG`上方打印中logBack的配置信息-->
<configuration debug="true">
    <!--包含配置文件 org/springframework/boot/logging/logback/defaults.xml-->
    <include resource="org/springframework/boot/logging/logback/defaults.xml" />

    <!--定義變量LOG_FILE,值爲${LO...}-->
    <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}}/spring.log}"/>

    <!--包含配置文件,該配置文件中,定義了 控制檯日誌是按什麼規則,什麼形式輸出的-->
    <include resource="org/springframework/boot/logging/logback/console-appender.xml" />

    <!--包含配置文件,該配置文件中,定義了 文件日誌是按什麼規則,什麼形式輸出的-->
    <include resource="org/springframework/boot/logging/logback/file-appender.xml" />

    <!--引入第三方appender, 起名爲http-->
    <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender">
        <!--請求的地址-->
        <endpointUrl>http://localhost:8081/log</endpointUrl>
    </appender>

    <!--定義日誌等級-->
    <root level="INFO">
        <!--啓用第一個appender爲CONSOLE, 該名稱定義於org/springframework/boot/logging/logback/console-appender.xml中-->
        <appender-ref ref="CONSOLE" />

        <!--啓用第二個appender爲FILE, 該名稱定義於org/springframework/boot/logging/logback/file-appender.xml中-->
        <appender-ref ref="FILE" />

        <!--啓用第三個appender爲HTTP-->
        <appender-ref ref="HTTP" />
    </root>
</configuration>

測試

測試方法如圖:

clipboard.png

  1. 使用瀏覽器來訪問當前項目的'/send'地址
  2. send中咱們加入logger
  3. 再新建一個新項目,用來接收http appender發送過來的日誌。

創建測試方法

LogBackApplication.java

package com.mengyunzhi.sample.logback;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@SpringBootApplication
@RestController
public class LogBackApplication {
    private static final Logger logger = LoggerFactory.getLogger(LogBackApplication.class);

    public static void main(String[] args) {
        SpringApplication.run(LogBackApplication.class, args);
    }

    @RequestMapping("send")
    public void send() {
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
    }
}

接收模塊

新建一個spring boot項目,而後設置端口爲8081。

clipboard.png
application.properties
server.port=8081

ServiceApplication.java

package com.mengyunzhi.sample.logback.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.HttpRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@SpringBootApplication
@RestController
public class ServiceApplication {
    private final static Logger logger = LoggerFactory.getLogger(ServiceApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class, args);
    }

    @RequestMapping("log")
    public void log(HttpServletRequest httpServletRequest) {
        logger.info(httpServletRequest.toString());
    }
}

啓動測試

使用debug模式來啓動兩個項目,項目啓動後,打開瀏覽器,輸入:http://localhost:8080/send,並在8081端口上的接收位置打斷點。

clipboard.png

查看斷點信息:

clipboard.png

此時咱們發現兩項信息,也證實數據的確是發送和接收成功了:

  1. 請求方法: POST
  2. 請求的協議:http

查看發送過來的MESSAGE

@RequestMapping("log")
    public void log(HttpServletRequest httpServletRequest) throws IOException {
        logger.info(httpServletRequest.toString());
        BufferedReader bufferedReader = httpServletRequest.getReader();
        String str, wholeStr = "";
        while((str = bufferedReader.readLine()) != null) {
            wholeStr += str;
        }
        logger.info(wholeStr);
    }

以下:

2019-01-16T06:06:49.707Z INFO  [http-nio-8080-exec-1] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/]: Initializing Spring DispatcherServlet 'dispatcherServlet'

是的,正如你發如今的同樣,一些原本打印在8081項目上面的info信息,被髮送過來了。

格式化數據

傳過來的字段串,並不友好,咱們接下來將其進行格式化。格式化的方法有兩種:1. 發送端格式化。2. 接收端格式化。接收端的格式化的思想是按空格將日誌拆分,而後要傳入到格式實體的不一樣的字段。這裏不闡述,不實現。
咱們重點放在第1種,發送端使用第三方庫進行格式化。

pom.xml

<!--log to json-->
        <dependency>
            <groupId>ch.qos.logback.contrib</groupId>
            <artifactId>logback-jackson</artifactId>
            <version>0.1.5</version>
        </dependency>

        <!--log to json-->
        <dependency>
            <groupId>ch.qos.logback.contrib</groupId>
            <artifactId>logback-json-classic</artifactId>
            <version>0.1.5</version>
        </dependency>

logback.xml

<!--引入第三方appender, 起名爲http-->
    <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender">
        <!--請求的地址-->
        <endpointUrl>http://localhost:8081/log</endpointUrl>
        <!--定義輸出格式JSON-->
        <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
            <jsonFormatter
                    class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
                <prettyPrint>true</prettyPrint>
            </jsonFormatter>
            <timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
        </layout>
    </appender>

再次啓動項目,訪問:http://localhost:8080/send查看斷點。

{  "timestamp" : "2019-01-16 14:17:54.783",  "level" : "ERROR",  "thread" : "http-nio-8080-exec-1",  "logger" : "com.mengyunzhi.sample.logback.LogBackApplication",  "message" : "error",  "context" : "default"}

咱們發現,之前的字段串,變成的json字符串,此時咱們即可以在接收端創建對應的實體,來輕易的接收了。

過濾掉INFO信息

當前雖然實現了將日誌寫入到第三方HTTP日誌服務器,可是一些咱們不想寫入的,好比說INFO信息,也被寫入了。下面,咱們寫一個過濾器,來實現只輸出warnerror的信息。

新建過濾器Filter.java

package com.mengyunzhi.sample.logback.service;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.LoggingEvent;
import ch.qos.logback.core.filter.AbstractMatcherFilter;
import ch.qos.logback.core.spi.FilterReply;

import java.util.Arrays;
import java.util.List;

/**
 * @author panjie
 */
public class Filter extends AbstractMatcherFilter {
    @Override
    public FilterReply decide(Object event) {
        if (!isStarted()) {
            return FilterReply.NEUTRAL;
        }

        LoggingEvent loggingEvent = (LoggingEvent) event;

        // 當級別爲warn或error,時觸發日誌。
        List<Level> eventsToKeep = Arrays.asList(Level.WARN, Level.ERROR);
        if (eventsToKeep.contains(loggingEvent.getLevel())) {
            return FilterReply.NEUTRAL;
        } else {
            return FilterReply.DENY;
        }

    }
}

設置過濾器:
logback-spring.xml

<!--引入第三方appender, 起名爲http-->
    <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender">
        <!--請求的地址-->
        <endpointUrl>http://localhost:8081/log</endpointUrl>
        <!--定義過濾器-->
        <filter class="com.mengyunzhi.sample.logback.Filter"/>
        <!--定義輸出格式JSON-->
        <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">
            <jsonFormatter
                    class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">
                <prettyPrint>true</prettyPrint>
            </jsonFormatter>
            <timestampFormat>yyyy-MM-dd' 'HH:mm:ss.SSS</timestampFormat>
        </layout>
    </appender>

測試:

clipboard.png

只接收了warnerror的數據。

統一配置

咱們在logback-spring.xml定義了<endpointUrl>http://localhost:8081/log</endpointUrl>,如何能夠將此項配置搬遷到application.properties中呢?

定義變量

application.properties

yunzhi.log.url=http://localhost:8081/log

引用變量

logback-spring.xml

<!--引入application配置信息-->
    <springProperty scope="context" name="logUrl" source="yunzhi.log.url"
                    defaultValue="localhost"/>
    <!--引入第三方appender, 起名爲http-->
    <appender name="HTTP" class="ch.qos.logback.ext.loggly.LogglyAppender">
        <!--請求的地址-->
        <endpointUrl>${logUrl}</endpointUrl>

此時,咱們即可以對logUrl在application.properties中進行統一管理了,固然了,不止如此,咱們還能夠在啓動項目的時候,使用--yunzhi.log.url=xxx來輕鬆的改變日誌接收地址。

總結

在總體實現的過程當中,咱們的解決思路仍然是:看官方文檔,學官方文檔,照抄官方文檔。欲速則不達,有學習一門新的知識時,優先學習官方sample code,其次是官方文檔。在學習的過程當中,還要特別的注意版本號的問題;如何正確的快速的高效率測試的問題。

每次有日誌就進行一次請求,對網絡資源是種浪費。是否能夠定時統一的將日誌發送給服務器呢?請繼續閱讀spring-boot下使用LogBack,使用HTTP協議將日誌推送到日誌服務器(二)

相關文章
相關標籤/搜索