使用 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
相关推荐
uzong2 小时前
技术故障复盘模版
后端
GetcharZp3 小时前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
桦说编程3 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研3 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi4 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
阿华的代码王国5 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
Jimmy5 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
AntBlack5 小时前
不当韭菜V1.1 :增强能力 ,辅助构建自己的交易规则
后端·python·pyqt
bobz9656 小时前
pip install 已经不再安全
后端
寻月隐君6 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github