OpenTelemetry Java 扩展:无需分叉 agent 即可自定义追踪

作者:Elasticsearch日期:2026/8/25

作者:来自 Elastic Sylvain Juge

一个 JAR 文件,在 OpenTelemetry Java agent 启动时加载,就可以过滤健康检查、重命名 span、添加资源属性以及控制采样,而无需修改应用程序代码。

你刚刚为一个 Java 应用设置了自动插桩。无需修改任何代码,追踪数据就开始流向你的可观测性平台。几分钟后,你发现健康检查端点正在大量充斥你的追踪视图,而且事务名称反映的是通用的框架模式,而不是你的业务领域操作。

分叉 agent 可以解决这个问题,但这样一来,你就需要负责处理每一次上游合并。你也可以使用手动插桩来获得完全的控制权,但这需要修改代码,并且需要持续维护。OpenTelemetry Java 扩展提供了一条更简洁的路径:使用一个独立的 JAR 文件,由 agent 在启动时加载,让你能够精确控制哪些内容被捕获和导出,而无需修改 agent 或应用程序代码。

例如,以下挑战非常常见:

  • 健康检查探针正在大量充斥你的追踪视图。
  • span 名称反映的是通用的框架模式,而不是你的业务领域操作。
  • 某些 span 名称或属性具有高基数,在追踪数据中造成噪声。
  • span 缺少与你的业务逻辑相关的属性。
  • Baggage 标头正在传播到不应该接收它们的下游服务。
  • 描述你的部署环境的资源属性无法被自动捕获,因为它们依赖于自定义环境变量。

其中一些问题可以通过配置来解决,或者使用中间的 OpenTelemetry Collector 进行处理。不过,这也可能增加遥测数据管道的复杂性,而你可能更希望在数据被捕获的源头解决这些问题。

什么是 OpenTelemetry Java 扩展

扩展是一个 JAR 文件,由 agent 在启动时加载。它通过 Java 的服务提供者接口(SPI)机制接入 agent 的扩展点,这也是 agent 内部使用的相同机制。

该扩展机制在上游 OpenTelemetry Java agent 和Elastic 的 OpenTelemetry 发行版中运行方式完全相同。你只需编写一次扩展,它就可以与两者配合使用。

作为参考,上游扩展文档提供了扩展点的完整概览以及一些示例。

本文并不旨在提供完整的参考资料,而是重点介绍一些你在生产环境中可能经常需要使用的简单场景:重命名 span、过滤嘈杂的追踪数据,或者传播 agent 在你的环境中未覆盖的上下文。

扩展还允许你修改和扩展 agent 自身的插桩功能。这超出了本文的讨论范围。以下是两个入门方向:

设置 OpenTelemetry Java 扩展项目

扩展是一个标准的 Java Gradle 项目,但有两个要求:输出必须是 shadow JAR(一个包含所有扩展依赖的 fat JAR),并且 OpenTelemetry 依赖必须声明为 compileOnly,这样就不会将 SDK 本身打包进去。

之所以要求使用 shadow JAR,是因为 agent 会通过自己的类加载器加载扩展。如果你将某个依赖声明为 implementation,它就会被打包进去,并可能与 agent 中已经存在的版本发生冲突。使用 compileOnly 可以让这些 JAR 完全不会被包含在扩展 JAR 中。

下面是一个最小的 build.gradle.kts 示例,用于创建一个不自定义插桩、因此仅依赖 OpenTelemetry SDK/API 的简单扩展。

1`1.  plugins {
22.    id("java")
33.    id("com.gradleup.shadow")
44.  }
5
66.  repositories {
77.    mavenCentral()
88.  }
9
1010.  java {
1111.    toolchain {
1212.      languageVersion.set(JavaLanguageVersion.of(8))
1313.    }
1414.  }
15
1616.  dependencies {
1717.    // Use BOM to manage OpenTelemetry dependency versions
1818.    compileOnly(platform("io.opentelemetry:opentelemetry-bom:1.64.0"))
1919.    // OpenTelemetry SDK autoconfiguration SPI (provided by agent)
2020.    compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
2121.    // OpenTelemetry SDK
2222.    compileOnly("io.opentelemetry:opentelemetry-sdk")
2323.    // Annotation processor for automatic SPI registration
2424.    compileOnly("com.google.auto.service:auto-service:1.1.1")
2525.    annotationProcessor("com.google.auto.service:auto-service:1.1.1")
2626.  }
27
2828.  tasks.assemble {
2929.    dependsOn(tasks.shadowJar)
3030.  }` AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
31

