栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Cloud 全链路日志追踪实现

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Cloud 全链路日志追踪实现

一、Spring Cloud 全链路日志追踪实现

基本实现原理:

  1. 过滤所有请求:有Request-No请求头则获取请求号,没有请求头则设置请求头
  2. feign远程调用添加过滤器,自动赋值请求头
  3. 以上两步能够完成当前线程与子线程 参数传递,对于线程池则无能为力。对于线程池则需要手动设置于释放
1. 过滤并设置请求头

对于不携带Request-No的请求,则生成并添加请求头,添加请求头需要包装请求对象

package com.aimilin.common.security.filter;


import com.aimilin.common.core.consts.CommonConstant;
import com.aimilin.common.core.context.requestno.RequestNoContext;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;


public class RequestNoFilter implements Filter {

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

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            // 生成唯一请求号uuid
            String requestNo = this.getRequestNo(request);
            request = this.setRequestHeader(request, requestNo);

            // 增加响应头的请求号
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.addHeader(CommonConstant.REQUEST_NO_HEADER_NAME, requestNo);

            // 临时存储
            RequestNoContext.set(requestNo);
            MDC.put(CommonConstant.TRACE_ID, requestNo);

            // 放开请求
            chain.doFilter(request, response);

        } finally {
            // 清除临时存储的唯一编号
            RequestNoContext.clear();
            MDC.remove(CommonConstant.TRACE_ID);
        }
    }

    
    private ServletRequest setRequestHeader(ServletRequest request, String requestNo) {
        // 没有包含请求号,则将请求号添加到请求头中
        if(request instanceof HttpServletRequest && StringUtils.isBlank(((HttpServletRequest) request).getHeader(CommonConstant.REQUEST_NO_HEADER_NAME))){
            HeaderMapRequestWrapper requestWrapper = new HeaderMapRequestWrapper((HttpServletRequest) request);
            requestWrapper.addHeader(CommonConstant.REQUEST_NO_HEADER_NAME, requestNo);
            return requestWrapper;
        }
        return request;
    }

    
    private String getRequestNo(ServletRequest request){
        if(request instanceof HttpServletRequest){
            String requestNo = ((HttpServletRequest) request).getHeader(CommonConstant.REQUEST_NO_HEADER_NAME);
            if(StringUtils.isNotBlank(requestNo)){
                return requestNo;
            }
        }
        return UUID.randomUUID().toString();
    }

    @Override
    public void destroy() {

    }

}

包装请求对象:

package com.aimilin.common.security.filter;

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


public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
    public HeaderMapRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    
    private Map headerMap = new HashMap();

    
    public void addHeader(String name, String value) {
        headerMap.put(name, value);
    }

    
    @Override
    public String getHeader(String name) {
        String headerValue = super.getHeader(name);
        if (headerMap.containsKey(name)) {
            headerValue = headerMap.get(name);
        }
        return headerValue;
    }

    
    @Override
    public Enumeration getHeaderNames() {
        List names = Collections.list(super.getHeaderNames());
        names.addAll(headerMap.keySet());
        return Collections.enumeration(names);
    }

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

经过请求头过滤器那么所有请求都会携带上Request-No请求头, 响应也会携带上Request-No

2. Feigin远程调用,自动复制请求头
package com.aimilin.common.feign.inteceptor;

import com.aimilin.common.core.utils.JsonUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

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


@Slf4j
public class FeignRequestInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate requestTemplate) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            Enumeration headerNames = request.getHeaderNames();
            Optional.ofNullable(headerNames).ifPresent(headers -> {
                while (headers.hasMoreElements()) {
                    String name = headers.nextElement();
                    String value = request.getHeader(name);

                    // 跳过 content-length
                    if (name.equals("content-length")){
                        continue;
                    }

                    requestTemplate.header(name, value);
                    log.trace(">>> feign header set {}:{}", name, value);
                }
            });
        }
        printLog(requestTemplate);
    }

    private void printLog(RequestTemplate requestTemplate) {
        if (log.isInfoEnabled()) {
            Target target = requestTemplate.feignTarget();
            log.info("Feign Request:n     app: {}n   class: {}n  method: {}n     url: {}n   param: {}n",
                    target.name(), target.type().getName(), requestTemplate.method(),
                    requestTemplate.url(),
                    this.getParam(requestTemplate)
            );
        }
    }

    private String getParam(RequestTemplate requestTemplate) {
        if (RequestMethod.GET.name().equals(requestTemplate.method())) {
            return JsonUtil.toJson(requestTemplate.queries());
        }
        return Objects.isNull(requestTemplate.body()) ? "" : new String(requestTemplate.body());
    }
}

对于线程池中执行的任务还是不能携带MDC和请求对象,因为RequestContextHolder也只能在当前线程与子线程中使用Request对象;

3. 线程池中使用MDC与Request对象

其原理就是任务执行前复制好变量,结束之后再删除变量。这里使用了alibaba TransmittableThreadLocal 线程池支持库;

import com.alibaba.ttl.TtlCallable;
import com.alibaba.ttl.TtlRunnable;
import lombok.NonNull;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

import static java.util.Optional.ofNullable;


public class TtlThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
    @NonNull
    @Override
    public void execute(@NonNull Runnable task) {
        super.execute(wrap(task));
    }

    @NonNull
    @Override
    public Future submit(@NonNull Runnable task) {
        return super.submit(wrap(task));
    }

    @NonNull
    @Override
    public  Future submit(@NonNull Callable task) {
        return super.submit(wrap(task));
    }

    @NonNull
    @Override
    public ListenableFuture submitListenable(@NonNull Runnable task) {
        return super.submitListenable(wrap(task));
    }

    @NonNull
    @Override
    public  ListenableFuture submitListenable(@NonNull Callable task) {
        return super.submitListenable(wrap(task));
    }

    
    private  Callable wrap(Callable task) {
        Optional requestOptional = ofNullable(RequestContextHolder.getRequestAttributes());
        Optional> mdcOptional = ofNullable(MDC.getCopyOfContextMap());
        return () -> {
            try {
                requestOptional.ifPresent(RequestContextHolder::setRequestAttributes);
                mdcOptional.ifPresent(MDC::setContextMap);
                return TtlCallable.get(task).call();
            } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }

    
    private Runnable wrap(Runnable task) {
        Optional requestOptional = ofNullable(RequestContextHolder.getRequestAttributes());
        Optional> mdcOptional = ofNullable(MDC.getCopyOfContextMap());
        return () -> {
            try {
                requestOptional.ifPresent(RequestContextHolder::setRequestAttributes);
                mdcOptional.ifPresent(MDC::setContextMap);
                TtlRunnable.get(task).run();
            } finally {
                MDC.clear();
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

异步现成初始化

 
    @Lazy
    @Bean(name = { TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME,
            AsyncAnnotationBeanPostProcessor.DEFAULT_TASK_EXECUTOR_BEAN_NAME })
    public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = builder.configure(new TtlThreadPoolTaskExecutor());
        // CALLER_RUNS:调用者所在的线程来执行
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return threadPoolTaskExecutor;
    }

经过上面几部,基本完成链路日志追踪,接下来是设置MDC

4. 设置日志MDC

添加配置:主要是设置控制台日志格式与文件日志格式。






    
    

使用方式:
参考:https://docs.spring.io/spring-boot/docs/2.6.x/reference/htmlsingle/#using



    
    
    
    
    
        
    
    

打印出来日志格式:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1007886.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号