Using OpenTelemetry Tracing

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/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";
    }
}

请注意,应用程序中不包含任何跟踪特定代码。默认情况下,发送到此端点的请求将在无需任何所需代码更改的情况下被跟踪。

Create the configuration

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

默认情况下,导出程序将使用 gRPC 协议和端点 http://localhost:4317 批量发送数据。

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

quarkus.application.name=myservice (1)
quarkus.otel.exporter.otlp.endpoint=http://localhost: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 端点。如果未设置,它将默认为 http://localhost:4317
3 通常用于身份验证的可选 gRPC 标头
4 将跟踪信息添加到日志消息。
5 您也可以只将跟踪信息放入访问日志中。在这种情况下,您必须省略控制台日志格式中的信息。

我们为连接相关属性提供了信号无关的配置,这意味着在以下情况下可以同时使用相同的属性进行跟踪和度量:

quarkus.otel.exporter.otlp.endpoint=http://localhost:4317

如果您需要对每个信号使用不同的配置,则可以使用特定属性:

quarkus.otel.exporter.otlp.traces.endpoint=http://trace-uri:4317 (1)
quarkus.otel.exporter.otlp.metrics.endpoint=http://metrics-uri:4317 (2)
1 跟踪导出器的端点。
2 指标导出器的端点。

Run the application

首先我们需要启动一个系统来可视化 OpenTelemetry 数据。我们有 2 个选项:

  • 为跟踪和指标启动一体化 Grafana OTel LGTM 系统。

  • Jaeger 系统仅用于跟踪。

Grafana OTel LGTM option

此功能包含 Quarkus Dev 服务,其中包括用于可视化数据的 Grafana、用于存储日志的 Loki、用于存储跟踪的 Tempo 以及用于存储指标的 Prometheus。还提供和 OTel 收集器来接收数据。

Jaeger to see traces option

配置并启动 OpenTelemetry Collector 以接收、处理和将遥测数据导出到 Jaeger,该数据将显示捕获的跟踪。

一体化 Jaeger 包括 Jaeger 代理、OTel 收集器和查询服务/UI。您无需安装单独收集器。您可以直接将跟踪数据发送到 Jaeger(在此处启用 OTLP 接收器后,参见此 blog entry 了解更多详细信息)。

通过以下 docker-compose.yml 文件启动 OpenTelemetry 收集器和 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, not yet used by Quarkus, optional
      - "14250:14250" # Receive from external otel-collector, optional
    environment:
      - COLLECTOR_OTLP_ENABLED=true

您应删除不需要的可选端口。

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
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 instrumentation 会为您的应用程序执行的每个 JDBC 查询添加一个 span,要启用它,请将以下依赖项添加到您的构建文件:

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

由于它使用专用 JDBC 数据源包装器,因此您必须为数据源启用遥测:

# enable tracing
quarkus.datasource.jdbc.telemetry=true

# configure datasource
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase

Additional configuration

某些用例将需要 OpenTelemetry 的自定义配置。这些章节将概述正确配置它所需的步骤。

ID Generator

OpenTelemetry 扩展将默认情况下在创建跟踪和跨度标识符时使用随机 ID Generator

某些特定于供应商的协议需要自定义 ID 生成器,可通过创建生成器来覆盖默认值。OpenTelemetry 扩展将检测 `IdGenerator`CDI bean,并在配置跟踪生成器时使用该 bean。

@Singleton
public class CustomConfiguration {

    /** Creates a custom IdGenerator for OpenTelemetry */
    @Produces
    @Singleton
    public IdGenerator idGenerator() {
        return AwsXrayIdGenerator.getInstance();
    }
}

Propagators

OpenTelemetry 通过 propagators跨越式地传播关注点,该关注点将共享一个底层的 `Context`以在分布式事务的生命周期中存储状态和访问数据。

默认情况下,OpenTelemetry 扩展启用 W3C Trace ContextW3C Baggage传播器,但是,您可以通过设置 OpenTelemetry Configuration Reference中描述的 `propagators`配置来选择任何受支持的 OpenTelemetry 传播器。

Additional Propagators

  • b3b3multi、`jaeger`和 `ottrace`传播器需要将 trace-propagators扩展作为依赖项添加到您的项目中。

pom.xml
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-extension-trace-propagators</artifactId>
</dependency>
build.gradle
implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
  • `xray`传播器需要将 aws扩展作为依赖项添加到您的项目中。

pom.xml
<dependency>
    <groupId>io.opentelemetry.contrib</groupId>
    <artifactId>opentelemetry-aws-xray-propagator</artifactId>
</dependency>
build.gradle
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator")

Customise 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();
    }
}

Resource

请参阅主 OpenTelemetry Guide resources 部分。

End User attributes

启用时,Quarkus 将 OpenTelemetry 最终用户属性作为跨度属性添加。在启用此功能之前,请验证 Quarkus Security 扩展是否存在且已配置。有关 Quarkus Security 的更多信息,请参阅 Quarkus Security overview

