Java项目分布式/集群部署,接口请求日志流程跟踪

Java项目分布式/集群部署,接口请求日志流程跟踪

由于公司的项目使用了SpringCloud微服务,并且各个模块都是集群部署,日志分布在不同的服务器,当线上请求出现问题,日志查询和跟踪是一个很麻烦的事情,大量日志输出导致很难筛出指定请求的全部相关日志,以及下游服务调用对应的日志。

解决思路

网关服务接收到请求,然后给每个请求分配一个唯一的请求ID(traceId),再将traceId放到请求头当中,这样该请求再调用下游服务时,下游服务便可以从请求头中获取到这个traceId,微服务之间就可以衔接起来。

日志打印

String traceId = request.getHeader("traceId");//从Header中获取traceId
log.info("接收到请求,traceId:{}",traceId);//打印日志

使用上面的方式打印日志,虽然可以达到目的,但是这意味着打印每一条日志,都需要获取traceId,而同一个请求的traceId是不变的,这样操作显然不是很好的处理方式。由于我处理日志是使用的logback,通过网上查询资料,logback日志框架都提供了MDC功能。

MDC

MDC 介绍
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

简而言之,MDC就是日志框架提供的一个InheritableThreadLocal,项目代码中可以将键值对放入其中,然后使用指定方式取出打印即可。

在logback 的取值方式为:

%X{traceId}

实现方案

gateway网关层

新增一个拦截器,拦截request请求,生成traceId并放入request的请求头。

import com.caiyi.sport.core.constant.HeaderConstant;
import com.caiyi.sport.core.constant.LogConstant;
import com.caiyi.sport.core.domain.App;
import com.caiyi.sport.core.utils.IPUtils;
import com.caiyi.sport.core.utils.UniqueStrCreator;
import com.caiyi.sport.core.utils.UserSourceMapUtil;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;


@Component
@Slf4j
@WebFilter(urlPatterns = {"/"}, filterName = "headerFilter")
public class HeaderFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper(request);
      	//生成唯一的字符串traceId
        String traceId = "traceId_" + UniqueStrCreator.getRandomStr();
      	//将traceId放到Header中
        requestWrapper.addHeader(LogConstant.TRACE_ID, traceId);
				//MDC加入相关参数,便于日志打印
        MDC.put(LogConstant.TRACE_ID, traceId);
        MDC.put("ip", IPUtils.getIpFromRequest(request));
        MDC.put("uri", request.getRequestURI());

        filterChain.doFilter(requestWrapper, servletResponse);
    }

    @Override
    public void destroy() {
    }
}
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;

@Slf4j
public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
    /**
     * construct a wrapper for this request
     *
     * @param request
     */
    public HeaderMapRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    private Map<String, String> headerMap = new HashMap<>();

    /**
     * add a header with given name and value
     *
     * @param name
     * @param value
     */
    public void addHeader(String name, String value) {
        headerMap.put(name, value);
    }

    @Override
    public String getHeader(String name) {
        //log.info("getHeader --->{}",name);
        String headerValue = super.getHeader(name);
        if (headerMap.containsKey(name)) {
            headerValue = headerMap.get(name);
        }
        return headerValue;
    }

    /**
     * get the Header names
     */
    @Override
    public Enumeration<String> getHeaderNames() {
        List<String> names = Collections.list(super.getHeaderNames());
        for (String name : headerMap.keySet()) {
            names.add(name);
        }
        return Collections.enumeration(names);
    }

    @Override
    public Enumeration<String> getHeaders(String name) {
        List<String> values = Collections.list(super.getHeaders(name));
        if (headerMap.containsKey(name)) {
            values = Arrays.asList(headerMap.get(name));
        }
        return Collections.enumeration(values);
    }
}

下游模块

新增一个拦截器或者切面,从request中取出traceId并放入MDC

//从Header中获取traceId
String traceId = request.getHeader("traceId");
MDC.put(LogConstant.TRACE_ID, traceId);

模块之间调用使用feign,需要新增一个Request拦截器

import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;

/**
 * 拦截请求Request
 *
 */
@Slf4j
@Configuration
public class FeignConfiguration implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
          //有可能是获取不到attributes
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        Enumeration<String> headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);
            }
            //log.info("feign interceptor header:{}", template);
        }
    }
}

logback配置

    <appender name="INFO_LOG"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <FileNamePattern>${path}/%d{yyyy-MM-dd}/info_%i.log
            </FileNamePattern>
            <MaxHistory>365</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!--文件达到 最大128MB时会被切割 -->
                <maxFileSize>128 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <pattern>%date [%X{traceId}] [%thread] %-5level %logger{0} [%file:%line] [%X{userId}] - %msg%n
            </pattern>
            <charset>utf8</charset>
        </encoder>
    </appender>

参考文章:https://blog.csdn.net/yangcheng33/article/details/80796129

posted @ 2019-11-26 15:52  忽如一夜春风来?  阅读(2240)  评论(0编辑  收藏  举报