背景
公司最近引入skywalking(后面都简称:sw),由于之前项目的traceId是在controller用uuid生成的,所以老大想让sw的traceId规则也变成简单的uuid.
思路
- 先了解sw的traceId默认生成规则
- 去网上了解是否有更新默认traceId规则的方式
- 验证各种找到的方式
skywalking traceId 默认生成规则
默认规则:uuid + 线程号 + (时间戳 * 10000 +序列号)
其中uuid是项目启动的时候用来表示当前服务的一个标识。
查看源代码:(查看方式,下载skywalking-agent源码)Downloads | Apache SkyWalking
java
public final class GlobalIdGenerator {
private static final String PROCESS_ID = UUID.randomUUID().toString().replaceAll("-", "");
private static final ThreadLocal<IDContext> THREAD_ID_SEQUENCE = ThreadLocal.withInitial(
() -> new IDContext(System.currentTimeMillis(), (short) 0));
private GlobalIdGenerator() {
}
/**
* Generate a new id, combined by three parts.
* <p>
* The first one represents application instance id.
* <p>
* The second one represents thread id.
* <p>
* The third one also has two parts, 1) a timestamp, measured in milliseconds 2) a seq, in current thread, between
* 0(included) and 9999(included)
*
* @return unique id to represent a trace or segment
*/
public static String generate() {
return StringUtil.join(
'.',
PROCESS_ID,
String.valueOf(Thread.currentThread().getId()),
String.valueOf(THREAD_ID_SEQUENCE.get().nextSeq())
);
}
private static class IDContext {
private long lastTimestamp;
private short threadSeq;
// Just for considering time-shift-back only.
private long lastShiftTimestamp;
private int lastShiftValue;
private IDContext(long lastTimestamp, short threadSeq) {
this.lastTimestamp = lastTimestamp;
this.threadSeq = threadSeq;
}
private long nextSeq() {
return timestamp() * 10000 + nextThreadSeq();
}
private long timestamp() {
long currentTimeMillis = System.currentTimeMillis();
if (currentTimeMillis < lastTimestamp) {
// Just for considering time-shift-back by Ops or OS. @hanahmily 's suggestion.
if (lastShiftTimestamp != currentTimeMillis) {
lastShiftValue++;
lastShiftTimestamp = currentTimeMillis;
}
return lastShiftValue;
} else {
lastTimestamp = currentTimeMillis;
return lastTimestamp;
}
}
private short nextThreadSeq() {
if (threadSeq == 10000) {
threadSeq = 0;
}
return threadSeq++;
}
}
}
网上查询到的方式
1. skywalking traceId如何自定义生成规则?_问答-阿里云开发者社区
调研结果:评论说是ai回答的,乱答的,没用。
2. 【SkyWalking】如何自定义TraceId文章介绍了在SkyWalking中,如何基于SW8协议自定义TraceI - 掘金
调研结果:这篇文章说的太简单了,太抽象了,而且基于sw8协议的更改,链路收集到的数据会有问题。
实测:链路的跨度会有问题,所以放弃。
问大佬了
实在没办法了,就放技术群里问了下,有个好心老哥帮我看了下。
主要思路是:在我们项目里自己弄一个和GlobalIdGenerator
类的全路径一模一样的,然后根据父委托机制可以重写我们项目里自己的GlobalIdGenerator
类。
这个有点奇怪,我直接在idea里运行时可以覆盖掉skywalking-agent里的GlobalIdGenerator
类的,但是将项目打成jar包就覆盖不了了。
修改源码
直接将下载下来的skywalking-agent源码进行修改。
很简单只需要修改GlobalIdGenerator
中的generate方法即可。
java
public static String generate() {
return UUID.randomUUID().toString().toUpperCase();
}
然后重新打包。
这种方式是可以的,但是老大说尽量不要改动源码的方式去实现。
再写一个探针
主要思路:在同事的提醒下,他说你可以自己再写一个探针,然后控制自己写的探针和skywalking探针的加载顺序,然后在自己写的那个探针里定义好一个GlobalIdGenerator
类中的generate
方法重写。
经过验证,这种方式是可以完成自定义traceId规则的,但是其实还是修改了源码,只不过是字节码层面的源码,老大最后说还是用sw的默认traceId。
贴一下自己实现探针的源码:
java
public class TraceIdAgent {
public static void premain(String agentArgs, Instrumentation inst) {
new AgentBuilder.Default()
// 找到以GlobalIdGenerator结尾的类全限定名
.type(ElementMatchers.nameEndsWith("GlobalIdGenerator"))
.transform((builder, type, classLoader, module, protectionDomain) ->
// 拦截到里面的 generate方法
builder.method(ElementMatchers.named("generate"))
// 替换成这个
.intercept(MethodDelegation.to(Interceptor.class)))
.installOn(inst);
}
// 重写我们的目标方法
public static class Interceptor {
public static String generate() {
return java.util.UUID.randomUUID().toString().replaceAll("-", "");
}
}
}
总结
简单的讲述了几种可以自定义的traceId的方法:
- 修改sw8跨进程头部协议,这个方式会让skywalking收集到错误的信息,如:链路的跨度信息。
- 根据父委托机制,在项目里重写一个
GlobalIdGenerator
类,自定义规则。(打成jar包不生效了,目前不知道原因) - 重写源码,这是最简单直接的方式。
- 自定义一个探针,在字节码层面上对生成traceId的方法进行拦截。
其中3,4是肯定可以的,特别是方法3。