开始之前,请先在 Maven Central 查看最新版本的 BOM。

扩展只在编译时依赖 OpenTelemetry SDK 和自动配置 SPI。agent 会在运行时提供其余的 SDK 和插桩实现。

在运行时加载 OpenTelemetry Java 扩展

要在运行时加载 OpenTelemetry Java 扩展,可以使用 otel.javaagent.extensions 系统属性或 OTEL_JAVAAGENT_EXTENSIONS 环境变量。该值是一个由逗号分隔的扩展 JAR 路径列表:

1`java -Dotel.javaagent.extensions=/path/to/my-extension.jar -javaagent:/path/to/opentelemetry-javaagent.jar -jar myapp.jar`AI写代码
2

上游 OpenTelemetry Java agent 还允许你直接将扩展嵌入 agent JAR,从而简化部署。

使用 OpenTelemetry Java 扩展过滤和重命名 span

你可以通过两种方式修改 span:

  • 使用 SpanProcessor,它会在 span 开始或结束时同步调用。
  • 使用 SpanExporter,它会在 span 导出时异步调用。

使用 SpanProcessor 重命名 span

SpanProcessor.onStart 会接收一个 ReadWriteSpan,这意味着你可以在 span 被导出之前调用 span.updateName()。如果需要根据 span 开始时就可用的属性进行重命名,这是正确的扩展点。

1`
2
31.  public class OperationRenamingSpanProcessor implements SpanProcessor {
4
53.    @Override
64.    public void onStart(Context parentContext, ReadWriteSpan span) {
75.      String operation = span.getAttribute(AttributeKey.stringKey("app.operation"));
86.      if (operation != null) {
97.        span.updateName(operation);
108.      }
119.    }
12
1311.    @Override
1412.    public boolean isStartRequired() { return true; }
15
1614.    @Override
1715.    public void onEnd(ReadableSpan span) {}
18
1917.    @Override
2018.    public boolean isEndRequired() { return false; }
21
2220.    @Override
2321.    public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); }
24
2523.    @Override
2624.    public CompletableResultCode forceFlush() { return CompletableResultCode.ofSuccess(); }
2725.  }
28
29`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
30

通过 AutoConfigurationCustomizerProvider 注册 SpanProcessor,并将其与已经配置的 processor 组合:

svg

1`
2
31.  @AutoService(AutoConfigurationCustomizerProvider.class)
42.  public class RenamingCustomizerProvider implements AutoConfigurationCustomizerProvider {
5
64.    @Override
75.    public void customize(AutoConfigurationCustomizer customizer) {
86.      customizer.addTracerProviderCustomizer(this::configureSdkTracerProvider);
97.    }
10
119.    private SdkTracerProviderBuilder configureSdkTracerProvider(
1210.        SdkTracerProviderBuilder tracerProvider, ConfigProperties config) {
1311.      return tracerProvider.addSpanProcessor(new OperationRenamingSpanProcessor());
1412.    }
15
1614.  }
17
18`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
19

modify-span EDOT Java 扩展示例提供了完整的实现。

使用 SpanExporter 过滤 span

SpanExporter 包装器允许你在 span 离开进程之前修改或丢弃它们。这对于健康检查等已知的高噪声端点非常有效。

svg

1`
2
31.  public class FilteringSpanExporter implements SpanExporter {
4
53.    private final SpanExporter delegate;
6
75.    public FilteringSpanExporter(SpanExporter delegate) {
86.      this.delegate = delegate;
97.    }
10
119.    @Override
1210.    public CompletableResultCode export(Collection<SpanData> spans) {
1311.      List<SpanData> filtered = new ArrayList<>();
1412.      for (SpanData span : spans) {
1513.        if (!"GET /health".equals(span.getName())) {
1614.          filtered.add(span);
1715.        }
1816.      }
1917.      return delegate.export(filtered);
2018.    }
21
2220.    @Override
2321.    public CompletableResultCode flush() { return delegate.flush(); }
24
2523.    @Override
2624.    public CompletableResultCode shutdown() { return delegate.shutdown(); }
2725.  }
28
29`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
30

通过 addSpanExporterCustomizer 注册 FilteringSpanExporter:

svg

1`customizer.addSpanExporterCustomizer((existing, config) -> new FilteringSpanExporter(existing));`AI写代码
2

modify-span EDOT Java 扩展示例提供了完整的实现。

这种方式有两个限制:

  • 它不会丢弃可能已经创建的任何子 span,例如健康检查调用数据库时产生的子 span。
  • 在 exporter 中过滤的 span 已经经过完整的 processor 管道,并占用了 batch processor 中的缓冲区空间。

如果你在这一阶段丢弃大量流量,自定义 Sampler(如下所示)会更加高效,因为它会在任何处理发生之前丢弃 span,同时也会过滤掉子 span。此外,在使用声明式配置时,基于规则的 sampler 允许你仅使用配置,就可以通过规则实现过滤。

使用 ResourceProvider 添加自定义资源属性

资源属性描述正在运行的内容:服务名称、版本和主机。ResourceProvider 允许你添加 agent 不知道的其他属性,例如你的平台通过环境变量注入的部署元数据。

下面的示例使用环境变量,但也可以使用配置文件、云元数据服务或 agent 在启动时能够访问的任何其他来源。

由于 SDK 初始化是同步的,因此在查询元数据端点等外部服务时,可能会导致 agent(以及应用程序)的启动速度变慢。如果可能,建议先检查环境变量和本地配置,然后再调用外部服务。

1`
2
31.  @AutoService(ResourceProvider.class)
42.  public class DeploymentResourceProvider implements ResourceProvider {
5
64.    @Override
75.    public Resource createResource(ConfigProperties config) {
86.      AttributesBuilder attributes = Attributes.builder();
9
108.      String region = System.getenv("DEPLOY_REGION");
119.      if (region != null) {
1210.        attributes.put(AttributeKey.stringKey("deployment.region"), region);
1311.      }
14
1513.      String buildVersion = System.getenv("BUILD_VERSION");
1614.      if (buildVersion != null) {
1715.        attributes.put(AttributeKey.stringKey("build.version"), buildVersion);
1816.      }
19
2018.      return Resource.create(attributes.build());
2119.    }
2220.  }
23
24`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
25

ResourceProvider 中的属性会与 agent 自身的资源合并。当两个 provider 提供相同的键时,具有更高 order() 值的 provider 获胜。agent 内置的 provider 使用 order 0,因此将 order() 重写为返回正整数,可以让你的 provider 获得更高优先级。

resource-attribute EDOT Java 扩展示例提供了完整的实现。

OpenTelemetry Java 中的自定义采样

当在 exporter 中进行过滤已经太晚或成本太高时,可以直接实现 Sampler。sampler 会在任何 span 处理之前运行,因此被丢弃的 span 不会接触 batch 缓冲区。

不过,采样决策只能依赖 span 开始时提供的属性。例如,HTTP 响应的状态码不能用于采样决策,因为它只有在 span 结束时才可用。

关键细节是:包装现有的 sampler,而不是替换它。这样,你的逻辑就可以与已有配置组合,同时仍然遵循上游服务传递的基于父级的决策。

1`
2
31.  public class HealthCheckSampler implements Sampler {
4
53.    private final Sampler delegate;
6
75.    public HealthCheckSampler(Sampler delegate) {
86.      this.delegate = delegate;
97.    }
10
119.    @Override
1210.    public SamplingResult shouldSample(
1311.        Context parentContext,
1412.        String traceId,
1513.        String name,
1614.        SpanKind spanKind,
1715.        Attributes attributes,
1816.        List<LinkData> parentLinks) {
1917.      if (spanKind == SpanKind.SERVER && name.contains("health")) {
2018.        return SamplingResult.create(SamplingDecision.DROP);
2119.      }
2220.      return delegate.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
2321.    }
24
2523.    @Override
2624.    public String getDescription() {
2725.      return "HealthCheckSampler{" + delegate.getDescription() + "}";
2826.    }
2927.  }
30
31`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
32

通过 addSamplerCustomizer 注册 HealthCheckSampler,它会同时提供现有的 sampler 和解析后的配置:

svg

1`customizer.addSamplerCustomizer((existing, config) -> new HealthCheckSampler(existing));`AI写代码
2

opentelemetry-java-contrib 中的社区扩展

opentelemetry-java-contrib代码仓库包含多个由社区维护的扩展。

其中一些已经包含在 OpenTelemetry Java agent 中(并继承到 Elastic 发行版中),但默认选择不启用:

Elastic 发行版的大多数功能都以扩展的形式存在于 contrib 代码仓库中,因此你可以以与供应商无关的方式,将它们与上游 agent 一起使用。

进一步阅读和扩展示例

上游扩展示例涵盖了本文未展示的其他扩展点,包括自定义 propagator、ID 生成器和被忽略类型的配置器。

Elastic baggage 示例展示了 baggage 的过滤 propagator 如何在一个包含两个服务的应用中端到端运行,同时还展示了如何在不修改应用程序代码的情况下,通过自定义插桩来添加 baggage。

本文介绍了项目设置以及生产环境中最可能遇到的模式。上面的两个链接可以帮助你进一步深入了解:上游示例增加了本文未涵盖的扩展点,而 baggage 示例则展示了一个完整的双服务实现,你可以在本地运行它。

原文:OpenTelemetry Java extensions: skip the agent fork — Elastic Observability Labs


《OpenTelemetry Java 扩展:无需分叉 agent 即可自定义追踪》 是转载文章,点击查看原文。


相关推荐


把 Agent 做成一家公司,真比通用提示词好用吗?
苏灿烤鱼2026/8/12

它卖的不是一个万能 Agent,而是一套可挑选、可安装的 AI 专业分工。 ⚡️ 30 秒速读:msitarzewski/agency-agents 以 143,086 星、今日 +971 登上 GitHub Trending #1,连续 3 天在榜,排名从 #3、#2 升到 #1。它把不同专业角色写成独立 Agent 文件,每个文件包含身份与性格、核心任务与工作流、带代码示例的技术交付物、成功指标和沟通风格;可用原生桌面应用或 Shell 脚本安装到 Claude Code、Cursor、


Vite 8.1 深度拆解:Rolldown 统一打包器如何终结前端构建的「双引擎时代」
NutShell Wang2026/8/2

2026 年 3 月 12 日,Vite 8.0 正式发布,将 Rolldown——一个用 Rust 编写的打包器——作为唯一打包引擎引入,取代了此前 esbuild(开发)+ Rollup(生产)的双引擎架构。这被官方称为「自 Vite 2 以来最重大的架构变更」。三个月后的 Vite 8.1(6 月 23 日发布)进一步推出了实验性打包开发模式,在 10,000 个 React 组件的测试中实现了约 15 倍的启动加速。与此同时,Rolldown 本身也在快速迭代——1.0 正式版于 5 月


线程栈与TLS和线程互斥
keyipatience2026/7/25

线程栈 主线程(进程 main 栈)特性: (1)来源:fork 复制父进程栈 (2)可动态自动扩容 (3)缺页容错特殊:允许访问未映射页、不一定直接段错误的栈 线程栈 mem = mmap(NULL, size, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); 标志MAP_STACK:专门标记这块内存用作线程栈;默认固定 8MB 大小(一般够用),不支持动态扩容,空间用完直接栈溢出崩溃;属于进程虚拟地址里一块独


如何用 AI 协助解决陌生技术问题:拆解-分析-熟悉-解决四步法
dozenyaoyida2026/7/17

你接过陌生项目吗?那种打开 IDE,几千个文件铺开,光看目录名就头大,盯着屏幕两小时一行代码没写的感觉。 或者更常见的,线上突然报了个错,涉及一个你从没读过的模块,老板在群里 @ 你,你点开文件,密密麻麻的调用链,不知道从哪开始查。 我做了十年开发,最近两年重度用 AI 辅助。最大的体会是,面对陌生问题,卡住你的从来不是"难",是"乱"。你不知道从哪下手,不知道自己不知道什么,于是在原地打转。 下面这套方法我用了几十次,核心就四个字:拆解、分析、熟悉、解决。AI 在每个阶段扮演的角色不一样,你介


