使用 extract + TextMapAdapter 实现了自定义 traceId

前言

某些特定的场景,需要我们通过代码的方式实现自定义 traceId

实现思路:通过 tracer.extract 能够构造出 SpanContext ,将构造出来的 SpanContext 作为上层节点信息,通过 asChildOf(SpanContext) 能够构造出当前的 span。

TraceId 如何参数定义

对于 tracer.extract 构建 SpanContext,内部通过 ContextInterpreter 进行解析并获取对应的 traceId 和 spanId,ContextInterpreter 部分实现代码将在下文做介绍。

传播器

ddtrace 支持几种传播协议,不同的传播协议的 traceId 的参数名不一样。

就 java 而言,ddtrace 支持两种传播协议:

  • Datadog :默认传播协议
  • B3 :B3 传播是标头"b3"和以"x-b3-"开头的标头的规范。这些标头用于跨服务边界的跟踪上下文传播。

使用 Datadog 传播器实现自定义 traceId

开启 Datadog 传播器

ini 复制代码
-Ddd.propagation.style.extract=Datadog
-Ddd.propagation.style.inject=Datadog

机制源码介绍

ddtrace 默认采用 Datadog 作为默认的传播协议,拦截器为DatadogContextInterpreter,其部分代码如下:

typescript 复制代码
	public boolean accept(String key, String value) {
		case 'x':
			if ("x-datadog-trace-id".equalsIgnoreCase(key)) {
				classification = 0;
			} else if ("x-datadog-parent-id".equalsIgnoreCase(key)) {
				classification = 1;
			} else if ("x-datadog-sampling-priority".equalsIgnoreCase(key)) {
				classification = 3;
			} else if ("x-datadog-origin".equalsIgnoreCase(key)) {
				classification = 2;


		....

		switch(classification) {
			case 0:
				this.traceId = DDId.from(firstValue);
				break;
			case 1:
				this.spanId = DDId.from(firstValue);
				break;
			case 2:
				this.origin = firstValue;
				break;
			case 3:
				this.samplingPriority = Integer.parseInt(firstValue);
				break;
		....
	}

代码实现

ini 复制代码
    /***
     * 自定义traceId相关信息,实现自定义链路
     * @param traceId
     * @param parentId
     * @param treeLength
     * @return
     */
    @GetMapping("/customTrace")
    @ResponseBody
    public String customTrace(String traceId, String parentId, Integer treeLength) {
        Tracer tracer = GlobalTracer.get();
        traceId = StringUtils.isEmpty(traceId) ? IdGenerationStrategy.RANDOM.generate().toString() : traceId;
        parentId = StringUtils.isEmpty(parentId) ? DDId.ZERO.toString() : parentId;
        treeLength = treeLength == null ? 3 : treeLength;
        for (int i = 0; i < treeLength; i++) {
            Map<String, String> data = new HashMap<>();
            data.put("x-datadog-trace-id", traceId);
            data.put("x-datadog-parent-id", parentId);

            SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
            Span serverSpan = tracer.buildSpan("opt" + i)
                    .withTag("service_name", "someService" + i)
                    .asChildOf(extractedContext)
                    .start();
            tracer.activateSpan(serverSpan).close();
            serverSpan.finish();
            parentId = serverSpan.context().toSpanId();
        }
        return "build success!";
    }

使用B3传播器实现自定义 traceId

<关于 B3 传播器介绍>

B3 有两种编码:Single Header 和 Multiple Header。

  • 多个标头编码 X-B3-在跟踪上下文中使用每个项目的前缀标头
  • 单个标头将上下文分隔为一个名为 b3. 提取字段时,单头变体优先于多头变体。

这是一个使用多个标头编码的示例流程,假设 HTTP 请求带有传播的跟踪:

开启 B3 传播器

ini 复制代码
-Ddd.propagation.style.extract=B3
-Ddd.propagation.style.inject=B3

机制源码介绍

kotlin 复制代码
	public boolean accept(String key, String value) {
		...
		char first = Character.toLowerCase(key.charAt(0));
		switch (first) {
			case 'f':
				if (this.handledForwarding(key, value)) {
					return true;
				}
				break;
			case 'u':
				if (this.handledUserAgent(key, value)) {
					return true;
				}
				break;
			case 'x':
				if ((this.traceId == null || this.traceId == DDId.ZERO) && "X-B3-TraceId".equalsIgnoreCase(key)) {
					classification = 0;
				} else if ((this.spanId == null || this.spanId == DDId.ZERO) && "X-B3-SpanId".equalsIgnoreCase(key)) {
					classification = 1;
				} else if (this.samplingPriority == this.defaultSamplingPriority() && "X-B3-Sampled".equalsIgnoreCase(key)) {
					classification = 3;
				} else if (this.handledXForwarding(key, value)) {
					return true;
				}
		}

	    ...

		String firstValue = HttpCodec.firstHeaderValue(value);
		if (null != firstValue) {
			switch (classification) {
				case 0:
					if (this.setTraceId(firstValue)) {
						return true;
					}
					break;
				case 1:
					this.setSpanId(firstValue);
					break;
				case 2:
					String mappedKey = (String)this.taggedHeaders.get(lowerCaseKey);
					if (null != mappedKey) {
						if (this.tags.isEmpty()) {
							this.tags = new TreeMap();
						}

						this.tags.put(mappedKey, HttpCodec.decode(firstValue));
					}
					break;
				case 3:
					this.samplingPriority = this.convertSamplingPriority(firstValue);
					break;
				case 4:
					if (this.extractB3(firstValue)) {
						return true;
					}
			}
		}

		...

以下方法是对 Single Header 方式的处理

ini 复制代码
	private boolean extractB3(String firstValue) {
		if (firstValue.length() == 1) {
			this.samplingPriority = this.convertSamplingPriority(firstValue);
		} else {
			int firstIndex = firstValue.indexOf("-");
			int secondIndex = firstValue.indexOf("-", firstIndex + 1);
			String b3SpanId;
			if (firstIndex != -1) {
				b3SpanId = firstValue.substring(0, firstIndex);
				if (this.setTraceId(b3SpanId)) {
					return true;
				}
			}

			if (secondIndex == -1) {
				b3SpanId = firstValue.substring(firstIndex + 1);
				this.setSpanId(b3SpanId);
			} else {
				b3SpanId = firstValue.substring(firstIndex + 1, secondIndex);
				this.setSpanId(b3SpanId);
				String b3SamplingId = firstValue.substring(secondIndex + 1);
				this.samplingPriority = this.convertSamplingPriority(b3SamplingId);
			}
		}

		return false;
	}

Multiple Header 代码实现

ini 复制代码
	private static void b3TraceByMultiple(){
        String traceId = DDId.from("6917954032704516265").toHexStringOrOriginal();
        Tracer tracer = GlobalTracer.get();
        String parentId = DDId.from("4025816492133344807").toHexStringOrOriginal();
        for (int i = 0; i < 3; i++) {
            Map<String, String> data = new HashMap<>();
            data.put("X-B3-TraceId", traceId);
            data.put("X-B3-SpanId", parentId);

            SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
            Span serverSpan = tracer.buildSpan("opt"+i)
                    .withTag("service","someService"+i)
                    .asChildOf(extractedContext)
                    .start();
            serverSpan.setTag("code","200");
            tracer.activateSpan(serverSpan).close();
            serverSpan.finish();
            parentId = DDId.from(serverSpan.context().toSpanId()).toHexStringOrOriginal();
            System.out.println( traceId+"\t"+serverSpan.context().toTraceId()+"\t"+parentId);
        }

    }

**注意:**Multiple Header 必需传入两个 header,分别为:X-B3-TraceIdX-B3-SpanId,从拦截器上分析,是不区分大小写的。

复制代码
6001828a33d570a9	6917954032704516265	58c4b35f113ee353
6001828a33d570a9	6917954032704516265	330359b7aaea9d6b
6001828a33d570a9	6917954032704516265	1ac0dcd332f9262f

Single Header 代码实现

ini 复制代码
	private static void b3TraceBySingle(){
        String traceId = DDId.from("6917954032704516265").toHexStringOrOriginal();
        Tracer tracer = GlobalTracer.get();
        String parentId = DDId.from("4025816492133344807").toHexStringOrOriginal();
        for (int i = 0; i < 3; i++) {
            String b3 = traceId+ "-"+parentId+"-1";
            Map<String, String> data = new HashMap<>();
            data.put("b3",b3);
            SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new TextMapAdapter(data));
            Span serverSpan = tracer.buildSpan("opt"+i)
                    .withTag("service","someService"+i)
                    .asChildOf(extractedContext)
                    .start();
            serverSpan.setTag("code","200");
            tracer.activateSpan(serverSpan).close();
            serverSpan.finish();
            parentId = DDId.from(serverSpan.context().toSpanId()).toHexStringOrOriginal();
            System.out.println( traceId+"\t"+serverSpan.context().toTraceId()+"\t"+parentId);
            System.out.println("b3="+b3);
        }

    }
ini 复制代码
6001828a33d570a9	6917954032704516265	308287d022272ed9
b3=6001828a33d570a9-37de92c518846627-1
6001828a33d570a9	6917954032704516265	5e6fbaad91daef5c
b3=6001828a33d570a9-308287d022272ed9-1
6001828a33d570a9	6917954032704516265	2cfbc225bddf5e6d
b3=6001828a33d570a9-5e6fbaad91daef5c-1

**注意:**Single Header 只需要 header 传入 b3 即可,格式为 traceId-parentId-Sampled

开启多种传播器

两种方式任选一种即可:

  • System Property :
ini 复制代码
-Ddd.propagation.style.inject=Datadog,B3
-Ddd.propagation.style.extract=Datadog,B3
  • Environment Variable:
ini 复制代码
DD_PROPAGATION_STYLE_INJECT=Datadog,B3
DD_PROPAGATION_STYLE_EXTRACT=Datadog,B3
相关推荐
程序员岳焱39 分钟前
Java 与 MySQL 性能优化:Java 实现百万数据分批次插入的最佳实践
后端·mysql·性能优化
麦兜*1 小时前
Spring Boot启动优化7板斧(延迟初始化、组件扫描精准打击、JVM参数调优):砍掉70%启动时间的魔鬼实践
java·jvm·spring boot·后端·spring·spring cloud·系统架构
大只鹅2 小时前
解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题
spring boot·后端·elasticsearch
ai小鬼头2 小时前
AIStarter如何快速部署Stable Diffusion?**新手也能轻松上手的AI绘图
前端·后端·github
IT_10242 小时前
Spring Boot项目开发实战销售管理系统——数据库设计!
java·开发语言·数据库·spring boot·后端·oracle
bobz9652 小时前
动态规划
后端
stark张宇3 小时前
VMware 虚拟机装 Linux Centos 7.9 保姆级教程(附资源包)
linux·后端
亚力山大抵3 小时前
实验六-使用PyMySQL数据存储的Flask登录系统-实验七-集成Flask-SocketIO的实时通信系统
后端·python·flask
超级小忍4 小时前
Spring Boot 中常用的工具类库及其使用示例(完整版)
spring boot·后端
CHENWENFEIc4 小时前
SpringBoot论坛系统安全测试实战报告
spring boot·后端·程序人生·spring·系统安全·安全测试