Using OpenTelemetry Metrics
本指南解释了您的 Quarkus 应用程序如何利用 OpenTelemetry (OTel) 为交互式 Web 应用程序提供指标。
This guide explains how your Quarkus application can utilize OpenTelemetry (OTel) to provide metrics for interactive web applications.
|
Prerequisites
Unresolved directive in opentelemetry-metrics.adoc - include::{includes}/prerequisites.adoc[]
Architecture
本指南中,我们将创建一个简单的 REST 应用程序来演示分布式追踪。
In this guide, we create a straightforward REST application to demonstrate distributed tracing.
Solution
我们建议你按照后续章节中的说明,逐步创建应用程序。但是,你可以直接跳到已完成的示例。
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can skip right to the completed example.
克隆 Git 存储库: git clone {quickstarts-clone-url}
,或下载 {quickstarts-archive-url}[存档]。
Clone the Git repository: git clone {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
解决方案位于 opentelemetry-quickstart
directory。
The solution is located in the opentelemetry-quickstart
directory.
Creating the Maven project
首先,我们需要一个新项目。使用以下命令创建一个新项目:
First, we need a new project. Create a new project with the following command:
Unresolved directive in opentelemetry-metrics.adoc - include::{includes}/devtools/create-app.adoc[]
此命令会生成 Maven 项目并导入 quarkus-opentelemetry
扩展,其中包括默认的 OpenTelemetry 支持以及用于 OTLP 的 gRPC span 导出器。
This command generates the Maven project and imports the quarkus-opentelemetry
extension,
which includes the default OpenTelemetry support,
and a gRPC span exporter for OTLP.
如果 Quarkus 项目已配置,则可以通过在项目基本目录中运行以下命令来将 quarkus-opentelemetry
扩展添加到项目中:
If you already have your Quarkus project configured, you can add the quarkus-opentelemetry
extension
to your project by running the following command in your project base directory:
Unresolved directive in opentelemetry-metrics.adoc - include::{includes}/devtools/extension-add.adoc[]
这会将以下内容添加到构建文件中:
This will add the following to your build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
implementation("io.quarkus:quarkus-opentelemetry")
Examine the Jakarta REST resource
使用以下内容创建一个 src/main/java/org/acme/opentelemetry/MetricResource.java
文件:
Create a src/main/java/org/acme/opentelemetry/MetricResource.java
file with the following content:
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()
方法调用的次数创建一个计数器。
Quarkus is not currently producing metrics out of the box.
Here we are creating a counter for the number of invocations of the hello()
method.
1 | Constructor injection of the Meter instance. |
2 | Create a LongCounter named hello-metrics with a description and unit. |
3 | Increment the counter by one for each invocation of the hello() method. |
Create the configuration
扩展程序不需要任何强制配置即可工作。
There are no mandatory configurations for the extension to work.
如果您需要更改任何默认属性值,这里有一个示例,说明如何使用 src/main/resources/application.properties
文件在应用程序中配置默认 OTLP gRPC 导出程序:
If you need to change any of the default property values, here is an example on how to configure the default OTLP gRPC Exporter within the application, using the src/main/resources/application.properties
file:
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 | All metrics created from the application will include an OpenTelemetry Resource indicating the metrics was created by the myservice application.
If not set, it will default to the artifact id. |
2 | Enable the OpenTelemetry metrics. Must be set at build time. |
3 | gRPC endpoint to send the metrics.
If not set, it will default to http://localhost:4317 . |
4 | Optional gRPC headers commonly used for authentication. |
要使用相同属性针对所有信号配置连接,请查看基本 configuration section of the OpenTelemetry guide。
To configure the connection using the same properties for all signals, please check the base configuration section of the OpenTelemetry guide.
要禁用 OpenTelemetry 的特定部分,可以设置此 section of the OpenTelemetry guide 中列出的属性。
To disable particular parts of OpenTelemetry, you can set the properties listed in this section of the OpenTelemetry guide.
Run the application
首先,我们需要启动一个系统来可视化 OpenTelemetry 数据。
First we need to start a system to visualise the OpenTelemetry data.
See the data
Grafana-OTel-LGTM dev service
可以使用 Grafana-OTel-LGTM devservice。
You can use the Grafana-OTel-LGTM devservice.
此 Dev service 包括用于可视化数据的 Grafana、存储日志的 Loki、存储轨迹的 Tempo 以及存储指标的 Prometheus。还提供了 OTel 收集器来接收数据。
This Dev service includes a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides and OTel collector to receive the data.
Logging exporter
在 application.properties
文件中设置导出器为 logging
后,可以将所有指标输出到控制台:
You can output all metrics to the console by setting the exporter to logging
in the application.properties
file:
quarkus.otel.metrics.exporter=logging 1
quarkus.otel.metric.export.interval=10000ms 2
1 | Set the exporter to logging .
Normally you don’t need to set this.
The default is cdi . |
2 | Set the interval to export the metrics.
The default is 1m , which is too long for debugging. |
Start the application
现在我们的应用程序已准备好运行。如果使用 application.properties
来配置跟踪器:
Now we are ready to run our application.
If using application.properties
to configure the tracer:
Unresolved directive in opentelemetry-metrics.adoc - include::{includes}/devtools/dev.adoc[]
或者通过 JVM 参数配置 OTLP gRPC 端点:
or if configuring the OTLP gRPC endpoint via JVM arguments:
include::{includes}/devtools/dev.adoc[]:!dev-additional-parameters:
Unresolved directive in opentelemetry-metrics.adoc - include::{includes}/devtools/dev.adoc[] :!dev-additional-parameters:
当 OpenTelemetry 收集器、Jaeger 系统和应用程序正在运行时,可以向提供的端点发出请求:
With the OpenTelemetry Collector, the Jaeger system and the application running, you can make a request to the provided endpoint:
$ curl http://localhost:8080/hello-metrics
hello-metrics
使用记录器导出器时,指标将打印到控制台上。下面是一个漂亮的打印示例:
When using the logger exporter, metrics will be printed to the console. This is a pretty printed example:
{
"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 | Resource attributes common to all telemetry data. |
2 | Instrumentation scope is allways io.quarkus.opentelemetry |
3 | The name, description and unit of the metric you defined in the constructor of the MetricResource class. |
4 | The value of the metric. 3 invocations were made until now. |
5 | Exemplars additional tracing information about the metric. In this case, the traceId and spanId of one os the request that triggered the metric, since it was last sent. |
按 CTRL+C
或键入 q
停止应用程序。
Hit CTRL+C
or type q
to stop the application.
Create your own metrics
OpenTelemetry Metrics vs Micrometer Metrics
指标是单一数值测量值,通常会作为附加数据捕获。这种辅助数据用于对指标进行分组或汇总以便进行分析。
Metrics are single numerical measurements, often have additional data captured with them. This ancillary data is used to group or aggregate metrics for analysis.
与 Quarkus Micrometer extension 中非常类似,可以使用 OpenTelemetry API 创建自己的指标,并且这些概念类似。
Pretty much like in the Quarkus Micrometer extension, you can create your own metrics using the OpenTelemetry API and the concepts are analogous.
OpenTelemetry API 提供了一个 Meter
接口,用于创建指标,而不是注册表。Meter
接口是创建指标的入口点。它提供了创建计数器、仪表和直方图的方法。
The OpenTelemetry API provides a Meter
interface to create metrics instead of a Registry.
The Meter
interface is the entry point for creating metrics.
It provides methods to create counters, gauges, and histograms.
可以将属性添加到指标中以添加维度,这与 Micrometer 中的标签非常相似。
Attributes can be added to metrics to add dimensions, pretty much like tags in Micrometer.
Obtain a reference to the Meter
使用以下方法之一来获得 Meter 引用:
Use one of the following methods to obtain a reference to a Meter:
Use CDI Constructor injection
@Path("/hello-metrics")
public class MetricResource {
private final Meter meter;
public MetricResource(Meter meter) {
this.meter = meter;
}
}
与 example above 中的内容非常相似。
Pretty much like in the metric-resource-class.
Counters
计数器用于测量非负值和递增值。
Counters can be used to measure non-negative, increasing values.
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 | Create a LongCounter named hello-metrics with a description and unit. |
2 | Increment the counter by one. |
3 | Add an attribute to the counter.
This will create a dimension called attribute.name with value attribute value . |
指标名称和维度的每个唯一组合都会产生一个唯一的时间序列。使用一组无限制的维度数据(许多不同的值,例如 userId)会导致“基数爆炸”,即新时间序列创建呈指数增长。避免这种情况!
Each unique combination of metric name and dimension produces a unique time series. Using an unbounded set of dimensional data (many different values like a userId) can lead to a "cardinality explosion", an exponential increase in the creation of new time series. Avoid!
OpenTelemetry 提供许多其他类型的计数器: LongUpDownCounter
、 DoubleCounter
、 DoubleUpDownCounter
,以及可观测的异步计数器,例如 ObservableLongCounter
、 ObservableDoubleCounter
、 ObservableLongUpDownCounter
和 ObservableDoubleUpDownCounter
。
OpenTelemetry provides many other types of Counters: LongUpDownCounter
, DoubleCounter
, DoubleUpDownCounter
and also Observable, async counters like ObservableLongCounter
, ObservableDoubleCounter
, ObservableLongUpDownCounter
and ObservableDoubleUpDownCounter
.
有关更多详细信息,请参阅 OpenTelemetry Java documentation about Counters。
For more details please refer to the OpenTelemetry Java documentation about Counters.
Gauges
可观测测量器用于测量非加性值。可以随时间增加或减少的值,就像汽车上的速度计。测量器在监视缓存或收集的统计数据时非常有用。
Observable Gauges should be used to measure non-additive values. A value that can increase or decrease over time, like the speedometer on a car. Gauges can be useful when monitoring the statistics for a cache or collection.
使用此指标向回调提供要定期探测的函数。该函数返回的值是该度量的值。
With this metric you provide a function to be periodically probed by a callback. The value returned by the function is the value of the gauge.
默认度量会记录 Double
值,但如果您想记录 Long
值,您可以使用
The default gauge records Double
values, but if you want to record Long
values, you can use
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 | Create a Gauge named jvm.memory.total with a description and unit. |
2 | If you want to record Long values you need this builder method because the default gauge records Double values. |
3 | Build the gauge with a callback. An imperative builder is also available. |
4 | Register the function to call to get the value of the gauge. |
5 | No added attributes, this time. |
Histograms
直方图是用于随时间测量值分布的同步工具。它适用于统计信息,例如直方图、摘要和百分比。请求持续时间和响应负载大小非常适合用于直方图。
Histograms are synchronous instruments used to measure a distribution of values over time. It is intended for statistics such as histograms, summaries, and percentile. The request duration and response payload size are good uses for a histogram.
在此部分中,我们有一个新类 HistogramResource
,它将创建一个 LongHistogram
。
On this section we have a new class, the HistogramResource
that will create a 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 | Create a LongHistogram named hello.roll.dice with a description and unit. |
2 | If you want to record Long values you need this builder method because the default histogram records Double values. |
3 | Set the explicit bucket boundaries for the histogram. The boundaries are inclusive. |
4 | Record the value of the roll. |
5 | Add an attribute to the histogram.
This will create a dimension called attribute.name with value value . |
注意基数爆炸。
Beware of cardinality explosion.
我们可以用 curl 命令调用端点。
We can invoke the endpoint with a curl command.
$ curl http://localhost:8080/roll-dice
2
如果我们执行 4 个连续请求,结果 2,2,3 and 4 将产生以下输出。为了简洁起见忽略了 Resource
和 InstrumentationScopeInfo
数据。
If we execute 4 consecutive requests, with results 2,2,3 and 4 this will produce the following output.
The Resource
and InstrumentationScopeInfo
data are ignored for brevity.
//...
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 | The name, description and unit of the metric you defined in the constructor of the HistogramResource class. |
2 | The aggregation temporality of the histogram. |
3 | The attribute added to the histogram when the values were recorded. |
4 | The sum of the values recorded. |
5 | The number of values recorded. |
6 | The minimum value recorded. |
7 | The maximum value recorded. |
8 | The explicit bucket boundaries for the histogram. |
9 | The number of values recorded in each bucket. |
10 | The list of exemplars with tracing data for the values recorded. We only show 1 of 3 exemplars for brevity. |
11 | One of the 2 calls made with the value 2. |
Differences with the Micrometer API
-
Timers and Distribution Summaries are not available in the OpenTelemetry API. Instead, use Histograms.
-
The OpenTelemetry API does not define annotations for metrics like Micrometer’s
@Counted
,@Timed
or@MeterTag
. You need to manually create the metrics. -
OpenTelemetry uses their own Semantic Conventions to name metrics and attributes.
Additional instrumentation
Quarkus OpenTelemetry 扩展尚未提供自动指标。我们计划在将来将现有的 Quarkus Micrometer 扩展指标桥接到 OpenTelemetry。
Automatic metrics are not yet provided by the Quarkus OpenTelemetry extension. We plan to bridge the existing Quarkus Micrometer extension metrics to OpenTelemetry in the future.
Exporters
请参阅 OpenTelemetry Guide exporters 主部分。
See the main OpenTelemetry Guide exporters section.
OpenTelemetry Configuration Reference
请参阅 OpenTelemetry Guide configuration 主参考。
See the main OpenTelemetry Guide configuration reference.