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

news2025/4/23 21:47:50

一、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中的如图所示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1382385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构【树+二叉树】

目录 线性表和非线性表 树的概念 树的存储表示 二叉树的概念 特殊二叉树 满二叉树 完全二叉树 二叉树的性质 二叉树的存储结构 顺序存储 链式存储 本篇我们开始进入数据结构中【树】的学习。 线性表和非线性表 逻辑结构&#xff1a;人想象出来的物理结构&#xf…

详解HTTPS加密工作过程

&#x1f697;&#x1f697;&#x1f697;今天给大家分享的是HTTPS加密的工作过程。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; ✈️✈…

力扣双周赛第三题----2857. 统计距离为 k 的点对

这题我们的暴力做法就是o(n^2),但是根据数据量这样会超时&#xff0c;所以我们不能用暴力解法去解决 那么想一想双指针可以吗&#xff0c;不可以。为什么呢&#xff1f;因为他没有一个特性可以让他双指针跳过前面或者后面一个点。比如他们数组有顺序的情况下&#xff0c;还有一…

大数据StarRocks(七):数据表创建

1. 基本概念 1.1 Row & Column 一张表包括行&#xff08;Row&#xff09;和列&#xff08;Column&#xff09;。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。 ⚫ 在默认的数据模型中&#xff0c;Column 只分为排序列和非排序列。存储引擎会按照排序列对…

紫光展锐M6780丨画质增强——更炫的视觉体验

智能显示被认为是推动数字化转型和创新的重要技术之一。研究机构数据显示&#xff0c;预计到2035年底&#xff0c;全球智能显示市场规模将达到1368.6亿美元&#xff0c;2023-2035年符合年增长率为36.4%。 随着消费者对高品质视觉体验的需求不断增加&#xff0c;智能手机、平板…

【数据结构与算法】之数组系列-20240114

这里写目录标题 一、414. 第三大的数二、448. 找到所有数组中消失的数字三、561. 数组拆分四、594. 最长和谐子序列 一、414. 第三大的数 简单 给你一个非空数组&#xff0c;返回此数组中第三大的数。如果不存在&#xff0c;则返回数组中最大的数。 示例 1&#xff1a; 输入&…

前端八股文(性能优化篇)

目录 1.CDN的概念 2.CDN的作用 3.CDN的原理 4.CDN的使用场景 5.懒加载的概念 6.懒加载的特点 7.懒加载的实现原理 8.懒加载与预加载的区别 9.回流与重绘的概念及触发条件 &#xff08;1&#xff09;回流 &#xff08;2&#xff09;重绘 10. 如何避免回流与重绘&#…

kylin集群使用nginx反向代理

前文已经提到&#xff0c;我安装了kylin集群。 kylin3集群问题和思考&#xff08;单机转集群&#xff09;-CSDN博客文章浏览阅读151次&#xff0c;点赞3次&#xff0c;收藏6次。由于是同一个集群的&#xff0c;元数据没有变化&#xff0c;所以&#xff0c;直接将原本的kylin使用…

20240106----重返学习-在VMware里给centos7设置静态IP地址

在VMware里给centos7设置静态IP地址 场景 学习nginx中&#xff0c;想要设置静态IP地址&#xff0c;以便让win10主环境中的Xshell里能够连接到VMware中的CentOS7上&#xff0c;进而可以在Xshell里进行操作。可以做到如复制粘贴之类的&#xff0c;而不是在虚拟机中的默认终端上…

Mac安装nvm以及使用nvm安装node

1. 安装nvm命令 git clone https://gitee.com/mirrors/nvm.git ~/.nvm && cd ~/.nvm && git checkout git describe --abbrev0 --tags2. 配置环境变量 vi ~/.bash_profileexport NVM_DIR"$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] &&…

【每日一题】删除排序链表中的重复元素

