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

2023-12-27 21:52

本文主要是介绍使用 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 传播器

-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")@ResponseBodypublic 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=B3
-Ddd.propagation.style.inject=B3

机制源码介绍

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

这篇关于使用 extract + TextMapAdapter 实现了自定义 traceId的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/544392

相关文章

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

MySQL中EXISTS与IN用法使用与对比分析

《MySQL中EXISTS与IN用法使用与对比分析》在MySQL中,EXISTS和IN都用于子查询中根据另一个查询的结果来过滤主查询的记录,本文将基于工作原理、效率和应用场景进行全面对比... 目录一、基本用法详解1. IN 运算符2. EXISTS 运算符二、EXISTS 与 IN 的选择策略三、性能对比

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

使用Python构建智能BAT文件生成器的完美解决方案

《使用Python构建智能BAT文件生成器的完美解决方案》这篇文章主要为大家详细介绍了如何使用wxPython构建一个智能的BAT文件生成器,它不仅能够为Python脚本生成启动脚本,还提供了完整的文... 目录引言运行效果图项目背景与需求分析核心需求技术选型核心功能实现1. 数据库设计2. 界面布局设计3

使用IDEA部署Docker应用指南分享

《使用IDEA部署Docker应用指南分享》本文介绍了使用IDEA部署Docker应用的四步流程:创建Dockerfile、配置IDEADocker连接、设置运行调试环境、构建运行镜像,并强调需准备本... 目录一、创建 dockerfile 配置文件二、配置 IDEA 的 Docker 连接三、配置 Do

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

python设置环境变量路径实现过程

《python设置环境变量路径实现过程》本文介绍设置Python路径的多种方法:临时设置(Windows用`set`,Linux/macOS用`export`)、永久设置(系统属性或shell配置文件... 目录设置python路径的方法临时设置环境变量(适用于当前会话)永久设置环境变量(Windows系统

python使用try函数详解

《python使用try函数详解》Pythontry语句用于异常处理,支持捕获特定/多种异常、else/final子句确保资源释放,结合with语句自动清理,可自定义异常及嵌套结构,灵活应对错误场景... 目录try 函数的基本语法捕获特定异常捕获多个异常使用 else 子句使用 finally 子句捕获所

C++11右值引用与Lambda表达式的使用

《C++11右值引用与Lambda表达式的使用》C++11引入右值引用,实现移动语义提升性能,支持资源转移与完美转发;同时引入Lambda表达式,简化匿名函数定义,通过捕获列表和参数列表灵活处理变量... 目录C++11新特性右值引用和移动语义左值 / 右值常见的左值和右值移动语义移动构造函数移动复制运算符