编辑此页面

使用 OpenTelemetry Metrics

本指南介绍 Quarkus 应用程序如何利用 OpenTelemetry (OTel) 为交互式 Web 应用程序提供指标。

此技术被认为是预览版。

预览阶段,不保证向后兼容性和生态系统中的存在性。特定的改进可能需要更改配置或 API,并且我们正在制定成为稳定版的计划。欢迎在我们的 邮件列表 或我们的 GitHub 问题跟踪器 中提出问题并反馈。

有关可能的完整状态列表,请查看我们的常见问题解答条目

本文档是 Quarkus 可观察性参考指南的一部分,该指南介绍了此组件和其他可观察性相关组件。

  • OpenTelemetry Metrics 被视为技术预览,默认情况下处于禁用状态。

  • OpenTelemetry 指南提供了关于 OpenTelemetry 扩展的独立于信号的信息。

  • 如果您想了解更多关于 OpenTelemetry Tracing 的信息,请参阅 OpenTelemetry Tracing 指南

先决条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • Docker 和 Docker Compose 或 Podman,以及 Docker Compose

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

架构

在本指南中,我们将创建一个简单的 REST 应用程序来演示分布式跟踪。

解决方案

我们建议您按照以下部分的说明逐步创建应用程序。但是,您可以直接跳到完成的示例。

克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载 存档

解决方案位于 opentelemetry-quickstart 目录中。

创建 Maven 项目

首先,我们需要一个新项目。使用以下命令创建一个新项目

CLI
quarkus create app org.acme:opentelemetry-quickstart \
    --extension='rest,quarkus-opentelemetry' \
    --no-code
cd opentelemetry-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=opentelemetry-quickstart \
    -Dextensions='rest,quarkus-opentelemetry' \
    -DnoCode
cd opentelemetry-quickstart

要创建 Gradle 项目,请添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

对于 Windows 用户

  • 如果使用 cmd,(不要使用反斜杠 \ 并将所有内容放在同一行上)

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=opentelemetry-quickstart"

此命令将生成 Maven 项目并导入 quarkus-opentelemetry 扩展,该扩展包含默认的 OpenTelemetry 支持,以及用于 OTLP 的 gRPC Span 导出器。

如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 quarkus-opentelemetry 扩展添加到您的项目中

CLI
quarkus extension add opentelemetry
Maven
./mvnw quarkus:add-extension -Dextensions='opentelemetry'
Gradle
./gradlew addExtension --extensions='opentelemetry'

这会将以下内容添加到您的构建文件中

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-opentelemetry</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-opentelemetry")

检查 Jakarta REST 资源

创建一个 src/main/java/org/acme/opentelemetry/MetricResource.java 文件,内容如下

package org.acme;

import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.jboss.logging.Logger;

@Path("/hello-metrics")
public class MetricResource {

    private static final Logger LOG = Logger.getLogger(MetricResource.class);

    private final LongCounter counter;

    public MetricResource(Meter meter) { (1)
        counter = meter.counterBuilder("hello-metrics") (2)
                .setDescription("hello-metrics")
                .setUnit("invocations")
                .build();
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        counter.add(1); (3)
        LOG.info("hello-metrics");
        return "hello-metrics";
    }
}

Quarkus 目前默认不生成指标。在这里,我们创建一个计数器来统计 hello() 方法的调用次数。

1 通过构造函数注入 Meter 实例。
2 创建一个名为 hello-metricsLongCounter,并附带描述和单位。
3 每次调用 hello() 方法时,计数器加一。

创建配置

OpenTelemetry Metrics 的唯一强制性配置是启用它的配置。

quarkus.otel.metrics.enabled=true

要更改任何默认属性值,以下是如何在应用程序中使用 src/main/resources/application.properties 文件配置默认 OTLP gRPC 导出器的示例。