文章目录 Tag题目来源解题思路方法一&#xff1a;比较相邻两节点 写在最后 Tag 【遍历】【链表】【2024-01-14】 题目来源 83. 删除排序链表中的重复元素 解题思路 方法一&#xff1a;比较相邻两节点 思路 比较两个相邻的节点&#xff0c;如果下一个节点值和当前节点值一样…

设计模式——1_4 外观(Facade)

文章目录 定义图纸一个例子&#xff1a;自动生成一杯茶沏茶的流程组合方式一&#xff1a;直接组合方法二&#xff1a;外观 碎碎念多个外观对象外观和封装外观和单例姑妄言之 定义 为子系统中的一组接口提供一个一致的界面&#xff0c;外观模式定义了一个高层接口&#xff0c;这…

Windows系统Outlook邮件备份导出与导入教程

注意&#xff1a;微软商店UWP版本outlook客户端暂时不支持邮件备份&#xff01;而Microsoft Office2003-目前(2021)中的outlook客户端才支持邮件备份。所以&#xff0c;想要备碧桂园集团邮箱邮件&#xff0c;请安装或者登录Microsoft Office中的outlook客户端以进行邮件的备份。…

Vue3+Vite连接高德地图JS API——地图显示、输入搜索

1 开通高德地图Web端JS API服务 1、进入高德地图API官网&#xff08;https://lbs.amap.com/&#xff09;&#xff1a; 2、注册登录。 3、进入控制台。 4、点击“应用管理”&#xff0c;点击“我的应用”&#xff0c;创建新应用。 5、添加Key&#xff0c;服务平台选择“Web端&…

Mysql数据库高版本向低版本迁移方法

操作步骤 1、首先低版本Mysql创建数据库 2、使用navicat工具&#xff0c;复制高版本数据库的表 3、在低版本数据库中粘贴&#xff0c;弹出数据传输界面&#xff0c;选项去掉包含字符集、包含引擎及表类型 使用该版本实现了Mysql8.0向Mysql5.5的迁移&#xff0c;如果在Mysql8.0生…

VS游戏打包教程

我用得天天酷跑小游戏做的例子 1:安装打包插件 2:在解决方案里新建一个项目 3:新建一个setup项目 4:界面如下(通过右键folder,可以创建folder目录和输出) 5:素材文件 6:素材放完了就项目输出 7:创建快捷方式 右键这个主输出选择第一个create shortcut 8:将这个快捷方式,拖到,…

《最新出炉》系列入门篇-Python+Playwright自动化测试-9-页面(page)

1.简介 通过前边的讲解和学习&#xff0c;细心认真地小伙伴或者童鞋们可能发现在Playwright中&#xff0c;没有Element这个概念&#xff0c;只有Page的概念&#xff0c;Page不仅仅指的是某个页面&#xff0c;例如页面间的跳转等&#xff0c;还包含了所有元素、事件的概念&#…

ESU毅速丨制造企业需不需要建设增材制造中心?

随着科技的不断发展&#xff0c;增材制造技术已经成为制造行业的新宠。越来越多的企业开始考虑建设增材制造中心&#xff0c;以提高生产效率、降低成本、加速产品创新。但是&#xff0c;对于制造企业来说&#xff0c;是否需要建设增材制造中心呢&#xff1f; 首先&#xff0c;我…

EfficientSAM 代码推理

SA网站主页&#xff1a;Segment Anything | Meta AI 论文主页&#xff1a;EfficientSAM 代码地址&#xff1a;https://github.com/yformer/EfficientSAM 官方给的推理代码是CPU版本的&#xff0c;如果想使用GPU推理需要自己修改一下 经过推理测试3090GPU&#xff0c;官方测试…

从字节码角度分析i++与++i的区别

情况一 : 当i与i没有对象接收值时, 没有任何区别 情况二 : 当i与i没有对象接收值时 可以看到 i时, 先把i值10从局部变量表拿到操作数栈(29), 之后执行iinc, 直接修改局部变量表里面的值10修改成11(30), 最后将操作树栈里面的值赋值给i4(33) (由于iinc直接修改的局部变量表里面…