springboot怎样设置全局的traceId(包括MQ)

发布时间:2024年01月14日

一、Controller打印TraceId

1、拦截所有的controller,输入输出将traceId放入MDC中:

package com.perkins.ebicycle.mobile.trace;

import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;

@Component
@Aspect
public class TraceIdAspect {

	private static final Logger logger = LoggerFactory.getLogger(TraceIdAspect.class);

	/**
	 * 日志跟踪标识
	 */
	private static final String TRACE_ID = "TraceId";

	/**
	 * 
	* @Title: controllerPointCut
	* @Description: 拦截所有controller入口下所有的 public方法
	* @throws
	 */
    @Pointcut("execution(public * com.perkins..*.controller..*(..))")
    public void controllerPointCut() {
    }
    
    /**
     * 
    * @Title: consumerPointcut
    * @Description: 拦截listener入口下所有的 public方法,用于mq的traceId
    * @throws
     */
    @Pointcut("execution(public * com.perkins..*.listener..*(..))")
    public void consumerPointcut() {}

    /**
     * 
    * @Title: controllerAround
    * @Description: 拦截controller方法处理
    * @param point
    * @return
    * @throws Throwable
    * @throws
     */
    @Around("controllerPointCut()")
	public Object controllerAround(ProceedingJoinPoint point) throws Throwable {
    	long startTime = System.currentTimeMillis();
    	ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
		HttpServletRequest request = attributes.getRequest();
		//设置traceId
		setTraceId(request);
    	 // 开始打印请求日志
    	printRequestLog(point,request);
    	//执行方法
		Object result = point.proceed();
		//打印出参
		printResponseLog(result);
		logger.info("------------- controllerAround 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
		// 移除 MDC
		MDC.remove(TRACE_ID);
		return result;
	}
    
    /**
     * 
    * @Title: consumerAround
    * @Description: 拦截消费者方法处理
    * @param point
    * @return
    * @throws Throwable
    * @throws
     */
    @Around("consumerPointcut()")
	public void consumerAround(ProceedingJoinPoint point) throws Throwable {
		long startTime = System.currentTimeMillis();
		Object[] args = point.getArgs();
		String traceId=null;
		if(args!=null && args.length>0) {
			Object arg=args[0];
			JSONObject obj=JSONObject.parseObject(arg.toString());
			if(obj.containsKey("ext")) {
				JSONObject extJson=obj.getJSONObject("ext");
				if(extJson.containsKey(TRACE_ID)) {
					traceId=extJson.getString(TRACE_ID);
				}
			}
			
		}
		if(StringUtils.isBlank(traceId)) {
			traceId=UUID.randomUUID().toString();
		}
		// 添加 MDC
		MDC.put(TRACE_ID, traceId);
		logger.info("========================================== Start ==========================================");
		logger.info("====Request Args====: {}", JSONObject.toJSONString(args));
		point.proceed();
		// 移除 MDC
		MDC.remove(TRACE_ID);
		logger.info("========================================== End ==========================================");
		logger.info("------------- consumerAround 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);
	}


	/**
     * 
    * @Title: printResponseLog
    * @Description:打印出参
    * @param result
    * @throws
     */
	private void printResponseLog(Object result) {
		logger.info("====Response Args====: {}", JSONObject.toJSONString(result));
		logger.info("========================================== End ==========================================");

	}

	/**
     * 
    * @Title: setTraceId
    * @Description: 设置traceId
    * @param request
    * @throws
     */
    private void setTraceId(HttpServletRequest request) {
    	String traceId = null;
		if (request != null && request.getHeader(TRACE_ID) != null) {
			traceId = request.getHeader(TRACE_ID);
		} else {
			traceId = UUID.randomUUID().toString();
		}
		// 添加 MDC
		MDC.put(TRACE_ID, traceId);
	}

	/**
     * 
    * @Title: printRequestLog
    * @Description: 打印入参
    * @param point
    * @param request
    * @throws
     */
	private void printRequestLog(ProceedingJoinPoint point, HttpServletRequest request) {
		
		// 打印请求相关参数
		logger.info("========================================== Start ==========================================");
		// 打印请求 url
		logger.info("====URL====: {}", request.getRequestURL().toString());
		// 打印 Http method
		logger.info("====HTTP Method====: {}", request.getMethod());
		// 打印调用 controller 的全路径以及执行方法
		logger.info("====Class Method====: {}.{}", point.getSignature().getDeclaringTypeName(),
				point.getSignature().getName());
		// 打印请求的 IP
		logger.info("====IP====: {}", request.getRemoteAddr());
		// 打印请求入参
		Object[] args = point.getArgs();
		List<Object> stream = ArrayUtils.isEmpty(args) ? Lists.newArrayList() : Arrays.asList(args);
		List<Object> logArgs = stream.stream()
				.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)))
				.collect(Collectors.toList());
		// 过滤后序列化无异常
		logger.info("====Request Args====: {}", JSONObject.toJSONString(logArgs));
	}
}