quarkus.application.name=myservice (1)
quarkus.otel.metrics.enabled=true (2)
quarkus.otel.exporter.otlp.metrics.endpoint=https://:4317 (3)
quarkus.otel.exporter.otlp.metrics.headers=authorization=Bearer my_secret (4)
1 从应用程序创建的所有指标都将包含一个 OpenTelemetry Resource,表明这些指标是由 myservice 应用程序创建的。如果未设置,则默认为工件 ID。
2 启用 OpenTelemetry 指标。必须在构建时设置。
3 用于发送指标的 gRPC 端点。如果未设置,则默认为 https://:4317
4 通常用于身份验证的可选 gRPC 标头。

要使用相同的属性为所有信号配置连接,请参阅 OpenTelemetry 指南的 基本配置部分

要禁用 OpenTelemetry 的特定部分,您可以设置 OpenTelemetry 指南 此部分 中列出的属性。

运行应用程序

首先,我们需要启动一个系统来可视化 OpenTelemetry 数据。

查看数据

Grafana-OTel-LGTM 开发服务

您可以使用 Grafana-OTel-LGTM 开发服务。

此开发服务包含用于可视化数据的 Grafana、用于存储日志的 Loki、用于存储跟踪的 Tempo 以及用于存储指标的 Prometheus。它还提供了一个 OTel 收集器来接收数据。

日志记录导出器

您可以通过在 application.properties 文件中将导出器设置为 logging 来将所有指标输出到控制台。

quarkus.otel.metrics.exporter=logging (1)
quarkus.otel.metric.export.interval=10000ms (2)
1 将导出器设置为 logging。通常您不需要设置此项。默认值为 cdi
2 设置导出指标的间隔。默认值为 1m,对于调试来说太长了。

还将此依赖项添加到您的项目中

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-logging</artifactId>
</dependency>

启动应用程序

现在我们准备运行应用程序。如果使用 application.properties 配置跟踪器

CLI
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

或者如果通过 JVM 参数配置 OTLP gRPC 端点

CLI
quarkus dev -Djvm.args="-Dquarkus.otel.exporter.otlp.endpoint=https://:4317"
Maven
./mvnw quarkus:dev -Djvm.args="-Dquarkus.otel.exporter.otlp.endpoint=https://:4317"
Gradle
./gradlew --console=plain quarkusDev -Djvm.args="-Dquarkus.otel.exporter.otlp.endpoint=https://:4317"

在 OpenTelemetry Collector、Jaeger 系统和应用程序都运行的情况下,您可以向提供的端点发出请求。

$ curl https://:8080/hello-metrics
hello-metrics

使用 logger 导出器时,指标将打印到控制台。这是一个美化打印的示例。

{
  "metric": "ImmutableMetricData",
  "resource": {
    "Resource": {
      "schemaUrl": null,
      "attributes": { (1)
        "host.name": "myhost",
        "service.name": "myservice ",
        "service.version": "1.0.0-SNAPSHOT",
        "telemetry.sdk.language": "java",
        "telemetry.sdk.name": "opentelemetry",
        "telemetry.sdk.version": "1.32.0",
        "webengine.name": "Quarkus",
        "webengine.version": "999-SNAPSHOT"
      }
    },
    "instrumentationScopeInfo": {
      "InstrumentationScopeInfo": { (2)
        "name": "io.quarkus.opentelemetry",
        "version": null,
        "schemaUrl": null,
        "attributes": {}
      }
    },
    "name": "hello-metrics", (3)
    "description": "hello-metrics",
    "unit": "invocations",
    "type": "LONG_SUM",
    "data": {
      "ImmutableSumData": {
        "points": [
          {
            "ImmutableLongPointData": {
              "startEpochNanos": 1720622136612378000,
              "epochNanos": 1720622246618331000,
              "attributes": {},
              "value": 3, (4)
              "exemplars": [ (5)
                {
                  "ImmutableLongExemplarData": {
                    "filteredAttributes": {},
                    "epochNanos": 1720622239362357000,
                    "spanContext": {
                      "ImmutableSpanContext": {
                        "traceId": "d91951e50b0641552a76889c5356467c",
                        "spanId": "168af8b7102d0556",
                        "traceFlags": "01",
                        "traceState": "ArrayBasedTraceState",
                        "entries": [],
                        "remote": false,
                        "valid": true
                      },
                      "value": 1
                    }
                  }
                }
              ]
            }
          }
        ],
        "monotonic": true,
        "aggregationTemporality": "CUMULATIVE"
      }
    }
  }
}
1 所有遥测数据通用的资源属性。
2 Instrumentation scope 始终是 io.quarkus.opentelemetry
3 您在 MetricResource 类构造函数中定义的指标的名称、描述和单位。
4 指标的值。到目前为止已进行了 3 次调用。
5 Exemplars 提供了关于指标的额外跟踪信息。在这种情况下,是在最后一次发送指标时触发该指标的请求之一的 traceId 和 spanId。

CTRL+C 或键入 q 停止应用程序。

创建您自己的指标

OpenTelemetry 指标与 Micrometer 指标

指标是单一的数值测量,通常带有附加数据。这些辅助数据用于对指标进行分组或聚合以进行分析。

Quarkus Micrometer 扩展 中的情况几乎一样,您可以使用 OpenTelemetry API 创建自己的指标,并且概念是相似的。

OpenTelemetry API 提供了一个 Meter 接口来创建指标,而不是一个 Registry。Meter 接口是创建指标的入口点。它提供了创建计数器、仪表和直方图的方法。

可以向指标添加属性以添加维度,这与 Micrometer 中的标签非常相似。

获取 Meter 的引用

使用以下方法之一获取 Meter 的引用。

使用 CDI 构造函数注入

@Path("/hello-metrics")
public class MetricResource {

    private final Meter meter;

    public MetricResource(Meter meter) {
        this.meter = meter;
    }
}

上面的示例 中的情况非常相似。

使用 @Inject 注释的成员变量

@Inject
Meter meter;

计数器

计数器可用于测量非负、递增的值。

LongCounter counter = meter.counterBuilder("hello-metrics") (1)
        .setDescription("hello-metrics")                    // optional
        .setUnit("invocations")                             // optional
        .build();

counter.add(1, (2)
        Attributes.of(AttributeKey.stringKey("attribute.name"), "attribute value")); // optional (3)
1 创建一个名为 hello-metricsLongCounter,并附带描述和单位。
2 将计数器加一。
3 向计数器添加属性。这将创建一个名为 attribute.name,值为 attribute value 的维度。
指标名称和维度的每个唯一组合都会产生一个唯一的时间序列。使用无界维度数据集(如 userId 的许多不同值)可能导致“基数爆炸”,即新时间序列的创建呈指数级增长。请避免!

OpenTelemetry 提供了许多其他类型的计数器:LongUpDownCounterDoubleCounterDoubleUpDownCounter 以及 Observable、异步计数器,如 ObservableLongCounterObservableDoubleCounterObservableLongUpDownCounterObservableDoubleUpDownCounter

仪表

Observable Gauges 应用于测量非累加值。它是随时间可能增加或减少的值,例如汽车的速度表。仪表在监控缓存或集合的统计信息时很有用。

通过此指标,您可以提供一个函数,由回调函数定期探测。函数返回的值是仪表的返回值。

默认仪表记录 Double 值,但如果您想记录 Long 值,可以使用

meter.gaugeBuilder("jvm.memory.total")                      (1)
        .setDescription("Reports JVM memory usage.")
        .setUnit("byte")
        .ofLongs()                                          (2)
        .buildWithCallback(                                 (3)
                result -> result.record(
                        Runtime.getRuntime().totalMemory(), (4)
                        Attributes.empty()));               // optional (5)
1 创建一个名为 jvm.memory.totalGauge,并附带描述和单位。
2 如果您想记录 Long 值,您需要此构建器方法,因为默认仪表记录 Double 值。
3 使用回调函数构建仪表。也可以使用命令式构建器。
4 注册用于获取仪表值的调用函数。
5 这次没有添加属性。

