Spring Boot 中的 OpenTelemetry

工程 | 莫里茨·哈尔布里特 | 2025年11月18日 | ...

这是 Road to GA 系列中的一篇新博客文章,这次我们将探讨 Spring Boot 中的 OpenTelemetry。

引言

在现代云原生架构中,可观测性不再是可选项;它是一项基本要求。你希望通过指标了解应用程序正在做什么,通过跟踪了解请求如何流经应用程序,以及通过日志了解应用程序正在说什么。

OpenTelemetry 项目(有时缩写为 OTel)提供了一个供应商中立的开源框架,用于收集、处理和导出遥测数据。它由 云原生计算基金会 提供支持,提供 API、SDK、用于导出数据的标准网络协议 OTLP,以及用于处理数据摄取、处理和导出到后端的可插拔架构(包括 OpenTelemetry Collector)。

经过检测的项目和库使用 API 发送可观测性数据。实现该 API 的 SDK 用于配置数据如何收集和导出。Collector 是一个可选组件,可以帮助聚合和筛选数据,但您也可以将数据直接发送到任何兼容的后端。

Spring 生态系统通过 Micrometer 提供了强大的可观测性支持,将 Spring Boot 与 OpenTelemetry 结合使用是涵盖所有可观测性信号(指标、追踪和日志)的强大方式。这里的关键支持因素是 OTLP 协议,而不是特定的库。

在这篇文章中,我们将详细介绍 OpenTelemetry 的含义,并了解如何将其与 Spring Boot 集成,以完美覆盖可观测性需求。

将 OpenTelemetry 与 Spring Boot 结合使用

当您选择将 OpenTelemetry 与 Spring Boot 集成时,有几种替代路径,从“直接引入运行时代理”到“使用内置 Spring 支持”。您有三种选择:

使用 OpenTelemetry Java 代理

入门很简单:您在启动时通过 -javaagent 标志附加 opentelemetry-javaagent.jar。此代理对库(HTTP、JDBC、Spring 等)进行字节码检测。这是最简单的“零代码更改”路径。代理会识别追踪、跨度(跨度是追踪的原子部分)、指标等。

这种方法的主要问题是 Java 代理必须与您的库版本紧密匹配,因为代理会修改它们的字节码。如果代理经过测试的版本与您使用的版本不匹配,问题可能很难诊断。此外,如果您正在使用 GraalVM 的 native-image 或想使用 Java 的 AOT 缓存,您必须经历额外的步骤。另外,如果您已经在使用代理,它们可能会发生冲突。

使用第三方 OpenTelemetry Spring Boot Starter

OpenTelemetry 有自己的 Spring Boot starter,它检测某些技术。然而,他们指出 OpenTelemetry Java 代理是他们默认的检测选择,并且只有在代理无法使用时才应使用 starter。此外,尽管starter 本身被标记为稳定,但它引入的依赖项带有 alpha 后缀。

使用 Spring 团队的 OpenTelemetry Spring Boot Starter

随着 Spring Boot 4.0 的发布,我们正在引入一个新的 OpenTelemetry Spring Boot Starter。它被称为 spring-boot-starter-opentelemetry(我们对这些名称确实很聪明,不是吗),并且可以通过 start.spring.io 上的“OpenTelemetry”依赖项进行选择。

我们可能有些偏见,但这是我们在 Spring Boot 应用程序中实现可观测性最喜欢的选项。

该 starter 包含 OpenTelemetry API 和通过 OTLP 导出 Micrometer 信号的组件。Micrometer Tracing 与 OpenTelemetry 桥接一起使用 OpenTelemetry API,以 OTLP 格式导出追踪。Micrometer OtlpMeterRegistry 用于通过 OTLP 协议将通过 Micrometer API 收集的指标发送到支持 OpenTelemetry 的指标后端。

重申一下:重要的是协议,而不是所使用的库。尽管 Spring 组合项目使用 Micrometer 作为其可观测性 API,但完全可以通过 OTLP 将信号导出到任何支持 OpenTelemetry 的后端,您很快就会看到。

Spring Boot 还支持通过 OTLP 将日志发送到支持 OpenTelemetry 的后端,但它不会在 Logback 和 Log4j2 中开箱即用安装日志附加器。这将来可能会改变,但即使现在,也很容易做到,我们也会在这篇博客文章中介绍设置。

这篇博客文章的其余部分将重点介绍如何使用 Spring 团队的新 OpenTelemetry Spring Boot Starter 在您的 Spring Boot 应用程序中获得无缝的 OpenTelemetry 体验。

导出指标