2、将traceId放入header通过feign往下个服务传值

package com.perkins.ebicycle.mobile.trace;


import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import com.perkins.ebicycle.common.util.StringUtils;

import feign.RequestInterceptor;

@Component
public class FeignLogInterceptor implements RequestInterceptor {
	
	/**
	 * 日志跟踪标识
	 */
	private static final String TRACE_ID = "TraceId";
	
	@Override
	public void apply(feign.RequestTemplate template) {
		String traceId = MDC.get(TRACE_ID);
		if (StringUtils.isEmpty(traceId)) {
			traceId = StringUtils.uuid();
		}
		template.header(TRACE_ID, traceId);
	}
}

二、将traceId在多线程之间打印

1、拿到主线程上下文

package com.perkins.ebicycle.mobile.trace;

import java.util.Map;

import org.slf4j.MDC;
import org.springframework.core.task.TaskDecorator;

public class MdcTaskDecorator implements TaskDecorator {
	/**
	 * 使异步线程池获得主线程的上下文
	 * 
	 * @param runnable
	 * @return
	 */
	@Override
	public Runnable decorate(Runnable runnable) {
		Map<String, String> map = MDC.getCopyOfContextMap();
		return () -> {
			try {
				MDC.setContextMap(map);
				runnable.run();
			} finally {
				MDC.clear();
			}
		};
	}
}

2、将traceId放入多线程中

package com.perkins.ebicycle.mobile.trace;

import java.util.concurrent.ThreadPoolExecutor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

/**
 * 
* @ClassName: ThreadPoolConfig
* @Description: 线程池配置
* @author dingjy
* @date 2024年1月2日 上午11:23:57
 */
@EnableAsync
@Configuration
public class ThreadPoolConfig {
	private int corePoolSize = 50;
	private int maxPoolSize = 200;
	private int queueCapacity = 1000;
	private int keepAliveSeconds = 300;

	@Bean(name = "threadPoolTaskExecutor")
	public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setMaxPoolSize(maxPoolSize);
		executor.setCorePoolSize(corePoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.setKeepAliveSeconds(keepAliveSeconds);
		executor.setTaskDecorator(new MdcTaskDecorator());
		// 线程池对拒绝任务(无线程可用)的处理策略
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		return executor;
	}
}

三、设置logback统一日志格式

<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{TraceId}] [%thread] %-5level %logger{50} %L - %msg%n</pattern>

四、在MQ中设置TraceId

1、将traceI放入mq的ext扩展对象中传递(MQ),也可以做mq拦截,放在拦截器中实现,由生产者实现

package com.perkins.notice.common.vo;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

import io.swagger.annotations.ApiModelProperty;

/**
 */
public class MqVO implements Serializable {
	@NotBlank(message = "topic不能为空")
	@ApiModelProperty(name = "topic", notes = "topic")
	private String topic;
	@NotNull(message = "tag不能为空")
	@ApiModelProperty(name = "tag", notes = "tag")
	private String tag;
	@ApiModelProperty(name = "mq消息体", notes = "mq消息体")
	private String body;

	@ApiModelProperty("msgId")
	private String msgId;
	@ApiModelProperty("key")
	private String key;
	@NotBlank(message = "appName不能为空")
	private String appName;

	public String getAppName() {
		return appName;
	}

	public void setAppName(String appName) {
		this.appName = appName;
	}

