一、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



	4.0.0

	com.cn.dl
	springbootlogdemo
	0.0.1-SNAPSHOT
	jar

	springbootlogdemo
	Demo project for Spring Boot

	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.6.RELEASE
		 
	

	
		UTF-8
		UTF-8
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
		

		
			org.projectlombok
			lombok
			1.16.22
		

		
			com.alibaba
			fastjson
			1.2.47
		

	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	



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 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 interface CommonConfig {
    String START_TIME = "startTime";
    String IP = "ip";
    String CONSUME_TIME = "consumeTime";
    String REQ_PATH = "reqPath";
    String RES_BODY = "resBody";
    String LOG_PREFIX = "logData==";
    String TRACE_ID = "tarceId";
    String LOG_TYPE = "logType";
    String START = "start";
    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 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



  
  
  
    
      %-15(%d{HH:mm:ss.SSS}) [%thread] %-5level %logger{80}[%line] -%msg%n
    
  

  
    ${logData}/logDataDemo.log
    
      ${logData}/logDataDemo-%d{yyyy-MM-dd}.log
      30
    
    
      
        %-20(%d{yyyy-MMM-dd HH:mm:ss} [%thread]) %-5level %logger{80} [%line] -%msg%n
      
      UTF-8
    
  

  
    
  
  
  
    
  

四、测试

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实时查询打印的日志。

 

 

 

 

 

你可能感兴趣的:(springboot,java常用技术)