直方图

直方图是同步工具,用于测量一段时间内的值分布。它用于直方图、摘要和百分位数等统计数据。请求持续时间和响应负载大小是直方图的良好用途。

在本节中,我们有一个新类 HistogramResource,它将创建一个 LongHistogram

package org.acme;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongHistogram;
import io.opentelemetry.api.metrics.Meter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.jboss.logging.Logger;

import java.util.Arrays;

@Path("/roll-dice")
public class HistogramResource {

    private static final Logger LOG = Logger.getLogger(HistogramResource.class);

    private final LongHistogram rolls;

    public HistogramResource(Meter meter) {
        rolls = meter.histogramBuilder("hello.roll.dice")  (1)
                .ofLongs()                                 (2)
                .setDescription("A distribution of the value of the rolls.")
                .setExplicitBucketBoundariesAdvice(Arrays.asList(1L, 2L, 3L, 4L, 5L, 6L, 7L)) (3)
                .setUnit("points")
                .build();
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String helloGauge() {
        var roll = roll();
        rolls.record(roll,                                 (4)
                Attributes.of(AttributeKey.stringKey("attribute.name"), "value"));            (5)
        LOG.info("roll-dice: " + roll);
        return "" + roll;
    }

    public long roll() {
        return (long) (Math.random() * 6) + 1;
    }
}
1 创建一个名为 hello.roll.diceLongHistogram,并附带描述和单位。
2 如果您想记录 Long 值,您需要此构建器方法,因为默认直方图记录 Double 值。
3 设置直方图的显式存储桶边界。边界是包含的。
4 记录掷骰子的值。
5 向直方图添加属性。这将创建一个名为 attribute.name,值为 value 的维度。
注意基数爆炸。

我们可以使用 curl 命令调用该端点。

$ curl https://:8080/roll-dice
2

如果我们执行 4 次连续请求,结果为 **2,2,3 和 4**,这将产生以下输出。为了简洁起见,忽略了 ResourceInstrumentationScopeInfo 数据。

//...
name=hello.roll.dice,
description=A distribution of the value of the rolls.,      (1)
unit=points,
type=HISTOGRAM,
data=ImmutableHistogramData{
    aggregationTemporality=CUMULATIVE,                      (2)
    points=[
        ImmutableHistogramPointData{
            getStartEpochNanos=1720632058272341000,
            getEpochNanos=1720632068279567000,
            getAttributes={attribute.name="value"},         (3)
            getSum=11.0,       (4)
            getCount=4,        (5)
            hasMin=true,
            getMin=2.0,        (6)
            hasMax=true,
            getMax=4.0,        (7)
            getBoundaries=[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0],   (8)
            getCounts=[0, 2, 1, 1, 0, 0, 0, 0],                  (9)
            getExemplars=[     (10)
                ImmutableDoubleExemplarData{
                    filteredAttributes={},
                    epochNanos=1720632063049392000,
                    spanContext=ImmutableSpanContext{
                        traceId=a22b43a600682ca7320516081eca998b,
                        spanId=645aa49f219181d0,
                        traceFlags=01,
                        traceState=ArrayBasedTraceState{entries=[]},
                        remote=false,
                        valid=true
                    },
                    value=2.0  (11)
                },
                //... exemplars for values 3 and 4 omitted for brevity
            ]
        }
    ]
}
1 您在 HistogramResource 类构造函数中定义的指标的名称、描述和单位。
2 直方图的聚合临时性。
3 记录值时添加到直方图的属性。
4 记录值的总和。
5 记录的值的数量。
6 记录的最小值。
7 记录的最大值。
8 直方图的显式存储桶边界。
9 每个存储桶中记录的值的数量。
10 带有记录值的跟踪数据的 Exemplars 列表。为简洁起见,我们仅显示 3 个 Exemplars 中的 1 个。
11 两次调用值 2 的其中一次。

与 Micrometer API 的区别