如前所述,Spring Boot 使用 Micrometer OTLP 注册表通过 OTLP 将 Micrometer 指标导出到任何支持 OpenTelemetry 的后端。所需的依赖项 io.micrometer:micrometer-registry-otlp 已包含在 spring-boot-starter-opentelemetry 中。有了这个依赖项,Micrometer 将以 OTLP 格式将指标导出到后端 https://:4318/v1/metrics。要自定义指标导出位置,请设置 management.otlp.metrics.export.url 属性,例如:

management.otlp.metrics.export.url=http://otlp.example.com:4318/v1/metrics

Micrometer 团队还为 OpenTelemetry 添加了所谓的观察约定。OpenTelemetry 中的信号遵循 OpenTelemetry 语义约定,Micrometer 中的观察约定实现了 OpenTelemetry 语义约定的稳定部分。要在 Spring Boot 中使用它们,您必须定义一些配置(这将来可能会改进

@Configuration(proxyBeanMethods = false)
public class OpenTelemetryConfiguration {

    @Bean
    OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() {
        return new OpenTelemetryServerRequestObservationConvention();
    }

    @Bean
    OpenTelemetryJvmCpuMeterConventions openTelemetryJvmCpuMeterConventions() {
        return new OpenTelemetryJvmCpuMeterConventions(Tags.empty());
    }

    @Bean
    ProcessorMetrics processorMetrics() {
        return new ProcessorMetrics(List.of(), new OpenTelemetryJvmCpuMeterConventions(Tags.empty()));
    }

    @Bean
    JvmMemoryMetrics jvmMemoryMetrics() {
        return new JvmMemoryMetrics(List.of(), new OpenTelemetryJvmMemoryMeterConventions(Tags.empty()));
    }

    @Bean
    JvmThreadMetrics jvmThreadMetrics() {
        return new JvmThreadMetrics(List.of(), new OpenTelemetryJvmThreadMeterConventions(Tags.empty()));
    }

    @Bean
    ClassLoaderMetrics classLoaderMetrics() {
        return new ClassLoaderMetrics(new OpenTelemetryJvmClassLoadingMeterConventions());
    }

}

Spring Boot 不会自动配置用于指标的 OpenTelemetry API。如果您真的想使用 OpenTelemetry 指标 API(我们不推荐)而不是 Micrometer API,或者如果您有一个使用 OpenTelemetry 指标 API 的库,请查看 Spring Boot 文档,了解如何使其工作。

导出追踪

Spring 项目使用 Micrometer Observation API 创建观测。观测是 Micrometer 中一个有趣的概念,因为它可以转换为指标**和**追踪。

然后,通过 io.micrometer:micrometer-tracing-bridge-otel 依赖项(也包含在 spring-boot-starter-opentelemetry 中),将由观测生成的追踪适配到 OpenTelemetry API。

Spring Boot 包含 OpenTelemetry SDK 的自动配置。对于追踪,它安装了一个 OtlpHttpSpanExporter(如果您更喜欢 gRPC 而不是 HTTP,则为 OtlpGrpcSpanExporter)。有了这个导出器,OpenTelemetry SDK 现在开始以 OTLP 格式将追踪(源自 Micrometer Observation)发送到您的后端。

要在您的应用程序中启用它,您必须设置 management.opentelemetry.tracing.export.otlp.endpoint 属性,例如:

management.opentelemetry.tracing.export.otlp.endpoint=https://:4318/v1/traces

导出日志

如前所述,Spring Boot 包含自动配置,可配置 OpenTelemetry SDK 以便能够以 OTLP 格式导出日志。然而,它不会在 Logback 或 Log4j2 中安装附加器,因此尽管底层基础设施存在,但实际上并没有导出任何日志。要以 OTLP 格式导出日志,您需要做两件事:

首先,设置属性 management.opentelemetry.logging.export.otlp.endpoint,例如:

management.opentelemetry.logging.export.otlp.endpoint=https://:4318/v1/logs

其次,在 Logback 或 Log4j2 中安装一个附加器,将日志条目发送到 OpenTelemetry API。

对于 Logback,我们首先需要包含 io.opentelemetry.instrumentation:opentelemetry-logback-appender-1.0:2.21.0-alpha 依赖项(版本号中的 -alpha 表示它被标记为不稳定;不幸的是,附加器没有非 alpha 版本。您可以在此处阅读更多信息)。

然后,我们必须创建一个自定义的 Logback 配置,该配置位于 src/main/resources/logback-spring.xml 文件中:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
    </appender>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="OTEL"/>
    </root>
</configuration>

此配置导入 Spring Boot 的 Logback 基本配置,然后定义一个名为 OTEL 的附加程序,该附加程序将所有日志事件发送到 OpenTelemetry API。然后将此附加程序添加到根记录器,从而除了控制台之外,还将所有日志条目发送到 OpenTelemetry API。

最后要做的是让 OpenTelemetryAppender 知道要使用哪个 OpenTelemetry API 实例。为此,我们可以创建一个小型 bean,将 OpenTelemetry 实例注入:

@Component
class InstallOpenTelemetryAppender implements InitializingBean {

    private final OpenTelemetry openTelemetry;

    InstallOpenTelemetryAppender(OpenTelemetry openTelemetry) {
        this.openTelemetry = openTelemetry;
    }

    @Override
    public void afterPropertiesSet() {
        OpenTelemetryAppender.install(this.openTelemetry);
    }
    
}

注意上下文

日志、指标和追踪使用上下文信息,例如当前的追踪 ID。默认情况下,当 Micrometer Tracing 在类路径上时,Spring Boot 会调整日志模式,以便在日志消息中也包含追踪 ID 和跨度 ID。如果您正在尝试查找属于某个追踪的日志,这会非常有用。

一个有用的模式是在服务器的响应中(例如,通过 HTTP 标头)包含请求的追踪 ID。这样,如果用户从您的 HTTP 端点收到错误响应,他们可以将追踪 ID 包含在工单中,您可以使用此追踪 ID 来获取属于该错误请求的所有日志。

使用此 Servlet 过滤器可以将追踪 ID 放入标头中:

@Component
class TraceIdFilter extends OncePerRequestFilter {
    
    private final Tracer tracer;

    TraceIdFilter(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String traceId = getTraceId();
        if (traceId != null) {
            response.setHeader("X-Trace-Id", traceId);
        }
        filterChain.doFilter(request, response);
    }

    private @Nullable String getTraceId() {
        TraceContext context = this.tracer.currentTraceContext().context();
        return context != null ? context.traceId() : null;
    }
    
}

如果您正在处理切换线程的方法,例如 @Async 注解的方法或使用 Spring 的 AsyncTaskExecutor,您会注意到在新线程中上下文丢失了。丢失的上下文会影响日志(不再包含追踪 ID)和追踪(丢失了跨度)。

上下文丢失是因为它存储在 ThreadLocal 中,而 ThreadLocal 不会传输到新线程。然而,解决方案非常简单:在 AsyncTaskExecutor(也用于 @Async 注解的方法)中使用 ContextPropagatingTaskDecoratorContextPropagatingTaskDecorator 使用 Micrometer 的上下文传播 API 来确保追踪上下文传输到新线程。安装 ContextPropagatingTaskDecorator 很简单:只需定义一个 @Bean 方法,如以下代码片段所示:

@Configuration(proxyBeanMethods = false)
public class ContextPropagationConfiguration {

    @Bean
    ContextPropagatingTaskDecorator contextPropagatingTaskDecorator() {
        return new ContextPropagatingTaskDecorator();
    }

}

Spring Boot 的自动配置会查找 TaskDecorator bean 并将其安装到 AsyncTaskExecutor 中。有了 ContextPropagatingTaskDecorator,上下文现在被传输到新线程,从而修复了日志中丢失的追踪 ID 和丢失的跨度。ContextPropagatingTaskDecorator 的整个设置将来可能会得到改进,以提供更无缝的体验。

上下文传播,再次

如果您正在处理多个服务,如果所有服务的追踪 ID 都相同,这样您就可以查看一个追踪并看到所有相关服务,或者找到参与该追踪的所有服务的日志,那不是很好吗?这就是分布式追踪中“分布式”的来源。

现在,您是否曾想过被调用的服务如何知道它是正在进行的追踪的一部分?这又是上下文传播,但这次上下文不是跨线程传播,而是跨进程边界传播。

有一个关于通过 HTTP 进行上下文传播的 W3C 建议,Spring Boot 开箱即用地配置了所有相关组件以使用它。发送方必须将当前追踪 ID 添加到标头中,接收方必须从标头中提取追踪 ID 并恢复上下文。

这一切都无缝运行,只要您遵循一个简单规则:不要谈论搏击俱乐部。哦,抱歉,剧本错了。您必须遵循的唯一规则是:不要自己 new 一个 RestTemplate / RestClient / WebClient。相反,注入一个 RestTemplateBuilder / RestClient.Builder / WebClient.Builder 并使用它来创建客户端。

Spring Boot 会自动配置这些构建器,并提供所有必要的基础设施,以自动在标头中发送追踪上下文。如果您自己用 new 创建客户端,那么此基础设施将不存在,上下文也不会传播,导致值班团队成员感到悲伤,并且冲刺回顾中出现红色方块。

让我们看看实际效果

我们准备了一个示例项目,您可以使用它来体验 OpenTelemetry 可观测性。它包含三个服务:

用户服务使用内存中的 H2 数据库和 Spring Data JDBC 根据用户 ID 查找用户。它公开了一个 HTTP API,用于根据给定的用户 ID 查找用户。

问候语服务有一个 HTTP API,它根据 Accept-Language 标头中指定的语言返回问候语。它知道英语、德语和西班牙语的问候语。

Hello 服务是入口点。它有一个 HTTP API,接收用户 ID 并返回该用户的问候语。为此,它会使用用户 ID 调用用户服务以获取用户的姓名。它还会调用问候语服务以获取问候语。然后它将用户的姓名与问候语结合起来并返回。

首先,让我们启动所有三个服务。此设置还包括 spring-boot-docker-compose 模块,该模块会自动检测名为 compose.yaml 的 Docker Compose 配置文件并调用 docker compose upcompose.yaml 文件包含一个使用 grafana/otel-lgtm 镜像的服务。Grafana LGTM 堆栈包含支持 OTLP 的日志、指标和追踪后端,所有这些都打包在一个 UI 中,我们可以使用它来查看可观测性信号。

Docker 容器启动并运行后,Spring Boot 会自动配置日志、指标和追踪的导出器,使其指向 Docker 容器。这就是为什么我们在 application.properties 中找不到前面提到的 OTLP 导出属性;这一切都在幕后进行。当您将其部署到生产环境时,您必须自己设置这些属性。如果您想了解更多关于这种开发者体验功能的信息,请阅读这篇博客文章

现在让我们执行第一个请求:

> curl -i https://:8080/api/1
HTTP/1.1 200 
X-Trace-Id: 0dbe0809731e35081d6db16c2ca0ef91
Content-Type: application/json
Content-Length: 12

Hello Moritz

太棒了,成功了。现在让我们用德语试试:

> curl -i https://:8080/api/1 --header "Accept-Language: de"
HTTP/1.1 200 
X-Trace-Id: 6c0753c7ec390fff15fcf05f536e21cd
Content-Type: application/json
Content-Length: 12

Hallo Moritz

很好,我们现在有两个追踪 ID 可以玩了。请注意,请求的追踪 ID 如何包含在 X-Trace-Id 标头中,使用了上面的 Servlet 过滤器。

让我们看看 Grafana UI 中是否有日志(您可以单击图像放大

A screenshot of Grafana, showing logs from all three services

在这里,我们可以看到日志已通过 OTLP 发送到 Grafana,我们现在可以在一个 UI 中查看来自三个服务的所有日志。也可以找到所有服务的日志,并根据追踪 ID 进行查找。

A screenshot of Grafana, showing logs from all three services for a given trace id

现在让我们看看是否可以找到追踪 ID 对应的追踪。

A screenshot of Grafana, showing the trace details

找到了!在这里,我们可以看到 hello 服务以漂亮的瀑布图调用了 greeting 服务和 user 服务。您还可以展开一个跨度以查看跨度属性。

A screenshot of Grafana, showing a span with the en-US locale

在这种情况下,我们使用了 Micrometer 的 @SpanTag 将问候语区域设置 (en_US) 和用户 ID (1) 附加到跨度。让我们看看第二个追踪,其中应该有德语区域设置:

A screenshot of Grafana, showing a span with the de locale

非常好,按预期工作。

最后一站,让我们看看服务生成的指标

A screenshot of Grafana, showing the custom say-hello metric

在这里,您可以看到一个自定义指标,名为 say-hello(通过使用 @Observed(name = "say-hello") 注解方法创建),它计算 hello 消息生成的次数。

您还可以开箱即用地获得大量关于应用程序的指标,例如执行器中的线程数、HTTP 服务器等。

A screenshot of Grafana, showing a part of the thread metrics

或者许多 JVM 指标

A screenshot of Grafana, showing a part of the JVM metrics

结论

我们希望您在 Spring Boot 与 OpenTelemetry 的这次旋风之旅中玩得开心。正如您所看到的,Spring Boot 与 OpenTelemetry 进行了很好的集成。无论您是否使用 Micrometer 的 Observation API,从 OTel 集成角度来看,这并不重要。重要的是协议 OTLP,它抽象了用于检测应用程序的 API。

带有新 OpenTelemetry starter 的 Spring Boot 4.0 将于 11 月 20 日发布。Micrometer 1.16 已发布,其中包含 OpenTelemetry 增强功能和许多其他新功能。如果您发现任何问题或对如何改进整个 OpenTelemetry 故事有好的想法,请在我们的问题跟踪器中联系我们!

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有