	/**
	 * 扩展属性json, 供其他组件加入
	 */
	private Map<Object, Object> ext =new HashMap();

	public String getKey() {
		return key;
	}

	public void setKey(String key) {
		this.key = key;
	}

	public String getTopic() {
		return topic;
	}

	public void setTopic(String topic) {
		this.topic = topic;
	}

	public String getTag() {
		return tag;
	}

	public void setTag(String tag) {
		this.tag = tag;
	}





	public String getBody() {
		return body;
	}

	public void setBody(String body) {
		this.body = body;
	}

	public String getMsgId() {
		return msgId;
	}

	public void setMsgId(String msgId) {
		this.msgId = msgId;
	}

	public Map<Object, Object> getExt() {
		return ext;
	}

	public void setExt(Map<Object, Object> ext) {
		this.ext = ext;
	}
}
package com.perkins.notice.api.web.controller;

import java.util.UUID;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.perkins.notice.common.vo.MqVO;

/**
 * @ClassName: BaseController
 * @Description: 基础控制器
 * @Author: Xiaoqiuping
 * @Date: 2023-12-21 11:24
 **/
public class BaseController {
	
	 Logger logger = LoggerFactory.getLogger(BaseController.class);
	 
	/**
	 * 日志跟踪标识
	 */
	private static final String TRACE_ID = "TraceId";

	public void setTraceId(MqVO mqVo) {
		ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder
				.getRequestAttributes();
		HttpServletRequest request = servletRequestAttributes.getRequest();
		String traceId = request.getHeader(TRACE_ID);
		logger.info("traceId:{}", traceId);
		if (StringUtils.isBlank(traceId)) {
			traceId = UUID.randomUUID().toString();
		}
		mqVo.getExt().put(TRACE_ID, traceId);
	}

}
package com.perkins.notice.api.web.controller;

import javax.validation.Valid;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.perkins.notice.biz.service.IProxySmsService;
import com.perkins.notice.biz.service.MQSenderService;
import com.perkins.notice.biz.service.RobotSenderService;
import com.perkins.notice.common.dto.SmsInfoDTO;
import com.perkins.notice.common.util.CommonResult;
import com.perkins.notice.common.vo.MqVO;
import com.perkins.notice.common.vo.RobotSendVO;
import com.perkins.notice.common.vo.SmsInfoVO;

import io.swagger.annotations.ApiOperation;

/**
 * 
* @ClassName: ProxyController
* @Description: 通知controller
* @author dingjy
* @date 2024年1月13日 上午9:59:41
 */
@RestController
@RequestMapping("/proxy/api")
public class ProxyController extends BaseController{

    protected  final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private IProxySmsService smsService;
    @Autowired
    private MQSenderService mqSenderService;
    @Autowired
    private RobotSenderService robotSenderService;
   
    @ApiOperation(value="短信发送接口", notes=" 普通短信发送 -> SmsInfo")
    @PostMapping("/send/sms")
    @ResponseBody
    public CommonResult sendSms(@Valid @RequestBody SmsInfoVO smsInfo){
        SmsInfoDTO dto = new SmsInfoDTO();
        BeanUtils.copyProperties(smsInfo, dto);
    	this.smsService.sendSms(dto);
    	return CommonResult.success("短信发送成功");
    }

    @ApiOperation(value="MQ发送接口", notes=" MQ发送接口 -> MQInfo")
    @PostMapping("/send/mq")
    @ResponseBody
    public CommonResult sendMq(@Valid @RequestBody MqVO mqVo){
    	//此处是关键==============================================
    	super.setTraceId(mqVo);
        mqSenderService.send(mqVo);
        return CommonResult.success("MQ发送成功");
    }

    @ApiOperation(value="机器人发送接口", notes=" 机器人发送接口")
    @PostMapping("/send/robot")
    @ResponseBody
    public CommonResult sendRobot(@Valid @RequestBody RobotSendVO robotSendVo){
        robotSenderService.send(robotSendVo);
        return CommonResult.success("机器人发送成功");
    }


}

2、消费者拦截器,将生产者发送的msg中ext的TraceId打印出来,代码同1.1中的如图所示:

文章来源:https://blog.csdn.net/DJYDFT2831djydft/article/details/135569043
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。