背景
项目环境没有接入skywalking等apm监控,也没有日志平台查看日志,这两天代码开发,只能通过线上机器日志去查询。如果日志刷屏、方法链路长,通过grep无法查询到整个请求的链路,严重影响效率。
文章目标:
- 了解主流APM日志插件集成
- 实现自己的日志插件记录traceId
- todo,细节的优化
日志插件集成
以skywalking 为例,如何在日志中,将链路traceId输出。
- Dependency the toolkit, such as using maven or gradle
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-log4j-2.x</artifactId>
<version>{project.release.version}</version>
</dependency>
- Config the
[%traceId]
pattern in your log4j2.xml
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d [%traceId] %-5p %c{1}:%L - %m%n"/>
</Console>
</Appenders>
```
通过上面两步,我们就可以将traceId 集成到我们的打印日志中。其他APM 如pinpoint等,基本一致。
看下代码,发现很简单,就两个类
```java
@Plugin(
name = "TraceIdConverter",
category = "Converter"
)
@ConverterKeys({"traceId"})
public class TraceIdConverter extends LogEventPatternConverter {
protected TraceIdConverter(String name, String style) {
super(name, style);
}
public static TraceIdConverter newInstance(String[] options) {
return new TraceIdConverter("traceId", "traceId");
}
public void format(LogEvent event, StringBuilder toAppendTo) {
Log4j2OutputAppender.append(toAppendTo);
}
}
public class Log4j2OutputAppender {
public Log4j2OutputAppender() {
}
public static void append(StringBuilder toAppendTo) {
toAppendTo.append("TID: N/A");
}
}
代码很简单,通过log4j插件,实现一套获取traceId 的机制,log4j再打印日志的时候,会将日志内容中拼接上对应标识位。
自己动手实现
看了skywalking的代码,我们其实也可以动手实现一个自己的日志集成traceId组件。只是我们要在Log4j2OutputAppender中自己实现一套获取traceId的机制。
详细代码可以看git代码
public class YmLogAppender {
//todo remove
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public YmLogAppender() {
}
public static void append(StringBuilder toAppendTo) {
String tid = threadLocal.get();
if (StringUtils.isBlank(tid)){
tid= UUID.randomUUID().toString();
threadLocal.set(tid);
}
toAppendTo.append("TID:"+tid);
}
}
日志文件中,加入[%traceId]即可
<PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p][%traceId] - %l - %m%n"/>
看下效果
通过grep tid 就可以将整个请求的链路的日志全都打印出来。
后记
简单的demo已经完成,并能提升一定的开发效率。但是细节上还有可以改进的点。
-
使用threadLocal存储上下文的日志信息,得考虑跨线程请情况
可以通过transmittable-thread-local 完成父子线程上下文的透传
-
threadLocal WeakReference问题
添加拦截器,拦截应用入口方法,在方法结束时,完成remove操作
-
跨应用透传
在RPC上下文中,存储当前请求的tid.通过RPC拦截器,获取上下文,进行传递
题外话,现在一些云厂商的链路追踪插件,通过javaagent集成,可以无编码完成traceId的打印,以及请求traceId 的接口返回,功能更加强大。但是通过查看skywalking 的代码,我们可以清晰了解实现的思路,后续我们可以自定义添加标识字段到我们的日志中,方便查看。