【从零开始大模型开发与微调:基于PyTorch与ChatGLM】(基于PyTorch卷积层的MNIST分类实战:从卷积直觉到高效卷积设计)
承渊政道2026/7/9

🔥承渊政道:个人主页 ❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》 《MySQL数据库学习》 《Python知识内容》 ✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介: 前面使用多层感知机完成了MNIST分类实战的演示.多层感知机是一种对目标数据进行整体分类的计算方法.虽然从演示效果来看,多层感知机可以较好地完成项目


开源「仓颉.Skill」2.0,你现在可以蒸馏任何视频!
AI袋鼠帝2026/7/1

大家好,我是袋鼠帝。 没想到cangjie-skill在4月开源,中间没怎么推,两个月还慢慢涨到了1.3K Star,有点出乎我的意料。 而且现在每天都还在增涨,感谢大家支持~ github.com/kangarookin… 说明大家对蒸馏书是有需求的(可以理解为人工智能拆书)。 也并不是像评论区一些人说的:“所有书AI都学过了,你这个是脱了裤子放屁。”那样不堪。 对一些大众非常熟悉的书,可能不太需要这个方式来蒸馏。但是有很多比较小众的书,AI不一定记得清楚,甚至还有很多新书是AI没有训练的。


AI Agent(六)- Dify 自定义工具实战 - 基于百度天气 API 搭建天气查询 Agent(天气智查助手)
BigDataMagician2026/6/22

文章目录 一、前言二、整体实现思路三、申请百度地图开放平台 AK1. 注册百度地图开放平台2. 登录百度地图开放平台3. 创建应用并获取AK4. 查看国内天气查询接口开发文档5. 接口测试 四、创建自定义工具1. OpenAPI 规范配置内容及说明1.1 OpenAPI 规范配置内容1.2 OpenAPI 规范配置说明 2. 配置OpenAPI 规范3. 工具测试 六、搭建天气智查助手Agent1. 创建Agent2. System Prompt(系统提示词)3. 调用工具4.


MyBatis魔法堂:结果集映射
独泪了无痕2026/6/14

一、ResultMap 的定义   在当今的软件开发领域,MyBatis 作为一款优秀的持久层框架,以其简洁的配置和强大的功能,深受广大开发者的喜爱。然而,在实际的项目开发中,我们常常会遇到数据模型与数据库表结构不一致的情况,这时就需要 MyBatis 的 resultMap 功能来帮助我们实现复杂的映射关系。想象一下,一个典型的业务场景:一个电商系统中的订单表,其字段包括订单ID、用户ID、商品ID、订单金额等。然而,在业务逻辑层,我们可能需要将订单信息与对应的用户信息和商品信息结合起来,以便


不用 Mac 也可以 Windows下管理iOS描述文件的非Xcode完整指南
程序员不说人话2026/6/7

很多开发者第一次接触 iOS 描述文件(Provisioning Profile)时,看到的教程基本都围绕 Xcode 和钥匙串。 但实际开发里,有一类项目并不是在 Mac 上完成的、uni-app、Flutter、React Native、HBuilderX 云打包、Windows 开发环境,这时问题会变成.mobileprovision 文件到底怎么管理? 尤其项目一多之后,开发者会开始遇到 描述文件和证书不匹配、Bundle ID 混乱、测试设备漏加、文件过期后无法安装、不同电脑之间无法同


栗子前端技术周刊第 131 期 - pnpm 11.3、npm 11.16.0、Astro 6.4...
晓得迷路了2026/6/1

🌰栗子前端技术周刊第 131 期 (2026.05.25 - 2026.05.31):浏览前端一周最新消息,学习国内外优秀文章,让我们保持对前端的好奇心。 📰 技术资讯 pnpm 11.3:pnpm 11.3 版本更新,新增阶段性发布命令 pnpm stage、用于管控信任策略生效规则的 trustLockfile 配置,同时原生支持 pkg、repo、set-script 等命令,以及多项其他功能。 npm 11.16.0:npm 11.16.0 已正式发布,该版本初步支持可自主选

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读