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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM