一、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();//设置traceIdsetTraceId(request);// 开始打印请求日志printRequestLog(point,request);//执行方法Object result = point.proceed();//打印出参printResponseLog(result);logger.info("------------- controllerAround 结束 耗时:{} ms -------------", System.currentTimeMillis() - startTime);// 移除 MDCMDC.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();}// 添加 MDCMDC.put(TRACE_ID, traceId);logger.info("========================================== Start ==========================================");logger.info("====Request Args====: {}", JSONObject.toJSONString(args));point.proceed();// 移除 MDCMDC.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();}// 添加 MDCMDC.put(TRACE_ID, traceId);}/*** * @Title: printRequestLog* @Description: 打印入参* @param point* @param request* @throws*/private void printRequestLog(ProceedingJoinPoint point, HttpServletRequest request) {// 打印请求相关参数logger.info("========================================== Start ==========================================");// 打印请求 urllogger.info("====URL====: {}", request.getRequestURL().toString());// 打印 Http methodlogger.info("====HTTP Method====: {}", request.getMethod());// 打印调用 controller 的全路径以及执行方法logger.info("====Class Method====: {}.{}", point.getSignature().getDeclaringTypeName(),point.getSignature().getName());// 打印请求的 IPlogger.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";@Overridepublic 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*/@Overridepublic 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());@Autowiredprivate IProxySmsService smsService;@Autowiredprivate MQSenderService mqSenderService;@Autowiredprivate RobotSenderService robotSenderService;@ApiOperation(value="短信发送接口", notes=" 普通短信发送 -> SmsInfo")@PostMapping("/send/sms")@ResponseBodypublic 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")@ResponseBodypublic CommonResult sendMq(@Valid @RequestBody MqVO mqVo){//此处是关键==============================================super.setTraceId(mqVo);mqSenderService.send(mqVo);return CommonResult.success("MQ发送成功");}@ApiOperation(value="机器人发送接口", notes=" 机器人发送接口")@PostMapping("/send/robot")@ResponseBodypublic CommonResult sendRobot(@Valid @RequestBody RobotSendVO robotSendVo){robotSenderService.send(robotSendVo);return CommonResult.success("机器人发送成功");}}
2、消费者拦截器,将生产者发送的msg中ext的TraceId打印出来,代码同1.1中的如图所示: