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);
}
}
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;
}
}
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{TraceId}] [%thread] %-5level %logger{50} %L - %msg%n</pattern>
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中的如图所示: