使用 OpenTelemetry Metrics
本指南介绍 Quarkus 应用程序如何利用 OpenTelemetry (OTel) 为交互式 Web 应用程序提供指标。
此技术被认为是预览版。 在预览阶段,不保证向后兼容性和生态系统中的存在性。特定的改进可能需要更改配置或 API,并且我们正在制定成为稳定版的计划。欢迎在我们的 邮件列表 或我们的 GitHub 问题跟踪器 中提出问题并反馈。 有关可能的完整状态列表,请查看我们的常见问题解答条目。 |
本文档是 Quarkus 可观察性参考指南的一部分,该指南介绍了此组件和其他可观察性相关组件。
|
先决条件
要完成本指南,您需要
-
大约 15 分钟
-
一个 IDE
-
已安装 JDK 17+ 并正确配置了
JAVA_HOME
-
Apache Maven 3.9.9
-
Docker 和 Docker Compose 或 Podman,以及 Docker Compose
-
如果您想使用它,可以选择 Quarkus CLI
-
如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置
解决方案
我们建议您按照以下部分的说明逐步创建应用程序。但是,您可以直接跳到完成的示例。
克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git
,或下载 存档。
解决方案位于 opentelemetry-quickstart
目录中。
创建 Maven 项目
首先,我们需要一个新项目。使用以下命令创建一个新项目
对于 Windows 用户
-
如果使用 cmd,(不要使用反斜杠
\
并将所有内容放在同一行上) -
如果使用 Powershell,请将
-D
参数用双引号括起来,例如"-DprojectArtifactId=opentelemetry-quickstart"
此命令将生成 Maven 项目并导入 quarkus-opentelemetry
扩展,该扩展包含默认的 OpenTelemetry 支持,以及用于 OTLP 的 gRPC Span 导出器。
如果您已经配置了 Quarkus 项目,则可以通过在项目基本目录中运行以下命令将 quarkus-opentelemetry
扩展添加到您的项目中
quarkus extension add opentelemetry
./mvnw quarkus:add-extension -Dextensions='opentelemetry'
./gradlew addExtension --extensions='opentelemetry'
这会将以下内容添加到您的构建文件中
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
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-metrics 的 LongCounter ,并附带描述和单位。 |
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
配置跟踪器
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
或者如果通过 JVM 参数配置 OTLP gRPC 端点
quarkus dev -Djvm.args="-Dquarkus.otel.exporter.otlp.endpoint=https://:4317"
./mvnw quarkus:dev -Djvm.args="-Dquarkus.otel.exporter.otlp.endpoint=https://:4317"
./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;
}
}
与 上面的示例 中的情况非常相似。
计数器
计数器可用于测量非负、递增的值。
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-metrics 的 LongCounter ,并附带描述和单位。 |
2 | 将计数器加一。 |
3 | 向计数器添加属性。这将创建一个名为 attribute.name ,值为 attribute value 的维度。 |
指标名称和维度的每个唯一组合都会产生一个唯一的时间序列。使用无界维度数据集(如 userId 的许多不同值)可能导致“基数爆炸”,即新时间序列的创建呈指数级增长。请避免! |
OpenTelemetry 提供了许多其他类型的计数器:LongUpDownCounter
、DoubleCounter
、DoubleUpDownCounter
以及 Observable、异步计数器,如 ObservableLongCounter
、ObservableDoubleCounter
、ObservableLongUpDownCounter
和 ObservableDoubleUpDownCounter
。
更多详情请参阅 OpenTelemetry Java 关于计数器的文档。
仪表
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.total 的 Gauge ,并附带描述和单位。 |
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.dice 的 LongHistogram ,并附带描述和单位。 |
2 | 如果您想记录 Long 值,您需要此构建器方法,因为默认直方图记录 Double 值。 |
3 | 设置直方图的显式存储桶边界。边界是包含的。 |
4 | 记录掷骰子的值。 |
5 | 向直方图添加属性。这将创建一个名为 attribute.name ,值为 value 的维度。 |
注意基数爆炸。 |
我们可以使用 curl 命令调用该端点。
$ curl https://:8080/roll-dice
2
如果我们执行 4 次连续请求,结果为 **2,2,3 和 4**,这将产生以下输出。为了简洁起见,忽略了 Resource
和 InstrumentationScopeInfo
数据。
//...
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 进行的。未来将进行工作以改进原生映像构建中的指标支持。
|
Micrometer 到 OpenTelemetry 桥
Micrometer 到 OpenTelemetry 的桥接统一了 Quarkus 中的所有遥测。它生成 Micrometer 的指标,但将其与 OpenTelemetry 遥测输出一起发送。更多详情请访问 Micrometer 和 OpenTelemetry 扩展。
导出器
请参阅 OpenTelemetry 指南 主要导出器 部分。
OpenTelemetry 配置参考
请参阅主要的 OpenTelemetry 指南配置参考。