使用 OpenTelemetry Tracing
本指南将介绍 Quarkus 应用程序如何利用 OpenTelemetry (OTel) 为交互式 Web 应用程序提供分布式跟踪。
本文档是 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/TracedResource.java
文件,内容如下:
package org.acme.opentelemetry;
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")
public class TracedResource {
private static final Logger LOG = Logger.getLogger(TracedResource.class);
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
LOG.info("hello");
return "hello";
}
}
请注意,应用程序中没有包含任何特定的跟踪代码。默认情况下,发送到此端点的请求将自动进行跟踪,无需任何代码更改。
创建配置
默认情况下,导出器将使用 gRPC 协议和端点 https://:4317
批量发送数据。
如果您需要更改任何默认属性值,这里有一个示例,说明如何使用 src/main/resources/application.properties
文件在应用程序中配置默认的 OTLP gRPC 导出器。
quarkus.application.name=myservice (1)
quarkus.otel.exporter.otlp.endpoint=https://:4317 (2)
quarkus.otel.exporter.otlp.headers=authorization=Bearer my_secret (3)
quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, parentId=%X{parentId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n (4)
# Alternative to the console log
quarkus.http.access-log.pattern="...traceId=%{X,traceId} spanId=%{X,spanId}" (5)
1 | 来自应用程序的所有遥测数据都将包含一个 OpenTelemetry Resource 属性,指示遥测数据是由 myservice 应用程序创建的。如果未设置,则默认为工件 ID。 |
2 | 用于发送遥测数据的 gRPC 端点。如果未设置,则默认为 https://:4317 。 |
3 | 常用的可选 gRPC 标头,用于身份验证。 |
4 | 将跟踪信息添加到日志消息中。 |
5 | 您也可以只将跟踪信息放入访问日志中。在这种情况下,您必须在控制台日志格式中省略此信息。 |
我们为连接相关的属性提供了信号无关的配置,这意味着当您设置时,可以将相同的属性用于跟踪和指标。
quarkus.otel.exporter.otlp.endpoint=https://:4317
如果您需要为每个信号使用不同的配置,可以使用特定属性。
quarkus.otel.exporter.otlp.traces.endpoint=http://trace-uri:4317 (1)
quarkus.otel.exporter.otlp.metrics.endpoint=http://metrics-uri:4317 (2)
quarkus.otel.exporter.otlp.logs.endpoint=http://logs-uri:4317 (3)
1 | 跟踪导出器的端点。 |
2 | 指标导出器的端点。 |
3 | 日志导出器的端点。 |
如果您需要直接导出 Span 和日志(例如,在无服务器环境/应用程序中),您可以将此属性设置为 true
。这将替换默认的数据批处理。
quarkus.otel.simple=true
运行应用程序
首先,我们需要启动一个系统来可视化 OpenTelemetry 数据。我们有两个选项:
-
启动一个用于跟踪和指标的“一站式”Grafana OTel LGTM 系统。
-
仅用于跟踪的 Jaeger 系统。
Grafana OTel LGTM 选项
该系统包含一个 Quarkus Dev 服务,其中包含用于可视化数据的 Grafana、用于存储日志的 Loki、用于存储跟踪的 Tempo 以及用于存储指标的 Prometheus。它还提供了一个 OTel Collector 来接收数据。
Jaeger 查看跟踪选项
配置并启动 OpenTelemetry Collector 来接收、处理和导出遥测数据到 Jaeger,Jaeger 将显示捕获的跟踪。
Jaeger-all-in-one 包括 Jaeger Agent、OTel Collector 以及查询服务/UI。您无需安装单独的 Collector。您可以直接将跟踪数据发送到 Jaeger(在启用 OTLP 接收器后,例如,请参阅这篇 博文 了解详情)。 |
通过以下 docker-compose.yml
文件启动 OpenTelemetry Collector 和 Jaeger 系统,您可以通过 docker-compose up -d
来启动它。
version: "2"
services:
# Jaeger
jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "14268:14268" # Receive legacy OpenTracing traces, optional
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
- "14250:14250" # Receive from external otel-collector, optional
environment:
- COLLECTOR_OTLP_ENABLED=true
您应该删除不需要的可选端口。
启动应用程序
现在我们已准备好运行应用程序。如果使用 application.properties
配置跟踪器,请执行以下操作:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
或者,如果通过 JVM 参数配置 OTLP gRPC 端点:
quarkus dev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=https://:4317"
./mvnw quarkus:dev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=https://:4317"
./gradlew --console=plain quarkusDev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=https://:4317"
在 OpenTelemetry Collector、Jaeger 系统和应用程序都运行的情况下,您可以向提供的端点发送请求。
$ curl https://:8080/hello
hello
提交第一个请求后,您将在日志中看到跟踪信息。
10:49:02 INFO traceId=, parentId=, spanId=, sampled= [io.quarkus] (main) Installed features: [cdi, opentelemetry, resteasy-client, resteasy, smallrye-context-propagation, vertx]
10:49:03 INFO traceId=17ceb8429b9f25b0b879fa1503259456, parentId=3125c8bee75b7ad6, spanId=58ce77c86dd23457, sampled=true [or.ac.op.TracedResource] (executor-thread-1) hello
10:49:03 INFO traceId=ad23acd6d9a4ed3d1de07866a52fa2df, parentId=, spanId=df13f5b45cf4d1e2, sampled=true [or.ac.op.TracedResource] (executor-thread-0) hello
然后访问 Jaeger UI 以查看跟踪信息。
按 CTRL+C
或输入 q
停止应用程序。
JDBC
此扩展捆绑的 JDBC 仪表盘 将为您的应用程序执行的每个 JDBC 查询添加一个 Span。
由于它使用了专用的 JDBC 数据源包装器,因此您必须通过 quarkus.datasource.jdbc.telemetry
属性为数据源启用遥测,如下例所示:
# enable tracing
quarkus.datasource.jdbc.telemetry=true
# configure datasource
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://:5432/mydatabase
其他配置
某些用例需要对 OpenTelemetry 进行自定义配置。这些部分将概述正确配置它所需的步骤。
ID 生成器
OpenTelemetry 扩展默认使用随机 ID 生成器 来创建跟踪和 Span 标识符。
某些特定于供应商的协议需要自定义 ID 生成器,您可以通过创建生产者来覆盖默认生成器。OpenTelemetry 扩展将检测 IdGenerator
CDI Bean,并在配置 Tracer 提供程序时使用它。
@Singleton
public class CustomConfiguration {
/** Creates a custom IdGenerator for OpenTelemetry */
@Produces
@Singleton
public IdGenerator idGenerator() {
return AwsXrayIdGenerator.getInstance();
}
}
传播器
OpenTelemetry 通过 传播器 传播跨领域关注点,这些传播器将共享一个基础 Context
,用于在分布式事务的生命周期中存储状态和访问数据。
默认情况下,OpenTelemetry 扩展会启用 W3C Trace Context 和 W3C Baggage 传播器。但是,您可以通过设置 propagators
配置来选择任何受支持的 OpenTelemetry 传播器,具体请参阅 OpenTelemetry 配置参考。
其他传播器
-
b3
、b3multi
、jaeger
和ottrace
传播器需要将 trace-propagators 扩展作为依赖项添加到您的项目中。
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-extension-trace-propagators</artifactId>
</dependency>
implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
-
xray
传播器需要将 aws 扩展作为依赖项添加到您的项目中。
<dependency>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>opentelemetry-aws-xray-propagator</artifactId>
</dependency>
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator")
自定义传播器
要自定义传播标头,您可以实现 TextMapPropagatorCustomizer
接口。例如,这可用于限制 OpenTelemetry 跟踪标头的传播,并防止潜在的敏感数据发送到第三方系统。
/**
* /**
* Meant to be implemented by a CDI bean that provides arbitrary customization for the TextMapPropagator
* that are to be registered with OpenTelemetry
*/
public interface TextMapPropagatorCustomizer {
TextMapPropagator customize(Context context);
interface Context {
TextMapPropagator propagator();
ConfigProperties otelConfigProperties();
}
}
资源
请参阅主 OpenTelemetry 指南资源 部分。
最终用户属性
启用后,Quarkus 会将 OpenTelemetry 最终用户属性添加为 Span 属性。在启用此功能之前,请验证 Quarkus Security 扩展是否已存在并已配置。有关 Quarkus Security 的更多信息,请参阅 Quarkus Security 概述。
这些属性仅在身份验证发生后(尽最大努力)添加。最终用户属性是否作为 Span 属性添加取决于您的 Quarkus 应用程序的身份验证和授权配置。如果您在身份验证之前创建自定义 Span,Quarkus 无法将最终用户属性添加到其中。Quarkus 只能将属性添加到身份验证完成后处于当前状态的 Span。关于自定义 Span 的另一个重要考虑因素是活动 CDI 请求上下文,它用于传播 Quarkus SecurityIdentity
。原则上,当在创建自定义 Span 之前激活了 CDI 请求上下文时,Quarkus 能够添加最终用户属性。
quarkus.otel.traces.eusp.enabled=true (1)
quarkus.http.auth.proactive=true (2)
1 | 启用最终用户属性功能,以便将 SecurityIdentity 主体和角色作为 Span 属性添加。最终用户属性是个人身份信息,因此在启用此功能之前,请确保您希望导出它们。 |
2 | 可以选择启用主动身份验证。启用主动身份验证可获得最佳结果,因为身份验证会更早发生。确定是否应在 Quarkus 应用程序中启用主动身份验证的一个好方法是阅读 Quarkus 的 主动身份验证 指南。 |
当使用自定义 Jakarta REST SecurityContexts 时,不支持此功能。 |
采样器
采样器 决定是丢弃还是转发跟踪,通过限制发送到 Collector 的收集跟踪数量来有效地管理噪声并减少开销。
Quarkus 配备了 内置采样器,您也可以选择创建自定义采样器。
要使用内置采样器,您可以通过设置所需的采样器参数来配置它,具体请参阅 OpenTelemetry 配置参考。例如,您可以配置采样器以保留 50% 的跟踪。
# build time property only:
quarkus.otel.traces.sampler=traceidratio
# Runtime property:
quarkus.otel.traces.sampler.arg=0.5
采样器的一个有趣用例是根据此示例在运行时激活和停用跟踪导出。
|
如果您需要使用自定义采样器,现在有两种不同的方式:
采样器 CDI 生产者
您可以创建一个采样器 CDI 生产者。Quarkus OpenTelemetry 扩展将检测 Sampler
CDI Bean,并在配置 Tracer 时使用它。
@Singleton
public class CustomConfiguration {
/** Creates a custom sampler for OpenTelemetry */
@Produces
@Singleton
public Sampler sampler() {
return JaegerRemoteSampler.builder()
.setServiceName("my-service")
.build();
}
}
OTel 采样器 SPI
这将使用 OTel Autoconfiguration 提供的 SPI 钩子。您可以创建一个简单的 Sampler 类:
public class CustomSPISampler implements Sampler {
@Override
public SamplingResult shouldSample(Context context,
String s,
String s1,
SpanKind spanKind,
Attributes attributes,
List<LinkData> list) {
// Do some sampling here
return Sampler.alwaysOn().shouldSample(context, s, s1, spanKind, attributes, list);
}
@Override
public String getDescription() {
return "custom-spi-sampler-description";
}
}
然后是一个 Sampler Provider:
public class CustomSPISamplerProvider implements ConfigurableSamplerProvider {
@Override
public Sampler createSampler(ConfigProperties configProperties) {
return new CustomSPISampler();
}
@Override
public String getName() {
return "custom-spi-sampler";
}
}
在 resources/META-INF/services
目录下写入 SPI 加载器文本文件,文件名为 io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider
,其中包含 CustomSPISamplerProvider
类的完全限定名。
然后在配置中激活:
quarkus.otel.traces.sampler=custom-spi-sampler
如您所见,CDI 的工作方式更简单。
附加仪表盘
某些 Quarkus 扩展需要额外的代码来确保跟踪传播到后续执行。这些部分将概述在进程边界之间传播跟踪所需的步骤。
本节中介绍的仪表盘已在 Quarkus 中进行了测试,并在标准模式和原生模式下均可运行。
CDI
通过使用 io.opentelemetry.instrumentation.annotations.WithSpan
注解任何支持 CDI 的 Bean 中的方法,将创建一个新的 Span 并建立与当前跟踪上下文所需的任何关系。
通过使用 io.opentelemetry.instrumentation.annotations.AddingSpanAttributes
注解任何支持 CDI 的 Bean 中的方法,不会创建新的 Span,但会将带有注解的方法参数添加到当前 Span 的属性中。
如果一个方法被错误地注解了 @AddingSpanAttributes
和 @WithSpan
注解,则 @WithSpan
注解将具有优先权。
方法参数可以注解 io.opentelemetry.instrumentation.annotations.SpanAttribute
注解,以指示应将哪些方法参数包含在 Span 中。参数名称也可以自定义。
示例
@ApplicationScoped
class SpanBean {
@WithSpan
void span() {
}
@WithSpan("name")
void spanName() {
}
@WithSpan(kind = SERVER)
void spanKind() {
}
@WithSpan
void spanArgs(@SpanAttribute(value = "arg") String arg) {
}
@AddingSpanAttributes
void addArgumentToExistingSpan(@SpanAttribute(value = "arg") String arg) {
}
}
可用的 OpenTelemetry CDI 注入
根据 MicroProfile Telemetry Tracing 规范,Quarkus 支持以下类的 CDI 注入:
-
io.opentelemetry.api.OpenTelemetry
-
io.opentelemetry.api.trace.Tracer
-
io.opentelemetry.api.trace.Span
-
io.opentelemetry.api.baggage.Baggage
您可以在任何支持 CDI 的 Bean 中注入这些类。例如,Tracer
对于启动自定义 Span 特别有用。
@Inject
Tracer tracer;
...
public void tracedWork() {
Span span = tracer.spanBuilder("My custom span")
.setAttribute("attr", "attr.value")
.setParent(Context.current().with(Span.current()))
.setSpanKind(SpanKind.INTERNAL)
.startSpan();
// traced work
span.end();
}
Mutiny
返回响应式类型的方法也可以用 @WithSpan
和 @AddingSpanAttributes
进行注解,以创建新的 Span 或将属性添加到当前 Span。
如果您需要在 Mutiny 管道中手动创建 Span,请使用 io.quarkus.opentelemetry.runtime.tracing.mutiny.MutinyTracingHelper
中的 wrapWithSpan
方法。
示例。假设您有以下管道:
Uni<String> uni = Uni.createFrom().item("hello")
//start trace here
.onItem().transform(item -> item + " world")
.onItem().transform(item -> item + "!")
//end trace here
.subscribe().with(
item -> System.out.println("Received: " + item),
failure -> System.out.println("Failed with " + failure)
);
像这样包装它:
import static io.quarkus.opentelemetry.runtime.tracing.mutiny.MutinyTracingHelper.wrapWithSpan;
...
@Inject
Tracer tracer;
...
Context context = Context.current();
Uni<String> uni = Uni.createFrom().item("hello")
.transformToUni(m -> wrapWithSpan(tracer, Optional.of(context), "my-span-name",
Uni.createFrom().item(m)
.onItem().transform(item -> item + " world")
.onItem().transform(item -> item + "!")
))
.subscribe().with(
item -> System.out.println("Received: " + item),
failure -> System.out.println("Failed with " + failure)
);
对于多管道,操作类似:
Multi.createFrom().items("Alice", "Bob", "Charlie")
.transformToMultiAndConcatenate(m -> TracingHelper.withTrace("my-span-name",
Multi.createFrom().item(m)
.onItem().transform(name -> "Hello " + name)
))
.subscribe().with(
item -> System.out.println("Received: " + item),
failure -> System.out.println("Failed with " + failure)
);
如果您不关心项目的顺序,可以使用 transformToMultiAndMerge
代替 transformToMultiAndConcatenate
。
Quarkus Messaging - Kafka
在使用 Quarkus Messaging 扩展处理 Kafka 时,我们可以通过以下方式将 Span 传播到 Kafka Record 中:
TracingMetadata tm = TracingMetadata.withPrevious(Context.current());
Message out = Message.of(...).withMetadata(tm);
上述代码创建了一个 TracingMetadata
对象,我们可以将其添加到正在生成的 Message
中,该对象会检索 OpenTelemetry Context
以提取当前 Span 进行传播。
Quarkus 安全事件
Quarkus 支持将 安全事件 导出为 OpenTelemetry Span 事件。
quarkus.otel.security-events.enabled=true (1)
1 | 将 Quarkus 安全事件导出为 OpenTelemetry Span 事件。 |
导出器
请参阅主 OpenTelemetry 指南导出器 部分。
已使用 OpenTelemetry Tracing 仪表盘化的 Quarkus 核心扩展
-
-
AMQP 1.0
-
RabbitMQ
-
Kafka
-
Pulsar
-
-
quarkus-vertx
(http 请求)
禁用自动跟踪的部分
可以通过将 quarkus.otel.instrument.*
属性设置为 false
来禁用自动跟踪的各个部分。
示例
quarkus.otel.instrument.grpc=false
quarkus.otel.instrument.messaging=false
quarkus.otel.instrument.resteasy-client=false
quarkus.otel.instrument.rest=false
quarkus.otel.instrument.resteasy=false
OpenTelemetry 配置参考
请参阅主要的 OpenTelemetry 指南配置参考。