使用 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-”开头的标头的规范。这些标头用于跨服务边界的跟踪上下文传播。B3有两种方式,分别是
    • B3SINGLE(B3_SINGLE_HEADER),对应 header 的 key 为 b3
    • B3(B3MULTI),对应 header 的 key 为 x-b3-

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

开启 Datadog 传播器

-Ddd.propagation.style.extract=Datadog
-Ddd.propagation.style.inject=Datadog

机制源码介绍

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

	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;
		....
	}

代码实现


    /***
     * 自定义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 传播器

-Ddd.propagation.style.extract=B3SINGLE
-Ddd.propagation.style.inject=B3SINGLE

机制源码介绍

	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 方式的处理

	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 代码实现

	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-TraceId 和 X-B3-SpanId,从拦截器上分析,是不区分大小写的。

6001828a33d570a9	6917954032704516265	58c4b35f113ee353
6001828a33d570a9	6917954032704516265	330359b7aaea9d6b
6001828a33d570a9	6917954032704516265	1ac0dcd332f9262f

Single Header 代码实现

	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);
        }

    }
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 :
-Ddd.propagation.style.inject=Datadog,B3SINGLE
-Ddd.propagation.style.extract=Datadog,B3SINGLE
  • Environment Variable:
DD_PROPAGATION_STYLE_INJECT=Datadog,B3SINGLE
DD_PROPAGATION_STYLE_EXTRACT=Datadog,B3SINGLE

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/283586.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot + Vue 抖音全平台项目

简介 本项目是一个短视频平台&#xff0c;拥有热度排行榜&#xff0c;热门视频&#xff0c;兴趣推送&#xff0c;关注推送&#xff0c;内容审核等功能。 源码下载 网盘 (访问密码: 8418) 登录/注册 首页 创作中心 架构设计 上传视频业务流程 视频推送流程 1.用户订阅分类后…

Java实现树结构(为前端实现级联菜单或者是下拉菜单接口)

Java实现树结构&#xff08;为前端实现级联菜单或者是下拉菜单接口&#xff09; 我们常常会遇到这样一个问题&#xff0c;就是前端要实现的样式是一个级联菜单或者是下拉树&#xff0c;如图 这样的数据接口是怎么实现的呢&#xff0c;是什么样子的呢&#xff1f; 我们可以看看 …

NodeJs - Chrome内存分析工具使用

NodeJs - Chrome内存分析工具使用 一. 前期准备二. Chrome 内存分析工具使用2.1 查看快照2.2 使用案例 一. 前期准备 我们下载好相关依赖&#xff1a; npm i v8-profiler-next测试代码&#xff1a; const v8Profiler require(v8-profiler-next) const fs require(fs)funct…

Intellij建议用String替换StringBuilder

文章目录 前言String 和 StringBuilder 性能对比String 和 StringBuilder 使用的字节码对比总结 本文收发地址 https://blog.csdn.net/CSqingchen/article/details/135324313 最新更新地址 https://gitee.com/chenjim/chenjimblog 前言 最近编码时看到 Intellij 建议使用 Stri…

2023-12-15 LeetCode每日一题(反转二叉树的奇数层)

2023-12-15每日一题 一、题目编号 2415. 反转二叉树的奇数层二、题目链接 点击跳转到题目位置 三、题目描述 给你一棵 完美 二叉树的根节点 root &#xff0c;请你反转这棵树中每个 奇数 层的节点值。 例如&#xff0c;假设第 3 层的节点值是 [2,1,3,4,7,11,29,18] &…

认识数据挖掘

随着数据库技术的迅速发展及数据库管理系统的广泛应用&#xff0c;人们积累的数据越来越多。激增的数据背后隐藏着许多重要的信息&#xff0c;人们希望能够对其进行更高层次的分析&#xff0c;以便更好地利用这些数据。目前的数据库系统可以高效地实现数据的录入、查询、统计等…

操作系统(Operator System)

这里写目录标题 1. 什么是操作系统2. 主要功能3. 计算机的层状结构4. 什么叫做管理5. 总结6. 为什么要有操作系统7. 最后 1. 什么是操作系统 操作系统&#xff08;英语&#xff1a;Operating System&#xff0c;缩写&#xff1a;OS&#xff09;是一组主管并控制计算机操作、运…

Debezium日常分享系列之:向 Debezium 连接器发送信号

Debezium日常分享系列之&#xff1a;向 Debezium 连接器发送信号 一、概述二、激活源信号通道三、信令数据集合的结构四、创建信令数据集合五、激活kafka信号通道六、数据格式七、激活JMX信号通道八、自定义信令通道九、Debezium 核心模块依赖项十、部署自定义信令通道十一、信…

【理论】STM32定时器时间计算公式 +【实践】TIM中断1s计时一次

前言&#xff1a;定时器TIM的详细知识点见我的博文&#xff1a;11.TIM定时中断-CSDN博客 STM32定时器时间计算公式 公式解释&#xff1a; ARR&#xff08;TIM_Period&#xff09;&#xff1a;自动重装载值&#xff0c;是定时器溢出前的计数值 PSC&#xff08;TIM_Prescaler&…

初始SpringBoot:详解特性和结构

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、SpringBoot…

骑砍MOD天芒传奇-前言

基于少年包青天3-天芒传奇开发的MOD,故事发生在北宋仁宗年间,玩家需要代替包拯寻找天芒,最终完成统一大业.开局可尝试使用暴雨梨花针神器. MOD下载地址:霸王•吕布 / pellets of legend GitCodehttps://gitcode.net/qq_35829452/1-mod 效果演示:骑砍1战团mod天芒传奇-重出江湖…

车载电子电器架构 —— 电子电气系统功能开发

车载电子电器架构 —— 电子电气系统功能开发 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何…

js遍历后端返回的集合将条件相同的放入同一个数组内

项目场景&#xff1a; echarts折线图需要根据条件动态展示多条不同曲线 解决方案&#xff1a; 后端直接将使用sql将数据查询出来返回即可,因为我这里不是Java使用的C#不是很熟练后台不好写逻辑,所以在前端js完成的 代码如下: function createline(villagename, buildingname…

微服务篇之Nacos快速入门

Nacos 简介 Nacos 起源 Nacos 起源于阿里巴巴 2008 年的五彩石项目&#xff08;完成微服务拆分和业务中台建设&#xff09;&#xff0c;经历了阿里十年双十⼀的洪峰流量的考验&#xff0c;沉淀了简单易用、稳定可靠、性能卓越等核心特性。随着云计算的兴起和受到开源软件行业…

ThinkPad产品如何升级BIOS程序

操作步骤: 重要提示&#xff1a; 更新BIOS存在风险&#xff01;如果您的电脑在此过程中出现死机&#xff0c;系统崩溃或断电&#xff0c;则BIOS或UEFI固件程序可能会损坏&#xff0c;这将使您的电脑无法启动。 由于BIOS的版本或者在升级过程中出错&#xff0c;可能会造成不可…

009、引用

1. 引用与借用 下面的示例重新定义了一个新的 calculate_length 函数。与之前不同的是&#xff0c;新的函数签名使用了 String 的引用作为参数而没有直接转移值的所有权&#xff1a; fn main() { let s1 String::from("hello"); let len calculate_length(&s1…

气象预报与计算机技术:深度融合与未来展望

气象预报与计算机技术:深度融合与未来展望 一、引言 气象预报,作为人类对自然界气象变化的探索与预测,随着时间的推移和技术的进步,已经逐渐从单纯的经验模式转变为依赖于精密的仪器与强大的计算机技术的科学预测。在本文中,我们将深入探讨气象预报与计算机技术之间的密…

[2024区块链开发入门指引] - 比特币与区块链诞生

一份为小白用户准备的免费区块链基础教程 工欲善其事,必先利其器 Web3开发中&#xff0c;各种工具、教程、社区、语言框架.。。。 种类繁多&#xff0c;是否有一个包罗万象的工具专注与Web3开发和相关资讯能毕其功于一役&#xff1f; 参见另一篇博文&#x1f449; 2024最全面…

数据结构:堆的三部曲 (一)堆的实现

堆的实现 1.堆的结构1.1堆的定义理解 2.堆的实现&#xff08;以小根堆为例&#xff09;2.1 堆结构体的定义2.2 堆的插入交换函数向上调整算法插入函数的代码 2.3 堆的删除向下调整算法&#xff1a;删除函数的代码&#xff1a; 2.4其他操作 3.测试以及完整源代码实现3.1测试代码…

山西电力市场日前价格预测【2024-01-02】

日前价格预测 预测说明&#xff1a; 如上图所示&#xff0c;预测明日&#xff08;2024-01-02&#xff09;山西电力市场全天平均日前电价为92.93元/MWh。其中&#xff0c;最高日前电价为275.90元/MWh&#xff0c;预计出现在18:00。最低日前电价为0.00元/MWh&#xff0c;预计出现…