如何用OpenTelemetry Java Instrumentation Agent捕获Spring Boot指标

965 阅读7分钟

在之前的一篇博文中,Adam Quan介绍了如何为Spring Boot应用程序设置可观察性精彩内容。对于度量,Adam使用了Prometheus Java Client库,并展示了如何使用范例来链接度量和跟踪。

然而,Prometheus Java Client库并不是从Spring Boot应用中获得度量的唯一方法。一个替代方法是使用OpenTelemetry Java仪表代理,以OpenTelemetry格式直接暴露Spring的度量。

这篇博文展示了如何用OpenTelemetry Java工具代理捕获Spring Boot指标。

在这篇博文中,我们将使用一个简单的Hello World REST服务作为应用实例。源代码来自Spring的Building a RESTful Web Service指南的示例代码./complete/ 目录:

git clone https://github.com/spring-guides/gs-rest-service.git
cd gs-rest-service/complete/
./mvnw clean package
java -jar target/rest-service-complete-0.0.1-SNAPSHOT.jar

该应用在8080端口暴露了一个REST服务,在那里你可以问候不同的名字,如http://localhost:8080/greeting?name=Grafana。它还没有暴露任何度量。

暴露一个普罗米修斯度量的端点

作为第一步,我们在我们的示例应用程序中启用度量,并直接以Prometheus格式公开这些度量。我们还不会使用OpenTelemetry的Java工具代理。

我们需要在pom.xml 中增加两个依赖项:


  org.springframework.boot
  spring-boot-starter-actuator


  io.micrometer
  micrometer-registry-prometheus
  runtime

spring-boot-starter-actuator 提供指标API和一些开箱即用的指标。在引擎盖下,它使用Micrometer度量库。micrometer-registry-prometheus 是为了以Prometheus格式暴露Micrometer度量。

接下来,我们需要启用Prometheus端点。用以下一行创建一个文件./src/main/resources/application.properties

management.endpoints.web.exposure.include=prometheus

重新编译并重启应用程序后,你会在http://localhost:8080/actuator/prometheus 上看到指标。开箱即用的指标包括一些JVM指标,如jvm_gc_pause_seconds ,一些来自日志框架的指标,如logback_events_total ,以及一些来自REST端点的指标,如http_server_requests

最后,我们希望有一个自定义指标来玩。自定义度量必须在Spring Boot提供的MeterRegistry 。所以第一步是把MeterRegistry 注入到GreetingController ,例如通过构造函数注入:

// ...
import io.micrometer.core.instrument.MeterRegistry;

@RestController
public class GreetingController {

  // ...
  private final MeterRegistry registry;

  // Use constructor injection to get the MeterRegistry
  public GreetingController(MeterRegistry registry) {
	this.registry = registry;
  }

  // ...
}

现在,我们可以添加我们的自定义度量。我们将创建一个Counter ,按名称追踪问候语的调用。我们将计数器添加到greeting() REST端点的现有实现中:

@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name){
 
  // Add a counter tracking the greeting calls by name
  registry.counter("greetings.total", "name", name).increment();
 
  // ...
}

让我们试试吧。重新编译并重启应用程序,用不同的名字调用问候语端点,比如http://localhost:8080/greeting?name=Grafana。http://localhost:8080/actuator/prometheus,你会看到指标greetings_total ,计算每个名字的调用数量:

# HELP greetings_total··
# TYPE greetings_total counter
greetings_total{name="Grafana",} 2.0
greetings_total{name="Prometheus",} 1.0

注意,使用用户输入作为标签值通常是个坏主意,因为这很容易导致cardinality爆炸(即为每个名字创建一个新的度量)。然而,在我们的例子中,这很方便,因为它给了我们一个简单的方法来尝试不同的标签值。

将OpenTelemetry收集器放在中间位置

OpenTelemetry收集器是一个用于接收、处理和输出遥测数据的组件。它通常位于中间,在要监测的应用程序和监测后端之间。

下一步,我们将配置一个OpenTelemetry收集器,从Prometheus端点抓取指标,并以Prometheus格式公开它们。

到目前为止,这不会增加任何功能,只是我们会得到OpenTelemetry收集器作为一个新的基础设施组件。收集器在8889端口暴露的指标应该与应用程序在8080端口暴露的指标相同。

github.com/open-teleme… 下载最新的otelcol_*.tar.gz 版本并将其解压。它应该包含一个名为otelcol 的可执行文件。在撰写本文时,最新的版本是otelcol_0.47.0_linux_amd64.tar.gz

创建一个名为config.yaml 的配置文件,内容如下:

receivers:
  prometheus:
    config:
      scrape_configs:
        - job_name: "example"
          scrape_interval: 5s
          metrics_path: '/actuator/prometheus'
          static_configs:
            - targets: ["localhost:8080"]

processors:
  batch:

exporters:
  prometheus:
    endpoint: "localhost:8889"

service:
  pipelines:
    metrics:
      receivers: [prometheus]
      processors: [batch]
      exporters: [prometheus]

