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

一、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.mfbz.cn/a/319752.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深思熟虑可能性模型介绍与使用

深思熟虑可能性模型介绍与使用 如何联系我 作者&#xff1a;鲁伟林 邮箱&#xff1a;thinking_fioa163.com或vlinyes163.com 版权声明&#xff1a;文章和记录为个人所有&#xff0c;如果转载或个人学习&#xff0c;需注明出处&#xff0c;不得用于商业盈利行为。 背景 20…

操作系统详解(5.1)——信号(Signal)的相关题目

系列文章&#xff1a; 操作系统详解(1)——操作系统的作用 操作系统详解(2)——异常处理(Exception) 操作系统详解(3)——进程、并发和并行 操作系统详解(4)——进程控制(fork, waitpid, sleep, execve) 操作系统详解(5)——信号(Signal) 文章目录 题目第一问第二问第三问 题目…

ES搜索的安装以及常用的增删改查操作(已经写好json文件,可以直接使用)

1.es的下载 https://www.elastic.co/cn/downloads/past-releases 2.elasticsearch安装及配置&#xff0c;遇到9200访问不了以及中文乱码&#xff0c;能访问了却要账户密码等问题 Elasticsearch启动后访问9200失败_http://localhost:9200无返回值-CSDN博客 3.开启es服务&#x…

Qat++,轻量级开源C++ Web框架

目录 一.简介 二.编译Oat 1.环境 2.编译/安装 三.试用 1.创建一个 CMake 项目 2.自定义客户端请求响应 3.将请求Router到服务器 4.用浏览器验证 一.简介 Oat是一个面向C的现代Web框架 官网地址&#xff1a;https://oatpp.io github地址&#xff1a;https://github.co…

Error: L6218E: Undefined symbol 系列错误汇总 (referred from main.o)

传送门 错误1&#xff1a; Undefined symbol(referred from main.o)错误2&#xff1a;Undefined_symbol _use_two_region memory 错误1&#xff1a; Undefined symbol(referred from main.o) Cube_GPIO\Cube_GPIO.axf: Error: L6218E: Undefined symbol LED_GPIO_Init (referr…

15个为你的品牌增加曝光的维基百科推广方法-华媒舍

维基百科是全球最大的免费在线百科全书&#xff0c;拥有庞大的用户群体和高质量的内容。在如今竞争激烈的市场中&#xff0c;利用维基百科推广品牌和增加曝光度已成为许多企业的重要策略。本文将介绍15种方法&#xff0c;帮助你有效地利用维基百科推广品牌&#xff0c;提升曝光…

GPT编程:运行第一个聊天程序

环境搭建 很多机器学习框架和类库都是使用Python编写的&#xff0c;OpenAI提供的很多例子也是Python编写的&#xff0c;所以为了方便学习&#xff0c;我们这个教程也使用Python。 Python环境搭建 Python环境搭建有很多种方法&#xff0c;我们这里需要使用 Python 3.10 的环境…

浅谈对Mybatis的理解

一、Mybatis的概述 MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code&#xff0c;由谷歌托管&#xff0c;并且改名为MyBatis 。2013年11月迁移到Github。 MyBatis是支持普通SQL查询&#xff0c;存储过程和高级映射的优…

ssm基于Web的课堂管理系统设计与实现论文

目 录 目 录 I 摘 要 III ABSTRACT IV 1 绪论 1 1.1 课题背景 1 1.2 研究现状 1 1.3 研究内容 2 2 系统开发环境 3 2.1 vue技术 3 2.2 JAVA技术 3 2.3 MYSQL数据库 3 2.4 B/S结构 4 2.5 SSM框架技术 4 3 系统分析 5 3.1 可行性分析 5 3.1.1 技术可行性 5 3.1.2 操作可行性 5 3…

数据库(1)

