一、日誌框架-springboot-logback
一、在分散式專案中,會出現很多跟蹤某個使用者的每一次請求,定位使用者請求過程中出現的問題,統計介面的響應時間、效率等。比如定位使用者請求過程中出現的問題,這就需要知道使用者請求的是哪個介面,即知道URI、請求引數,在介面中出現了什麼問題,如果在日誌中沒有列印使用者的請求引數、沒有記錄同一次請求相同的id之類的引數、那麼定位問題是非常痛苦的。常見的日誌框架有:log4j、log4j 2、 slf4j,Common logging 、JUL、 logback等,目前流行的主要是logback。logback當前分成三個模組:logback-core,logback- classic和logback-access。logback-core是其它兩個模組的基礎模組。logback-classic是log4j的一個 改良版本。logback的效率、穩定性、佔用記憶體小都比log4j好,所以現在大多數專案都選擇logback作為日誌框架。
二、這個案例的目的:
1、使用者一次請求,列印的日誌traceId相同,即跟蹤id,方便定位問題(主要內容);
2、記錄響應時間、方便以後統計分析介面質量(這是後話了);
3、列印請求引數、響應結果(主要內容);
4、使用logstash或者filebeat採集日誌,輸入到elasticsearch,在kibana中可以快速定位問題、統計分析等。(後面的學習計劃)
三、建立springboot專案
1、pom.xml
<?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> <groupId>com.cn.dl</groupId> <artifactId>springbootlogdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springbootlogdemo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.22</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2、Controller
package com.cn.dl.controller; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; /** * Created by Tiger on 2018/10/29. */ @RestController @RequestMapping({"log"}) @Slf4j public class LogDemoController { @RequestMapping({"test"}) public void logTest(@RequestParam("userName") String name, @RequestParam("age") int age, @RequestParam("interest") List<String> interest){ log.info("name:{},age:{},interest:{}",name,age,interest); } @RequestMapping({"test_two"}) public JSONObject logTestTwo(@RequestParam("dataType") int dataType){ JSONObject result = new JSONObject(); if(dataType == 1){ result.put("name","Tiger"); result.put("age",18); result.put("gender","man"); }else if (dataType == 2){ result.put("name","WYY"); result.put("age",17); result.put("gender","woman"); }else{ result.put("type","java"); result.put("study","springboot2"); } return result; } }
3、TraceIdInterceptor:攔截所有請求,記錄traceId。MDC(Mapped Diagnostic Context,對映除錯上下文)是 log4j 和 logback 提供的一種方便在多執行緒條件下記錄日誌的功能。這裡使用slf4j的MDC用來儲存traceId,可以追蹤使用者請求,方便快捷。
package com.cn.dl.interceptor;
import com.cn.dl.common.CommonConfig;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* Created by Tiger on 2018/10/29.
*/
public class TraceIdInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String traceId = UUID.randomUUID().toString().replace("-","");
MDC.put(CommonConfig.TRACE_ID, traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
4、NetUtils:從request中獲取使用者ip
package com.cn.dl.utils;
import javax.servlet.http.HttpServletRequest;
/**
* 獲取使用者真實IP
* Created by Tiger on 2018/10/29.
*/
public class NetUtils {
private static final String[] HEADERS_TO_TRY = {
"X-Forwarded-For",
"Proxy-Client-IP",
"WL-Proxy-Client-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR"
};
/**
* 獲取使用者真實IP地址
* @param request
* @return
*/
public static String getClientIpAddress(HttpServletRequest request) {
String rip = request.getRemoteAddr();
for (String header : HEADERS_TO_TRY) {
String ip = request.getHeader(header);
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
rip = ip;
break;
}
}
int pos = rip.indexOf(',');
if (pos >= 0) {
rip = rip.substring(0, pos);
}
return rip;
}
}
5、CommonConfig:公共配置,主要是個人編碼習慣,為了定義統一的規範、程式碼整潔、方便維護等,這些公共配置是非常有必要的,這裡有一個配置:public static final String LOG_PREFIX = "logData==",在使用logstash採集日誌的時候,就知道它的作用了。
package com.cn.dl.common;
/**
* Created by Tiger on 2018/10/29.
*/
public class CommonConfig {
public static final String START_TIME = "startTime";
public static final String IP = "ip";
public static final String CONSUME_TIME = "consumeTime";
public static final String REQ_PATH = "reqPath";
public static final String RES_BODY = "resBody";
public static final String LOG_PREFIX = "logData==";
public static final String TRACE_ID = "tarceId";
public static final String LOG_TYPE = "logType";
public static final String START = "start";
public static final String END = "end";
}
6、LogDemoInterceptor:從request中獲取ip、請求引數、記錄請求開始的時間戳、在請求介面之前輸出日誌。
package com.cn.dl.interceptor;
import com.alibaba.fastjson.JSONObject;
import com.cn.dl.common.CommonConfig;
import com.cn.dl.utils.NetUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* Created by Tiger on 2018/10/29.
*/
public class LogDemoInterceptor implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger("outToFile");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Long startTime = System.currentTimeMillis();
JSONObject json = new JSONObject();
//使用者ip
json.put(CommonConfig.IP, NetUtils.getClientIpAddress(request));
//請求路徑
json.put(CommonConfig.REQ_PATH,request.getRequestURI());
//請求引數
Map<String,String[]> map = request.getParameterMap();
map.forEach((key,value) -> {
json.put(key,request.getParameter(key));
});
//記錄請求開始時間
request.setAttribute(CommonConfig.START_TIME,startTime);
//traceId
json.put(CommonConfig.TRACE_ID, MDC.get(CommonConfig.TRACE_ID));
json.put(CommonConfig.LOG_TYPE,CommonConfig.START);
logger.info(CommonConfig.LOG_PREFIX + json.toJSONString());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
JSONObject json = new JSONObject();
Long startTime = (Long) request.getAttribute(CommonConfig.START_TIME);
//請求耗時
json.put(CommonConfig.CONSUME_TIME,System.currentTimeMillis() - startTime);
//traceId
json.put(CommonConfig.TRACE_ID,MDC.get(CommonConfig.TRACE_ID));
//響應Data
json.put(CommonConfig.RES_BODY,request.getAttribute(CommonConfig.RES_BODY));
//日誌型別
json.put(CommonConfig.LOG_TYPE,CommonConfig.END);
logger.info(CommonConfig.LOG_PREFIX + json.toJSONString());
}
}
7、ControllerConfiger:配置需要攔截的請求以及需要做哪些事情。
package com.cn.dl.configuration;
import com.cn.dl.interceptor.LogDemoInterceptor;
import com.cn.dl.interceptor.TraceIdInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Created by Tiger on 2018/10/29.
*/
@Configuration
public class ControllerConfiger implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO: 2018/10/29 這兩個攔截的順序可以變化嗎?不可以,因為需要先獲取traceId,
// TODO: 2018/10/29 不然LogDemoInterceptor -> preHandle方法列印的日誌沒有traceId
/**
* 同一次請求traceId相同
* */
registry.addInterceptor(new TraceIdInterceptor()).addPathPatterns("/**");
/**
* 列印請求路徑、請求引數已經返回的引數等等
* */
registry.addInterceptor(new LogDemoInterceptor()).addPathPatterns("/**");
}
}
8、ControllerReponseAdvice:為了在響應體返回之前記錄資料、修改資料、加密(郵箱、電話號碼等隱私資訊,例如:158****0215,實現這種小功能)等。
package com.cn.dl.advice;
import com.alibaba.fastjson.JSON;
import com.cn.dl.common.CommonConfig;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* Created by Tiger on 2018/10/29.
*/
@ControllerAdvice
public class ControllerReponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
return true;
}
@Override
public Object beforeBodyWrite(@Nullable Object obj, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (obj != null) {
try {
HttpServletRequest servletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
servletRequest.setAttribute(CommonConfig.RES_BODY, JSON.toJSONString(obj));
} catch (Exception e) {
e.printStackTrace();
}
}
return obj;
}
}
10、logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="10 seconds">
<property name="logData" value="./logDatas" />
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
<appender name="CONSOLE_OUT" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>%-15(%d{HH:mm:ss.SSS}) [%thread] %-5level %logger{80}[%line] -%msg%n</pattern>
</layout>
</appender>
<appender name="LOG_DEMO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${logData}/logDataDemo.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logData}/logDataDemo-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>
%-20(%d{yyyy-MMM-dd HH:mm:ss} [%thread]) %-5level %logger{80} [%line] -%msg%n
</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="CONSOLE_OUT" />
</root>
<!--輸出到指定日誌檔案-->
<logger name="outToFile" level="INFO" additivity="false">
<appender-ref ref="LOG_DEMO"/>
</logger>
</configuration>
四、測試
1、預期列印的日誌同一次請求traceId是相同的,請求介面時打印出請求引數、使用者ip,介面返回時有返回引數、請求耗時等資訊。
2018-十月-29 11:58:25 [http-nio-8080-exec-1] INFO outToFile [39] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24"}
2018-十月-29 11:58:26 [http-nio-8080-exec-2] INFO outToFile [39] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24"}
2018-十月-29 11:58:27 [http-nio-8080-exec-3] INFO outToFile [39] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24"}
2018-十月-29 12:03:23 [http-nio-8080-exec-1] INFO outToFile [41] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24","tarceId":"b27527bc9a284a7496f11d2a4843a5ca"}
2018-十月-29 12:03:24 [http-nio-8080-exec-2] INFO outToFile [41] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24","tarceId":"72f4f8a697d74a1298105b7afcb8b37b"}
2018-十月-29 12:03:24 [http-nio-8080-exec-3] INFO outToFile [41] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24","tarceId":"92b4129de3b44ae19136d75a8ee4d4b0"}
2018-十月-29 12:03:25 [http-nio-8080-exec-4] INFO outToFile [41] -logData=={"interest1":"籃球,足球","ip":"127.0.0.1","name":"Tiger","reqPath":"/log/test","interest2":"123,23","age":"24","tarceId":"59314d66d8554ec0bfc8deeca506e408"}
2018-十月-29 13:03:15 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","reqPath":"/log/test_two","tarceId":"8efe4b8a28f648eca94ec560639a07b3"}
2018-十月-29 13:03:15 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","reqPath":"/error","tarceId":"9c4521d0d8ba4ddaadd461dde1184bfa"}
2018-十月-29 13:03:23 [http-nio-8080-exec-2] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"d409a8f851f3460c88e055f92752046a"}
2018-十月-29 13:04:21 [http-nio-8080-exec-3] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"f3be19fa4fd44d89a88ec304e901a821"}
2018-十月-29 13:05:37 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"185419a3d1444ead8d8b604cf524ce4f"}
2018-十月-29 13:07:43 [http-nio-8080-exec-10] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"7c063a6c6ca84e63b9d984d8c4490020"}
2018-十月-29 13:07:43 [http-nio-8080-exec-10] INFO outToFile [60] -logData=={"logType":"end","consumeTime":105,"tarceId":"7c063a6c6ca84e63b9d984d8c4490020"}
2018-十月-29 13:09:01 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"75b5edb346be4f7fa5ddd1236d7632a3"}
2018-十月-29 13:09:01 [http-nio-8080-exec-1] INFO outToFile [59] -logData=={"logType":"end","consumeTime":137,"tarceId":"75b5edb346be4f7fa5ddd1236d7632a3"}
2018-十月-29 13:09:02 [http-nio-8080-exec-2] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"21b38c96334448e49e28f720a7001381"}
2018-十月-29 13:09:02 [http-nio-8080-exec-2] INFO outToFile [59] -logData=={"logType":"end","consumeTime":2,"tarceId":"21b38c96334448e49e28f720a7001381"}
2018-十月-29 13:10:37 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"42cfca81a86444d9aa811b54af513482"}
2018-十月-29 13:10:37 [http-nio-8080-exec-1] INFO outToFile [59] -logData=={"logType":"end","consumeTime":106,"tarceId":"42cfca81a86444d9aa811b54af513482"}
2018-十月-29 13:12:04 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two","tarceId":"81a13f3d36d74d4fa3c840c1a46282b7"}
2018-十月-29 13:12:04 [http-nio-8080-exec-1] INFO outToFile [59] -logData=={"logType":"end","consumeTime":125,"resBody":"{\"gender\":\"man\",\"name\":\"Tiger\",\"age\":18}","tarceId":"81a13f3d36d74d4fa3c840c1a46282b7"}
2018-十月-29 13:15:01 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/","tarceId":"268a0c984e1f4093aae2ae4adf205747"}
2018-十月-29 13:15:01 [http-nio-8080-exec-1] INFO outToFile [59] -logData=={"logType":"end","consumeTime":59,"tarceId":"268a0c984e1f4093aae2ae4adf205747"}
2018-十月-29 13:15:01 [http-nio-8080-exec-1] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/error","tarceId":"1890a30a82644abc8abb7fb86c5c8d01"}
2018-十月-29 13:15:01 [http-nio-8080-exec-1] INFO outToFile [59] -logData=={"logType":"end","consumeTime":77,"resBody":"{\"timestamp\":1540790101767,\"status\":404,\"error\":\"Not Found\",\"message\":\"No message available\",\"path\":\"/\"}","tarceId":"1890a30a82644abc8abb7fb86c5c8d01"}
2018-十月-29 13:15:15 [http-nio-8080-exec-2] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/","tarceId":"47ef2b0d4f304065a363a2dff8cfaf71"}
2018-十月-29 13:15:15 [http-nio-8080-exec-2] INFO outToFile [59] -logData=={"logType":"end","consumeTime":1,"tarceId":"47ef2b0d4f304065a363a2dff8cfaf71"}
2018-十月-29 13:15:15 [http-nio-8080-exec-2] INFO outToFile [42] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/error","tarceId":"159cd790d1574e6cb3b11a647857e24d"}
2018-十月-29 13:15:15 [http-nio-8080-exec-2] INFO outToFile [59] -logData=={"logType":"end","consumeTime":3,"resBody":"{\"timestamp\":1540790115376,\"status\":404,\"error\":\"Not Found\",\"message\":\"No message available\",\"path\":\"/\"}","tarceId":"159cd790d1574e6cb3b11a647857e24d"}
2018-十月-29 13:18:35 [http-nio-8080-exec-10] INFO outToFile [44] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/"}
2018-十月-29 13:18:35 [http-nio-8080-exec-10] INFO outToFile [66] -logData=={"logType":"end","consumeTime":57,"tarceId":"f06737292396431e9c523da820d322fb"}
2018-十月-29 13:18:35 [http-nio-8080-exec-10] INFO outToFile [44] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/error","tarceId":"f06737292396431e9c523da820d322fb"}
2018-十月-29 13:18:35 [http-nio-8080-exec-10] INFO outToFile [66] -logData=={"logType":"end","consumeTime":63,"resBody":"{\"timestamp\":1540790315526,\"status\":404,\"error\":\"Not Found\",\"message\":\"No message available\",\"path\":\"/\"}","tarceId":"5792b7de44174dc4a27bd56edccc27f8"}
2018-十月-29 13:18:44 [http-nio-8080-exec-9] INFO outToFile [44] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two"}
2018-十月-29 13:18:44 [http-nio-8080-exec-9] INFO outToFile [66] -logData=={"logType":"end","consumeTime":18,"resBody":"{\"gender\":\"man\",\"name\":\"Tiger\",\"age\":18}","tarceId":"1e33c92d7bef44eba71a2a21a9d9a573"}
2018-十月-29 13:18:57 [http-nio-8080-exec-8] INFO outToFile [44] -logData=={"logType":"start","ip":"127.0.0.1","dataType":"1","reqPath":"/log/test_two"}
2018-十月-29 13:18:57 [http-nio-8080-exec-8] INFO outToFile [66] -logData=={"logType":"end","consumeTime":2,"resBody":"{\"gender\":\"man\",\"name\":\"Tiger\",\"age\":18}","tarceId":"5a921e9e59304a92902e7ef1b8e1b625"}
五、案例不足之處
1、未處理介面中列印的日誌;
2、未記錄介面中出現的異常日誌。
六、下次目標
1、使用filebeat處理列印的日誌檔案;
2、使用logback將日誌輸出到kafka中;
3、本地搭建elasticsearch、kibana,可以在kibana實時查詢列印的日誌。