现在运行采集器,用

./otelcol --config=config.yaml

你可以在http://localhost:8889/metrics 上访问度量。

附加OpenTelemetry Java仪器代理

我们现在准备把我们的应用程序从暴露Prometheus度量标准转换为直接提供OpenTelemetry度量标准。我们将摆脱应用程序中的Prometheus端点,使用OpenTelemetry的Java仪表代理来暴露指标。

首先,我们必须重新配置OpenTelemetry收集器的接收端,以使用OpenTelemetry线路协议(otlp),而不是从Prometheus端点刮取指标:

receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:

exporters:
  prometheus:
    endpoint: "localhost:8889"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

现在,从github.com/open-teleme…,下载最新版本的OpenTelemetry Java仪器代理代理中的度量值默认是禁用的,所以你需要通过设置环境变量OTEL_METRICS_EXPORTER=otlp 来启用它们。然后,在连接了代理的情况下重新启动示例程序:

export OTEL_METRICS_EXPORTER=otlp
java -javaagent:./opentelemetry-javaagent.jar -jar ./target/rest-service-complete-0.0.1-SNAPSHOT.jar

一分钟左右后,你会看到收集器再次在http://localhost:8889/metrics上暴露指标。然而,这一次它们被直接使用OpenTelemetry线路协议运送到收集器。应用程序的Prometheus端点不再参与。现在你可以删除application.properties 中的普罗米修斯端点配置,并从pom.xml 中删除micrometer-registry-prometheus 的依赖关系。

然而,如果你仔细观察,你会发现指标与之前暴露的不同。

衔接OpenTelemetry和Micrometer

我们现在在收集器中看到的指标是来自OpenTelemetry Java仪器代理本身。它们不是由Spring Boot应用程序维护的原始指标。代理在给我们提供一些关于REST端点调用的开箱即用的指标方面做得很好,比如http_server_duration 。但是,有些指标明显缺失,比如Spring框架最初提供的logback_events_total 。而我们的自定义指标greetings_total ,也不再可用了。

为了了解原因,我们需要看看Spring Boot度量的内部工作方式。Spring使用Micrometer作为其度量标准库。Micrometer为应用开发者提供了一个通用的API,并为供应商提供了一个灵活的仪表注册表,以便为他们特定的监控后端暴露指标。

在上面的第一步中,我们使用了Prometheus的仪表注册表,这是Micrometer的注册表,用于为Prometheus暴露度量。

用OpenTelemetry Java仪器代理捕获Micrometer指标几乎是开箱即用。该代理检测到Micrometer,并在飞行中注册了一个OpenTelemetryMeterRegistry

不幸的是,代理注册的是Micrometer的Metrics.globalRegistry ,而Spring通过依赖注入使用自己的注册实例。如果OpenTelemetryMeterRegistry 最终出现在错误的MeterRegistry 实例中,它就不会被Spring使用。

为了解决这个问题,我们需要将OpenTelemetry的OpenTelemetryMeterRegistry 作为一个Spring Bean来使用,这样Spring在设置依赖注入时就可以正确注册它。这可以通过在你的Spring boot应用程序中添加以下代码来实现:

@SpringBootApplication
public class RestServiceApplication {

  // Unregister the OpenTelemetryMeterRegistry from Metrics.globalRegistry and make it available
  // as a Spring bean instead.
  @Bean
  @ConditionalOnClass(name = "io.opentelemetry.javaagent.OpenTelemetryAgent")
  public MeterRegistry otelRegistry() {
	Optional otelRegistry = Metrics.globalRegistry.getRegistries().stream()
    	.filter(r -> r.getClass().getName().contains("OpenTelemetryMeterRegistry"))
    	.findAny();
	otelRegistry.ifPresent(Metrics.globalRegistry::remove);
	return otelRegistry.orElse(null);
  }

  // ...
}

上面的代码段从Micrometer的Metrics.globalRegistry 中取消了对OpenTelemetryMeterRegistry 的注册,而是将其作为Spring Bean公开。它只有在代理被连接时才会运行,这是通过@ConditionalOnClass 注解实现的。

在重新编译和重启应用程序后,所有的指标都将提供给OpenTelemetry收集器,包括所有原始的Spring Boot指标和我们自定义的greetings_total

有些信息是多余的,所以你甚至可以将Spring Boot提供的http_server_requests 中的信息与OpenTelemetry的Java仪器代理添加的http_server_duration 中的信息进行比较。

总结

在这篇博文中,我们向你展示了如何用OpenTelemetry Java仪器代理捕获Spring Boot指标。我们首先以Prometheus格式暴露Spring Boot指标,然后把OpenTelemetry收集器放在中间,然后把示例程序从暴露Prometheus切换到直接暴露OpenTelemetry线路协议。

最后,我们强调了你需要添加到你的Java应用中的几行代码,以便将Spring Boot的Micrometer指标与OpenTelemetry Java仪器代理提供的OpenTelemetryMeterRegistry