目录 1.什么是数据库 1.1.数据 1.2.数据库 1.3 常见数据库 1.3.1 关系型数据库 1.3.2 非关系型数据库 2.mysql概述 ​编辑 **3.MySQL本地仓库安装 在Linux端操作 4.MySQL网络安装 **方法一&#xff1a;RPM捆绑包 下载安装RPM捆绑包&#xff1a; 在windows端操作&a…

开箱即用的企业级前后端分离【.NET Core6.0 Api + Vue 2.x + RBAC】权限框架-Blog.Core

前言 今天要给大家推荐一个开箱即用的企业级前后端分离【.NET Core6.0 Api Vue 2.x RBAC】权限框架&#xff08;提高生产效率&#xff0c;快速开发就选它&#xff09;&#xff1a;Blog.Core。 推荐原因 Blog.Core通过详细的文章和视频讲解&#xff0c;将知识点各个击破&…

element表格数据,表头上(下)角标,html字符串渲染

1. 问题描述 在动态渲染的element表格中&#xff0c;表头和表中数据是一个含有html的字符串&#xff0c;需要渲染 2. 效果 3. 代码 const columns ref([{ text: 差值<sub>-3</sub> / 10<sup>-6</sup>℃<sup>-1</sup>, value: aallowEr…

三菱FX系列PLC定长切割控制(线缆裁切)

三菱PLC绝对定位指令DDRVA实现往复运动控制详细介绍请查看下面文章链接&#xff1a; https://rxxw-control.blog.csdn.net/article/details/135570157https://rxxw-control.blog.csdn.net/article/details/135570157这篇博客我们介绍线缆行业的定长切割控制相关算法。 未完待…

Xmind 网页端登录及多端同步

好久没用 Xmind 了&#xff0c;前几天登录网页端突然发现没办法登录了&#xff0c;总是跳转到 Xmind AI 页面。本以为他们不再支持网页端了&#xff0c;后来看提示才知道只是迁移到了新的网址&#xff0c;由原来的 xmind.works 现在改成了的 xmind.ai。又花费好长时间才重新登录…

Vue3 移动端自适应方案postcss-px-to-viewport

我的环境 依赖名版本pnpm8.14.0Node16.20.1Vue3.3Vite5.0.8 一、安装 pnpm install postcss-px-to-viewport1.1.1 --save-dev 二、配置 vite.config.ts import postcsspxtoviewport from postcss-px-to-viewportexport default defineConfig({css: {postcss: {plugins: [p…

【HarmonyOS】网络数据请求连接与数据持久化操作

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

QT获取程序编译时间与当前时间的区别及应用场景

一.获取编译时间与当前时间的区别 1.编译日期时间&#xff1a;这个信息通常用于标识某个源代码文件或整个应用程序的编译时间&#xff0c;程序一旦编译出来不会再改变&#xff0c;通常用于记录或跟踪代码的版本和更改历史。 2.运行当前日期时间&#xff1a;这是指程序在运行时…

ssm基于web的电影购票系统+vue论文

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统电影购票信息管理难度大&#xff0c;容错率低&#xff0c…

NI PXIe-6386国产替代,8路AI(16位,14 MS/s/ch),2路A​O,24路DIO,PXI多功能I/O模块

PXIe-6386 PXIe&#xff0c;8路AI&#xff08;16位&#xff0c;14 MS/s/ch&#xff09;&#xff0c;2路A​O&#xff0c;24路DIO&#xff0c;PXI多功能I/O模块 PXIe-6386是一款同步采样的多功能DAQ设备。该模块提供了模拟 I/O、数字I/O、四个32位计数器和模拟和数字触发。板载N…

【Linux】 系统目录结构

进入到根目录 cd /ls目录名具体作用/存放系统系统相关的目录文件/boot放置linux系统内核文件和启动时用到的一些引导文件/home包含linux系统上各用户的主目录&#xff0c;子目录名称默认以该用户名命名/root系统管理员root的家目录/bin包含常用的命令文件&#xff08;如ls 等&a…
最新文章