网络请求链路:

client->gateway->serviceA->serviceB

实现办法

gateway接收请求初始化这个请求的request_id,转发请求到下游服务时携带request_id的请求头,serviceA接收到请求将request_id注入log MDC,需要调用其他服务时使用feignClient,配置转发上游服务发送过来的请求头。这样同一请求以request_id串联起来。

日志格式:

$ $ %d [%thread] [%X] %level% logger{50} - %msg%n
在各个服务添加aop拦截controller,将每个请求的url header param result记录下来,这样在查询时很容易定位问题。

效果:

image.png
相关代码:

@Configuration
public class FeignConfiguration {
    @Bean
    public RequestInterceptor requestInterceptor() {
        return requestTemplate -> {
            ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attrs != null) {
                HttpServletRequest request = attrs.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String value = request.getHeader(name);
                        /**
                         * 遍历请求头里面的属性字段,将logId和token添加到新的请求头中转发到下游服务
                         * */
                        requestTemplate.header(name, value);
                    }
                }
            }
        };
    }
}
@Slf4j
@Aspect   //定义一个切面
@Configuration
public class LogRecordAspect {


    // 定义切点Pointcut
    @Pointcut("execution(* com.xx.controller.*Controller.*(..))")
    public void excudeService() {
    }

    @Around("excudeService()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        long start = System.currentTimeMillis();
        String url = request.getRequestURL().toString();
        String method = request.getMethod();
        String uri = request.getRequestURI();
        String queryString = request.getQueryString();
        Enumeration<String> headers = request.getHeaderNames();
        StringBuffer headerStr = new StringBuffer();
        while (headers.hasMoreElements()){
            String headerName = headers.nextElement();
            if(headerName.equals("uid")||headerName.equals("Authorization")){
                headerStr.append(headerName).append(":").append(request.getHeader(headerName)).append(";");
            }
        }

        // result的值就是被拦截方法的返回值
        Object result = pjp.proceed();
        long end = System.currentTimeMillis();
        log.info("request url: {}, header : {}, method: {}, uri: {}, params: {},time : {} , result: {}", url,headerStr.toString(), method, uri, queryString,end-start, JSON.toJSONString(result));

        return result;
    }


}
@Configuration
public class FilterConfig {
    @Autowired
    private LogMDCFilter logMDCFilter;

    @Bean
    public FilterRegistrationBean<LogMDCFilter> logFilterRegistration() {
        FilterRegistrationBean<LogMDCFilter> registration =
                new FilterRegistrationBean<LogMDCFilter>();
        // 注入过滤器
        registration.setFilter(logMDCFilter);
        // 拦截规则
        registration.addUrlPatterns("/*");
        // 过滤器名称
        registration.setName(logMDCFilter.getClass().getName());
        // 过滤器顺序
        registration.setOrder(0);

        return registration;
    }
}
@Component
public class LogMDCFilter implements Filter{


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request= (HttpServletRequest)servletRequest;
        if(request.getHeader("request_id")!=null){
            MDC.put("requestId", request.getHeader("request_id"));
        }else{
            MDC.put("requestId", UUID.randomUUID().toString().replace("-", ""));
        }
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            MDC.remove("requestId");
        }
    }

    @Override
    public void destroy() {

    }
}