只有在已尽力进行身份验证的情况下才会添加属性。是否将最终用户属性作为跨度属性添加取决于 Quarkus 应用程序的身份验证和授权配置。如果您在身份验证之前创建自定义跨度,Quarkus 无法向其添加最终用户属性。身份验证完成后,Quarkus 只能将属性添加到当前跨度。关于自定义跨度的另一个重要事项是用于传播 Quarkus `SecurityIdentity`的活动 CDI 请求上下文。原则上,当在创建自定义跨度之前已为您激活 CDI 请求上下文时,Quarkus 能够添加最终用户属性。

quarkus.otel.traces.eusp.enabled=true 1
quarkus.http.auth.proactive=true 2
1 启用最终用户属性功能,以便将 `SecurityIdentity`主体和角色作为跨度属性添加。最终用户属性是个人身份信息,因此在启用此功能之前,请确保您要导出这些信息。
2 选择性地启用主动身份验证。在启用主动身份验证时,可实现最佳效果,因为身份验证会更早发生。确定是否应在 Quarkus 应用程序中启用主动身份验证的一个好方法是阅读 Quarkus Proactive authentication指南。

当使用自定义 Jakarta REST SecurityContexts时,不支持此功能。

Sampler

sampler决定是否应放弃或转发跟踪,它有效地管理噪声,并通过限制发送给收集器的收集的跟踪数来减少开销。

Quarkus 配备了 built-in sampler,您还可以选择创建自定义抽样器。

若要使用内置抽样器,您可以通过设置 OpenTelemetry Configuration Reference中详述的所需抽样器参数来对其进行配置。例如,您可以将抽样器配置为保留 50% 的跟踪:

# build time property only:
quarkus.otel.traces.sampler=traceidratio
# Runtime property:
quarkus.otel.traces.sampler.arg=0.5

抽样器的一个有趣用例是根据此示例在运行时激活和停用跟踪导出:

# build time property only:
quarkus.otel.traces.sampler=traceidratio
# On (default). All traces are exported:
quarkus.otel.traces.sampler.arg=1.0
# Off. No traces are exported:
quarkus.otel.traces.sampler.arg=0.0

Quarkus 3.0 在配置方面引入了重大变更。 抽样器相关的属性名称和值更改以符合最新的 Java OpenTelemetry SDK。在过渡期间,可以将新配置值设置为旧属性,因为我们正在映射 quarkus.opentelemetry.tracer.samplerquarkus.otel.traces.sampler。 如果采样器基于父级,则无需设置,现在已删除的属性 quarkus.opentelemetry.tracer.sampler.parent-based。 您现在需要在 quarkus.opentelemetry.tracer.sampler 上设置的值为:

Old Sampler config value New Sampler config value 新的采样器配置值(基于父级)

on

always_on

parentbased_always_on

off

always_off

parentbased_always_off

ratio

traceidratio

parentbased_traceidratio

如果您需要使用自定义采样器,现在有 2 种不同的方法:

Sampler CDI Producer

您可以创建一个采样器 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 Sampler SPI

这将使用 OTel 自动配置提供的 SPI 钩子。您可以创建简单的采样器类:

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";
    }
}

然后是采样器提供程序:

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 上使用名称 io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider 编写 SPI 加载器文本文件,其中包含 CustomSPISamplerProvider 类的完全限定名称。

然后在配置中激活:

quarkus.otel.traces.sampler=custom-spi-sampler

如您所见,CDI 更易于使用。

Additional instrumentation

某些 Quarkus 扩展将需要额外的代码来确保跟踪传播到后续执行。这些部分将概述跨进程边界传播跟踪所需的条件。

本节中记录的检测已通过 Quarkus 测试,并且可在标准模式和原生模式下工作。

CDI

使用 io.opentelemetry.instrumentation.annotations.WithSpan 注释对任何 CDI 感知 bean 中的方法进行注释将创建一个新 Span,并与当前 Trace 上下文建立任何所需的关系。

使用 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) {

    }
}

Available OpenTelemetry CDI injections

根据 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();
}

Quarkus Messaging - Kafka

在使用针对 Kafka 的 Quarkus 消息传递扩展时,我们可以将跨度传播到 Kafka 记录中,方法是:

TracingMetadata tm = TracingMetadata.withPrevious(Context.current());
Message out = Message.of(...).withMetadata(tm);

以上内容创建了一个 TracingMetadata 对象,我们可以将其添加到正在生成的 Message,该对象会检索 OpenTelemetry Context 以提取用于传播的当前跨度。

Quarkus Security Events

Quarkus 支持将 Security events 导出为 OpenTelemetry Span 事件。

quarkus.otel.security-events.enabled=true 1
1 将 Quarkus Security 事件导出为 OpenTelemetry Span 事件。

Exporters

请参阅 OpenTelemetry Guide exporters 主部分。

Quarkus core extensions instrumented with OpenTelemetry tracing

Disable parts of the automatic tracing

通过将 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 Configuration Reference

请参阅 OpenTelemetry Guide configuration 主参考。