Using OpenTelemetry Metrics

Prerequisites

如要完成本指南,您需要:

  • Roughly 15 minutes

  • An IDE

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

  • Apache Maven ${proposed-maven-version}

  • 如果你想使用 Quarkus CLI, 则可以选择使用

  • 如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately

Architecture

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

Solution

我们建议你按照后续章节中的说明,逐步创建应用程序。但是,你可以直接跳到已完成的示例。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 opentelemetry-quickstart directory

Creating the Maven project

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

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

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

适用于 Windows 用户:

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

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

此命令会生成 Maven 项目并导入 quarkus-opentelemetry 扩展,其中包括默认的 OpenTelemetry 支持以及用于 OTLP 的 gRPC span 导出器。

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

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

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

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

Examine the Jakarta REST resource

使用以下内容创建一个 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() 方法的每次调用,将计数器增加一。

Create the configuration

扩展程序不需要任何强制配置即可工作。

如果您需要更改任何默认属性值,这里有一个示例,说明如何使用 src/main/resources/application.properties 文件在应用程序中配置默认 OTLP gRPC 导出程序:

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

要使用相同属性针对所有信号配置连接,请查看基本 configuration section of the OpenTelemetry guide

要禁用 OpenTelemetry 的特定部分,可以设置此 section of the OpenTelemetry guide 中列出的属性。

Run the application

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

See the data

Grafana-OTel-LGTM dev service

可以使用 Grafana-OTel-LGTM devservice。

此 Dev service 包括用于可视化数据的 Grafana、存储日志的 Loki、存储轨迹的 Tempo 以及存储指标的 Prometheus。还提供了 OTel 收集器来接收数据。

Logging exporter

application.properties 文件中设置导出器为 logging 后,可以将所有指标输出到控制台:

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

Start the application

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

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

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

include::./_includes/devtools/dev.adoc[]:!dev-additional-parameters:

当 OpenTelemetry 收集器、Jaeger 系统和应用程序正在运行时,可以向提供的端点发出请求:

$ curl http://localhost:8080/hello-metrics
hello-metrics

使用记录器导出器时,指标将打印到控制台上。下面是一个漂亮的打印示例:

{
  "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 检测范围始终为 io.quarkus.opentelemetry
3 指标的名称、说明和单位,均在 MetricResource 类的构建器中定义。
4 指标值。到目前为止已调用 3 次。
5 该指标的示例附加跟踪信息。在本例中,这是触发指标的某个请求的 traceId 和 spanId,因为它是最近发送的。

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

Create your own metrics

OpenTelemetry Metrics vs Micrometer Metrics

指标是单一数值测量值,通常会作为附加数据捕获。这种辅助数据用于对指标进行分组或汇总以便进行分析。

Quarkus Micrometer extension 中非常类似,可以使用 OpenTelemetry API 创建自己的指标,并且这些概念类似。

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

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

Obtain a reference to the Meter

使用以下方法之一来获得 Meter 引用:

Use CDI Constructor injection

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

    private final Meter meter;

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

example above 中的内容非常相似。

Member variable using the @Inject annotation

@Inject
Meter meter;

Counters

计数器用于测量非负值和递增值。

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,以及可观测的异步计数器,例如 ObservableLongCounterObservableDoubleCounterObservableLongUpDownCounterObservableDoubleUpDownCounter

有关更多详细信息,请参阅 OpenTelemetry Java documentation about Counters

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 用回调构建度量。l 命令式构建器也可用。
4 注册该函数,以便获得度量值。
5 这次没有添加属性。

Histograms

直方图是用于随时间测量值分布的同步工具。它适用于统计信息,例如直方图、摘要和百分比。请求持续时间和响应负载大小非常适合用于直方图。

在此部分中,我们有一个新类 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 http://localhost:8080/roll-dice
2

如果我们执行 4 个连续请求,结果 2,2,3 and 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 The minimum value recorded.
7 The maximum value recorded.
8 直方图的显式边界。
9 每个区间中记录的值的数量。
10 具有记录值的跟踪数据的示例列表。为了简洁起见,我们只显示了 3 个示例中的 1 个。
11 以值 2 进行的 2 次调用之一。

Differences with the Micrometer API

  • 计时器和分布摘要在 OpenTelemetry API 中不可用。请改用直方图。

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

  • OpenTelemetry 使用自己的 Semantic Conventions 来命名指标和属性。

Resource

请参阅主 OpenTelemetry Guide resources 部分。

Additional instrumentation

Quarkus OpenTelemetry 扩展尚未提供自动指标。我们计划在将来将现有的 Quarkus Micrometer 扩展指标桥接到 OpenTelemetry。

Exporters

请参阅 OpenTelemetry Guide exporters 主部分。

OpenTelemetry Configuration Reference

请参阅 OpenTelemetry Guide configuration 主参考。