  • OpenTelemetry API 中没有 Timers 和 Distribution Summaries。而是使用 Histograms。

  • OpenTelemetry API 没有为指标定义像 Micrometer 的 @Counted@Timed@MeterTag 这样的注释。您需要手动创建指标。

  • OpenTelemetry 使用自己的 语义约定 来命名指标和属性。

资源

请参阅 OpenTelemetry 指南 主要资源 部分。

自动插装

Microprofile 2.0

我们为 JVM 指标和 HTTP 服务器请求指标提供了自动插装,符合 Microprofile Metrics 2.0 规范

可以通过将以下属性设置为 false 来禁用这些指标。

quarkus.otel.instrument.jvm-metrics=false
quarkus.otel.instrument.http-server-metrics=false

这些是截至 2025 年 6 月 12 日,当指标启用时 OpenTelemetry 扩展生成的指标。

指标名称 描述 类型 JVM 上可用? 原生映像可用? MP 2.0?

http.server.request.duration

HTTP 服务器请求的持续时间

HISTOGRAM

Y

Y

Y

jvm.memory.committed

已提交内存的度量

LONG_SUM

Y

无数据生成

Y

jvm.memory.used

已使用内存的度量

LONG_SUM

Y

无数据生成

Y

jvm.memory.limit

可获得的内存最大值的度量

LONG_SUM

Y

不适用

Y

jvm.memory.used_after_last_gc

自上次垃圾回收事件后此池使用的内存度量。

LONG_SUM

Y

无数据生成

Y

jvm.gc.duration

JVM 垃圾回收操作的持续时间

HISTOGRAM

Y

不适用

Y

jvm.class.count

当前已加载类的数量。

LONG_SUM

Y

无数据生成

Y

jvm.class.loaded

自 JVM 启动以来已加载的类的数量。

LONG_SUM

Y

无数据生成

Y

jvm.class.unloaded

自 JVM 启动以来已卸载的类的数量。

LONG_SUM

Y

无数据生成

Y

jvm.cpu.count

Java 虚拟机可用的处理器数量。

LONG_SUM

Y

Y

N

jvm.cpu.limit

LONG_SUM

Y

无数据生成

N

jvm.cpu.time

JVM 报告的进程使用的 CPU 时间。

DOUBLE_SUM

Y

不适用

N

jvm.system.cpu.utilization

JVM 报告的进程使用的 CPU 时间。

DOUBLE_SUM

不适用

无数据生成

N

jvm.cpu.recent_utilization

JVM 报告的进程的近期 CPU 利用率。

DOUBLE_GAUGE

Y

无数据生成

N

jvm.cpu.longlock

长时间锁定

HISTOGRAM

Y

Y

N

jvm.cpu.context_switch

DOUBLE_SUM

Y

无数据生成

N

jvm.network.io

网络读取/写入字节。

HISTOGRAM

Y

不适用

N

jvm.network.time

网络读取/写入持续时间。

HISTOGRAM

Y

不适用

N

jvm.thread.count

正在执行的平台线程数。

LONG_SUM

Y

Y

Y

上述原生映像评估是使用 GraalVM 23.0.2 进行的。未来将进行工作以改进原生映像构建中的指标支持。

  • 如果您同时使用 quarkus-micrometerquarkus-micrometer-opentelemetry 扩展,建议禁用这些插装。

Micrometer 到 OpenTelemetry 桥

Micrometer 到 OpenTelemetry 的桥接统一了 Quarkus 中的所有遥测。它生成 Micrometer 的指标,但将其与 OpenTelemetry 遥测输出一起发送。更多详情请访问 Micrometer 和 OpenTelemetry 扩展

导出器

请参阅 OpenTelemetry 指南 主要导出器 部分。

OpenTelemetry 配置参考

请参阅主要的 OpenTelemetry 指南配置参考。

相关内容