Migrate from OpenTracing to OpenTelemetry tracing
在 Quarkus 3.x 中将应用程序从 OpenTracing迁移到 OpenTelemetry tracing。 现有的 OpenTracing 框架已不再被支持,取而代之的是新的 OpenTelemetry 追踪框架。我们宣布了 OpenTracing deprecation on November 2022,并且正在从 Quarkus 核心存储库中删除扩展并将其移至 Quarkiverse Hub。 如果您尚未将您的应用程序迁移至 OpenTelemetry 追踪,现在是时候执行此操作了。 如果您需要从 Quarkus 2.16.x 进行迁移,请注意配置属性有所不同,您需要检查旧的 Quarkus OpenTelemetry 指南版本 here。
Prerequisites
如要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
安装了 JDK 17+,已正确配置
JAVA_HOME
-
Apache Maven ${proposed-maven-version}
-
如果你想使用 Quarkus CLI, 则可以选择使用
-
如果你想构建一个本机可执行文件(或如果你使用本机容器构建,则使用 Docker),则可以选择安装 Mandrel 或 GraalVM 以及 configured appropriately
Summary
演示分为 5 个部分。请先阅读摘要,然后跳转至最符合您的用例的部分。
1 - *starting point*展示了使用 OpenTracing 的 quickstart 应用
2 - 如果您在没有任何手动检测的情况下执行 *big bang change*OpenTracing,第一个部分适合任何人
3 - 这是在手动检测代码时 *big bang replacement*OpenTracing。我们解释了 OpenTracing 和 OpenTelemetry 之间的主要区别
4 - 最后的部分使用了 OpenTracing shim。如果您有一个手动检测代码的大型应用程序,这将非常有用。它可以通过允许在新的 OpenTelemetry API 之上使用旧的 OpenTracing API 帮助逐步执行迁移
5 - 结论和额外资源
下面描述的任务分为 3 类:
-
Dependencies
-
Configuration
-
Code
Starting point
本教程建立在 `opentracing-quickstart`传统项目之上。
Generate the legacy project
通过执行以下命令来创建旧项目:
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 指南。
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}"
此命令将生成一个导入 `smallrye-opentracing`扩展(其中包括 OpenTracing 支持和默认 Jaeger跟踪器)的 Maven 结构。
Check out the existing legacy project
为了方便起见,github 中有一个包含教程所有步骤的项目。您可以使用以下命令对其进行克隆:
git clone git@github.com:quarkusio/opentracing-quickstart-migration.git
为了方便起见, the repository包含要迁移的应用程序,其中包括几个分支,这些分支包含模仿本教程中描述的迁移步骤的提交。您可以签出 `main`分支从头开始。
The application
Quarkus 项目有一个端点,相关的类如下所示:
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "Hello from Quarkus REST";
}
}
在生成的项目中没有明确的 OpenTracing 代码,但是 `smallrye-opentracing`扩展默认情况下已存在并启用,并且它会自动检测代码。
我们启动 Jaeger-all-in-one Docker 镜像,我们将在其中检索并查看捕获的跟踪:
docker run -e COLLECTOR_OTLP_ENABLED=true -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest
此时,您可以使用 Quarkus 开发模式运行应用程序:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
如果您调用 /hello
endpoint,则可以在此地址的 Jaeger UI 中检索相关跟踪:[role="bare"][role="bare"]http://localhost:16686/
它们将如下所示:
Big bang change from OpenTracing to OpenTelemetry
这是最简单的路径,这种情况不需要手工检测。我们可以在没有副作用的情况下,将 OpenTracing 大幅度更改为 OpenTelemetry。
Change dependencies
要在两个框架之间迁移,必须删除旧的 quarkus-smallrye-opentracing
扩展,并用构建文件中的 quarkus-opentelemetry
扩展替换它:
从项目中删除传统扩展:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-opentracing</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-opentracing")
添加新的扩展:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
implementation("io.quarkus:quarkus-opentelemetry")
Application properties
您应该从 application.properties
文件中删除旧的 OpenTracing 属性,以 quarkus.jaeger.*
开头,如下例所示:
#Legacy OpenTracing properties to be removed
quarkus.jaeger.service-name=legume
quarkus.jaeger.sampler-type=const
quarkus.jaeger.sampler-param=1
quarkus.jaeger.endpoint=http://localhost:14268/api/traces
quarkus.jaeger.log-trace-context=true
如果您在 OpenTelemetry 属性中使用默认值,则不必在 application.properties
文件中包含任何内容。
一些常见要迁移的属性为:
Legacy OpenTracing property | New OpenTelemetry property |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
启用和禁用扩展的方式截然不同。默认情况下启用 OpenTelemetry 扩展,您可以通过选中 this section of the OpenTelemetry guide 来禁用全部或部分内容。
所有 OpenTelemetry 属性及其默认值都可以在 OpenTelemetry configuration reference 中找到。
Run the application
无需重新启动 Quarkus,自动重新加载应已启动,您现在可以调用 /hello
endpoint,然后在 Jaeger UI 中查看跟踪:[role="bare"][role="bare"]http://localhost:16686/
但是,您现在可以看到 OpenTelemetry 自动检测产生的跨度,而不是 OpenTracing 产生的跨度:
如果您没有自己的任何手工检测,就完成了!
The big bang replacement, when you have manual instrumentation
假设不是上述 GreetingResource
类,而是更复杂的内容。除了 Starting point 的更改之外,还需要执行其他工作。
此类现在使用 @Traced
注释并创建"手动"编程跨度。
将该代码复制/粘贴到快速入门项目中的 GreetingResource
类:
The GreetingsResource with OpenTracing manual instrumentation
package org.acme;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.opentracing.Traced;
@Path("/hello")
@ApplicationScoped
public class GreetingResource {
@Inject
io.opentracing.Tracer legacyTracer; 1
@GET
@Produces(MediaType.TEXT_PLAIN)
@Traced(operationName = "Not needed, will change the current span name") 2
public String hello() {
// Add a tag to the active span
legacyTracer.activeSpan().setTag(Tags.COMPONENT, "GreetingResource"); 3
// Create a manual inner span
Span innerSpan = legacyTracer.buildSpan("Count response chars").start();
try (Scope dbScope = legacyTracer.scopeManager().activate(innerSpan)) {
String response = "Hello from Quarkus REST";
innerSpan.setTag("response-chars-count", response.length());
return response;
} catch (Exception e) {
innerSpan.setTag("error", true); 4
innerSpan.setTag("error.message", e.getMessage());
throw e;
} finally {
innerSpan.finish();
}
}
}
1 | 传统的 OpenTracing 跟踪器必须替换为新的 OpenTelemetry 跟踪器。 |
2 | @Traced 注释以 @WithSpan 注释替换,但请注意,此新注释将始终创建一个新的跨度。您不应该在 JAX-RS 终结点上使用它,因为它们已经配置了检测。 |
3 | Tag 类已被 Attribute 类替换。应尽可能使用 Tags ,以使属性名称与规范保持一致,从而替换 SemanticAttributes 。 |
4 | OpenTelemetry 中有处理错误的新方法。 |
OpenTelemetry 跟踪器与 OpenTracing API 不兼容。主要更改总结在以下表格中:
Note | MicroProfile OpenTracing v3 | OpenTelemetry |
---|---|---|
1 |
|
|
2 |
|
|
3 |
Tag |
Attribute |
3 |
Tags |
SemanticAttributes |
4 |
|
|
- |
SpanContext 中 Span 携带的 Baggage |
Baggage 是一种与 OTel Context 并行传播的独立信号,它不属于它的一部分。 |
在更新依赖项后,由于 quickstart 项目现在使用 OpenTelemetry 运行,上述类将导致构建中断。此类错误将显示在日志中:
2023-10-27 16:11:12,454 ERROR [io.qua.dep.dev.IsolatedDevModeMain] (main) Failed to start quarkus: java.lang.RuntimeException: io.quarkus.builder.BuildException: Build failure: Build failed due to errors
[error]: Build step io.quarkus.arc.deployment.ArcProcessor#validate threw an exception: jakarta.enterprise.inject.spi.DeploymentException: jakarta.enterprise.inject.UnsatisfiedResolutionException: Unsatisfied dependency for type io.opentracing.Tracer and qualifiers [@Default]
...
必须使用新的 OpenTelemetry API。这是迁移代码的一种方法:
GreetingsResource with OpenTelemetry manual instrumentation
package org.acme;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import static io.opentelemetry.api.trace.StatusCode.*;
@Path("/hello")
@ApplicationScoped
public class GreetingResource {
@Inject
io.opentelemetry.api.trace.Tracer otelTracer;
@GET
@Produces(MediaType.TEXT_PLAIN)
@WithSpan(value = "Not needed, will create a new span, child of the automatic JAX-RS span")
public String hello() {
// Add a tag to the active span
Span incomingSpan = Span.current();
incomingSpan.setAttribute(SemanticAttributes.CODE_NAMESPACE, "GreetingResource");
// Create a manual inner span
Span innerSpan = otelTracer.spanBuilder("Count response chars").startSpan();
try (Scope scope = innerSpan.makeCurrent()) {
final String response = "Hello from Quarkus REST";
innerSpan.setAttribute("response-chars-count", response.length());
return response;
} catch (Exception e) {
innerSpan.setStatus(ERROR);
innerSpan.recordException(e);
throw e;
} finally {
innerSpan.end();
}
}
}
删除所有 OpenTracing 依赖项后,代码将生成。不要忘记仔细检查跟踪是否包含正确的跨度。你可以在 Jaeger UI 中看到它们:[role="bare"][role="bare"]http://localhost:16686/。
The OpenTracing shim
在本节中,我们展示了一个 OpenTelemetry 库,它可以通过提供访问旧版本的 OpenTracing API 来平滑过渡。这可以帮助迁移具有许多手动检测点的应用程序。
要继续本节,代码项目必须是 Starting point。如果你有与上一节相关的更改,请在继续之前撤消更改或根据 Starting point 的说明重新生成项目。
The dependencies
删除 quarkus-smallrye-opentracing
扩展,然后将 quarkus-opentelemetry
扩展和 opentelemetry-opentracing-shim
库添加到构建文件中:
从项目中删除传统扩展:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-opentracing</artifactId>
</dependency>
implementation("io.quarkus:quarkus-smallrye-opentracing")
添加新的扩展:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-opentracing-shim</artifactId>
<!-- No need to declare the version -->
</dependency>
implementation("io.quarkus:quarkus-opentelemetry")
implementation("io.quarkus:opentelemetry-opentracing-shim")
The code changes
记住 The GreetingsResource with OpenTracing manual instrumentation 中 GreetingResource
类的初始版本:
package org.acme;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.tag.Tags;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.opentracing.Traced;
@Path("/hello")
@ApplicationScoped
public class GreetingResource {
@Inject
io.opentracing.Tracer legacyTracer; 1
@GET
@Produces(MediaType.TEXT_PLAIN)
@Traced(operationName = "Not needed, will change the current span name") 2
public String hello() {
// Add a tag to the active span
legacyTracer.activeSpan().setTag(Tags.COMPONENT, "GreetingResource"); 3
// Create a manual inner span
Span innerSpan = legacyTracer.buildSpan("Count response chars").start();
try (Scope dbScope = legacyTracer.scopeManager().activate(innerSpan)) {
String response = "Hello from Quarkus REST";
innerSpan.setTag("response-chars-count", response.length());
return response;
} catch (Exception e) {
innerSpan.setTag("error", true);
innerSpan.setTag("error.message", e.getMessage());
throw e;
} finally {
innerSpan.finish();
}
}
}
1 | 必须删除 Tracer 注释,而我们改为注入 OpenTelemetry SDK。我们需要它在 <3> 中。 |
2 | @Traced 注释已被 @WithSpan 注释取代,但要注意,此新注释将始终创建一个新的 Span。你不应该在 JAX-RS 端点上使用它,我们只把它放在这里是为了演示目的。 |
3 | 我们必须获取 legacyTracer 的一个实例。Shim 包含一个用于此目的的实用程序类:Tracer legacyTracer = OpenTracingShim.createTracerShim(openTelemetry); |
更改后,代码将编译,你将同时使用 OpenTracing 和 OpenTelemetry API:
package org.acme;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.opentracingshim.OpenTracingShim;
import io.opentracing.Scope;
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.tag.Tags;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
@ApplicationScoped
public class GreetingResource {
@Inject
io.opentelemetry.api.OpenTelemetry openTelemetry;
@GET
@Produces(MediaType.TEXT_PLAIN)
@WithSpan(value = "Not needed, will create a new span, child of the automatic JAX-RS span")
public String hello() {
// Add a tag to the active span
Tracer legacyTracer = OpenTracingShim.createTracerShim(openTelemetry);
legacyTracer.activeSpan().setTag(Tags.COMPONENT, "GreetingResource");
// Create a manual inner span
Span innerSpan = legacyTracer.buildSpan("Count response chars").start();
try (Scope dbScope = legacyTracer.scopeManager().activate(innerSpan)) {
String response = "Hello from Quarkus REST";
innerSpan.setTag("response-chars-count", response.length());
return response;
} catch (Exception e) {
innerSpan.setTag("error", true);
innerSpan.setTag("error.message", e.getMessage());
throw e;
} finally {
innerSpan.finish();
}
}
}
建议不要将 shim 用于永久解决方案,而只将其用作平滑迁移的工具。