分布式日志链路追踪
因为在目前的任务分配上,我们是属于按照微服务上来划分开发小组的,也就是基本上我们都只是负责自己的服务了,在针对自己服务的请求日志上,我用的是aop形式来进行记录,这样可以最大限度的降低代码的入侵性
/**
* 前端接口日志
*/
@Slf4j
@Component
@Aspect
public class FrontLogAspect {
/**
* 记录接口日志
*
* @param proceeding
* @return
*/
@Around(value = "execution(* com.zhaojiafang.gateway.controller.sales.*.*(..))")
public Object log(ProceedingJoinPoint proceeding) throws Throwable {
// 创建流水号
TLocalHelper.createSeq();
Thread thread = Thread.currentThread();
String name = thread.getName();
thread.setName(TLocalHelper.getSeq());
HttpServletRequest request = getRequest();
String args = null;
try {
args = new ObjectMapper().writeValueAsString(proceeding.getArgs());
} catch (JsonProcessingException e) {
e.printStackTrace();
args="";
}
log.info("前端接口请求路径:{},请求参数:{},流水号:{}", request.getRequestURI(), args, TLocalHelper.getSeq());
Object result = proceeding.proceed();
log.info("接口执行结束,返回数据:{},流水号:{}", result.toString(), TLocalHelper.getSeq());
thread.setName(name);
TLocalHelper.closeLocal();
return result;
}
/**
* 获取request
*
* @return
*/
public static HttpServletRequest getRequest() {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
return sra.getRequest();
}
}
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* 线程缓存工具
*
* @author yaojn
*/
public class TLocalHelper {
private static ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<Map<String, String>>();
public static void closeLocal() {
threadLocal.remove();
}
private static class LastTime {
private static long time;
private static int i;
}
/**
* 创建流水
*
* @return
*/
public static String createSeq() {
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
Date now = new Date();
String sequence = null;
long nowSec = now.getTime() / 1000;
synchronized (LastTime.class) {
if (LastTime.time >= nowSec) {
LastTime.i++;
} else {
LastTime.i = 0;
LastTime.time = nowSec;
}
sequence = "25" + df.format(new Date(LastTime.time * 1000))
+ String.format("%04d", LastTime.i);
}
Map<String, String> tlMap = new HashMap<String, String>();
tlMap.put("localseq", sequence);
threadLocal.set(tlMap);
return sequence;
}
/**
* 获取流水
*
* @return
*/
public static String getSeq() {
Map<String, String> map = threadLocal.get();
if (Objects.nonNull(map)) {
return map.get("localseq");
} else {
return "";
}
}
}
ps: 我之前的日志记录都会记录相对应的流水号:log.info("前端接口请求路径:{},请求参数:{},流水号:{}")
然后这段时间又学到了一个概念,就是MDC ,这个MDC 是slf 包下的一个类,他的作用就在于当我们在代码中设置
MDC.put("TRACE_ID", TraceLogUtils.getTraceId());
并且结合
logback 的pattern 设置中添加 [%X{TRACE_ID}]
这样就可以不用在显示的打印出 流水号,而是logback的日志会自动的打印上流水号
当然上面的代码很不完善,因为用threalocal 他并不支持线程上下文切换上traceId 的传递,基于此 可以参考线程上下文切换数值传递的方法,这个有很多的解决方法来进行实现,就不展开说了。
但是在实际的开发或者生成中,其实上面的代码有点缺陷,因为基本都是分布式微服务开发,然后我这个的流水号只是基于当前服务的,这种的方式肯定存在着缺陷,在服务之间调用上不利于链路追踪
因为我需要在 前端用户调用->A->B->C 这种形式下把最开始的A中或者前端生成的traceId 给保存下来并且在调用下游服务的时候也传递过去,所以在总结下service层需要做3件事情
1.接受前一个服务传递过来的traceId
2.日志打印
3.调用下游服务的时候传递traceId;
因此我第一时间想到的就是通过请求头的形式把traceId 放入请求头中进行传递,
因此我定义了2个类
1:定义过滤器,将headler 里面的数据给取出来然后保存到threadlocal里面
private static final String TRACE_ID = "traceId";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
try {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String traceId = httpRequest.getHeader(TRACE_ID);
TraceIdHelper.setTraceId(traceId);
filterChain.doFilter(request, response);
} finally {
// 清除MDC的traceId值,确保当次请求不会影响其他请求
TraceIdHelper.clearTraceId();
}
}
2.定义feign拦截器
在服务之间调用的时候把traceId 通过feign拦截器中设置的请求头设置进行传递
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
requestTemplate.header(TraceIdFilter.TRACE_ID, TraceIdHelper.getTraceId());
}
}
}
在当时我通过feign拦截的时候遇到了一个No thread-bound request found 的异常
在网上找到的答案是:拦截器的实质是因为重开子线程,无法获取到threadlocal 需要进行额外配置
# 更换hystrix策略,解决无法传递threadLocal变量问题
hystrix:
command:
default:
execution:
isolation:
strategy: SEMAPHORE
上面的代码帮我完成了feign调用之间的traceId的传递,但是我们在项目中也用到了rabbitmq 作为服务之间的通信使用,但是基于rabbitmq 目前还没有找到更好的方式来传递数据,目前我能想到的就是在传递消息体的过程中新增个traceId 字段,但是因为这种是涉及到了代码入侵这一块了,所以这个目前没有实现
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南