SpringBoot日志跟蹤

1. 背景

隨著分布式的發(fā)展,開發(fā)問(wèn)題排查過(guò)程的日志分析逐漸復(fù)雜,現(xiàn)在一般通過(guò)ELK系統(tǒng)整合分布式系統(tǒng)的所有日志進(jìn)行分析,此時(shí)需要一個(gè)全局的日志id進(jìn)行鏈路跟蹤。

2. 解決思路

2.1 系統(tǒng)內(nèi)部

Log4j和Logback提供MDC,可以實(shí)現(xiàn)基于ThreadLocal級(jí)別的數(shù)據(jù)存儲(chǔ),從而實(shí)現(xiàn)系統(tǒng)內(nèi)部同一線程日志打印的traceId一致性

備注:如需在系統(tǒng)內(nèi)的多線程實(shí)現(xiàn)日志一致性,則可以通過(guò)進(jìn)行參數(shù)傳遞方式傳輸traceId

2.2 跨系統(tǒng)

  • Feign調(diào)用SpringBoot項(xiàng)目

    由于Feign訪問(wèn)其他服務(wù)時(shí)Hystrix會(huì)使用線程池方式進(jìn)行調(diào)用,故需要實(shí)現(xiàn)traceId跨線程、跨系統(tǒng)傳輸。跨系統(tǒng)傳輸通過(guò)請(qǐng)求頭方式傳輸,跨線程則使用

  • 異構(gòu)系統(tǒng)

    直接通過(guò)請(qǐng)求頭傳輸traceId

3. 實(shí)現(xiàn)思路

微信圖片_20211211091435.png

3.1 服務(wù)內(nèi)部日志跟蹤

  • 網(wǎng)關(guān)生成traceId
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 獲取請(qǐng)求對(duì)象request
        ServerHttpRequest request = exchange.getRequest();
        // 獲取響應(yīng)對(duì)象response
        ServerHttpResponse response = exchange.getResponse();
        // 請(qǐng)求頭加入traceId
        long nanoTime = System.nanoTime();
        request.mutate().header("traceId", String.valueOf(nanoTime)).build();
        
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
    
}
  • 服務(wù)過(guò)濾器攔截請(qǐng)求頭加入MDC
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.slf4j.MDC;
import org.springframework.context.annotation.Configuration;

import com.alibaba.fastjson.JSON;
import com.xxx.common.utils.LogUtils;

@Configuration
public class RequestHeaderFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String sTraceId = req.getHeader("traceId");
        MDC.put("traceId", sTraceId);
        chain.doFilter(request, response);
    }
}

日志配置xml中使用[%X{traceId}]引用日志id

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <property name="log_dir" value="/data/logs/demo"/>
    <property name="maxHistory" value="30" />
    
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} -%msg%n</pattern>
        </encoder>
    </appender>
    
    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/info-log.log</fileNamePattern>
            <maxHistory>${maxHistory}</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%green(%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n)</pattern>
        </encoder>
    </appender>
    
    <appender name="demoLog" 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.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${log_dir}/%d{yyyy-MM-dd}/demo.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>30GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId}] - %msg%n</pattern>
        </encoder>
    </appender>  
    
    <logger name="demoLog" additivity="false" level="INFO">
        <appender-ref ref="demoLog"/>
    </logger>

    <root level="INFO">
        <appender-ref ref="INFO"/>
    </root>

</configuration>

當(dāng)前服務(wù)中相同線程打印日志都會(huì)帶上日志id,如需要使用線程池或異步線程處理業(yè)務(wù),請(qǐng)自行傳入?yún)?shù)或進(jìn)行線程間的數(shù)據(jù)傳輸

3.2 跨服務(wù)Feign調(diào)用跟蹤

先使用過(guò)濾器從請(qǐng)求頭獲取traceId,存儲(chǔ)到HystrixRequestVariableDefault中,

然后攔截器中獲取到traceId,加入RequestTemplate的請(qǐng)求頭中

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableDefault;

import feign.RequestInterceptor;
import feign.RequestTemplate;

@Configuration
public class FeignConfigContext {
    private static final HystrixRequestVariableDefault<String> varTraceId = new HystrixRequestVariableDefault<>();

    public static HystrixRequestVariableDefault<String> getVariableInstance() {
        return varTraceId;
    }

    @Bean
    public RequestInterceptor hystrixInterceptor() {
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                String traceId = FeignConfigContext.getVariableInstance().get();
                requestTemplate.header("traceId", traceId);
            }
        };

    }

    @Bean
    public FilterRegistrationBean<Filter> hystrixFilter() {
        FilterRegistrationBean<Filter> filter = new FilterRegistrationBean<>();
        filter.setFilter(new Filter() {
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
                HttpServletRequest req = (HttpServletRequest) request;
                String traceId = req.getHeader("traceId");
                if (!HystrixRequestContext.isCurrentThreadInitialized()) {
                    HystrixRequestContext.initializeContext();
                }
                HystrixRequestVariableDefault<String> variable = FeignConfigContext.getVariableInstance();
                variable.set(traceId);
                try {
                    chain.doFilter(request, response);
                } catch (Exception e) {
                    if (HystrixRequestContext.isCurrentThreadInitialized()) {
                        HystrixRequestContext.getContextForCurrentThread().shutdown();
                    }
                }
            }

        });
        filter.addUrlPatterns("/*");
        return filter;

    }

}

4. 總結(jié)

本教程通過(guò)過(guò)濾器、攔截器結(jié)合HystrixRequestVariableDefault實(shí)現(xiàn)系統(tǒng)內(nèi)和跨系統(tǒng)的日志跟蹤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容