PAYATHON 2026

Quarkus 打包部署后为何找不到 OtlpMetricExporterProvider?

排障赵师傅

结论

先删除三项 quarkus.otel.*.exporter=otlp,恢复默认值 cdi,也可以显式改为 cdi,然后重新打包部署。保留 quarkus.otel.metrics.enabled=true,自定义 metrics 仍可通过 OTLP 发送到 Collector,无须设置 quarkus.otel.metrics.exporter=none。

这两个配置控制的内容不同:

  • quarkus.otel.metrics.exporter=cdi:使用通过 CDI 接入的 exporter。
  • quarkus.otel.exporter.otlp.protocol=grpc:指定 OTLP 的传输协议。

Quarkus 内置了基于 Vert.x 的 OTLP exporter,默认通过 CDI 装配,因此使用 cdi 也能通过 OTLP 导出数据。Quarkus exporter 说明

为什么开发模式正常,打包后却找不到 Provider?

异常中的 Provider:

io.opentelemetry.exporter.otlp.internal.OtlpMetricExporterProvider

属于上游 OpenTelemetry Java 的 OTLP exporter。显式配置:

quarkus.otel.metrics.exporter=otlp

会让自动配置尝试选择名为 otlp 的 exporter。Quarkus 默认则通过 CDI 接入自己的 exporter。

ServiceConfigurationError: Provider ... not found 表示服务发现过程读到了 Provider 的注册信息,但当前 ClassLoader 无法加载对应的实现类。此时错误发生在 exporter 加载阶段,还没有开始向 Collector 发送数据。

开发模式和打包运行的类加载环境不同,依赖版本、服务注册文件或打包内容的问题,可能只在部署产物中暴露。仅凭当前异常,无法确定差异来自哪个 JAR 或哪一步打包,也不能直接认定是 native image 的反射配置问题。

当前配置应先修正三项 exporter=otlp。Quarkus 3.20 文档已说明 exporter 默认通过 CDI 装配;3.27 文档进一步明确,上游 OpenTelemetry OTLP exporter 不属于其支持的接入方式。3.20 配置说明、3.27 exporter 说明

如何修改配置?

相关配置可以改为:

quarkus.http.port=8080
quarkus.application.name=dts-mock-svc

quarkus.otel.enabled=true

quarkus.otel.traces.enabled=true
quarkus.otel.metrics.enabled=true
quarkus.otel.logs.enabled=true

# 这三项也可以直接删除,使用默认值
quarkus.otel.traces.exporter=cdi
quarkus.otel.metrics.exporter=cdi
quarkus.otel.logs.exporter=cdi

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

metrics.enabled 决定是否启用 OpenTelemetry metrics,metrics.exporter 决定输出方式。将 exporter 设为 none 会关闭该通道的指标输出,因此需要继续导出自定义指标时,不应使用这个值。启用开关和 exporter 选择涉及构建期配置,修改后需要重新构建。Quarkus 配置说明

部署时也可以使用 Quarkus 对应的环境变量:

export QUARKUS_OTEL_SERVICE_NAME=dts-mock-svc
export QUARKUS_OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export QUARKUS_OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317

otel-collector 是示例主机名,需要替换为应用实际能够访问的 Collector 地址。变量名称可在 Quarkus 配置参考中核对。

如果 Collector 位于另一个容器或 Pod,通常不能继续使用 localhost。它指向应用自身的网络环境,只有 Collector 与应用共享网络命名空间时才适用。地址错误会导致连接失败,但不会造成当前的 Provider 加载错误。

依赖是否需要调整?

使用 Quarkus 内置的 OTLP 导出时,保留扩展依赖即可,版本由项目使用的 Quarkus Platform BOM 管理:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-opentelemetry</artifactId>
</dependency>

如果 okhttp-jvm 只是为这次 OpenTelemetry 接入补加的依赖,可以在切回 CDI exporter 后移除,再重新验证。Quarkus 内置的 exporter 使用 Vert.x,官方说明了移除 OkHttp 依赖的背景。Quarkus 官方说明

如果其他业务库也使用 OkHttp,应按这些库的要求保留。不要为了消除当前异常,继续手工添加不同版本的 OpenTelemetry exporter、SDK 或 sender 依赖。

可以先检查依赖来源:

./mvnw dependency:tree -Dincludes='io.opentelemetry:*,com.squareup.okhttp3:*'

重点查看是否有其他依赖引入了上游 exporter,以及是否手动覆盖了 BOM 管理的 OpenTelemetry 版本。

自定义 metrics 如何保留?

如果自定义指标使用 OpenTelemetry API,可以继续使用 Quarkus 注入的 OpenTelemetry 实例。例如:

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.metrics.LongCounter;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

@ApplicationScoped
public class BusinessMetrics {

    private final LongCounter completedJobs;

    @Inject
    public BusinessMetrics(OpenTelemetry openTelemetry) {
        this.completedJobs = openTelemetry
                .getMeter("dts-mock-svc")
                .counterBuilder("jobs.completed")
                .setDescription("Number of completed jobs")
                .setUnit("{job}")
                .build();
    }

    public void recordCompletedJob() {
        completedJobs.add(1);
    }
}

业务代码调用 recordCompletedJob() 后,Quarkus 管理的 metrics SDK 和 exporter 会处理这些指标。更改 exporter 配置,无须重写计数逻辑。Quarkus 的 OpenTelemetry Metrics 指南提供了手动指标的接入示例。

如果现有代码使用 Micrometer MeterRegistry,还需要检查 Micrometer 到 OpenTelemetry 的接入配置。仅启用 quarkus.otel.metrics.enabled,不能说明 Micrometer 指标已经进入 OTLP 导出链路。

重新打包后怎样验证?

先构建,再直接运行 JVM 打包产物:

./mvnw clean package
java -jar target/quarkus-app/quarkus-run.jar

该启动命令适用于默认的 fast-jar。部署时必须复制整个 target/quarkus-app/ 目录,因为运行所需的依赖也在其中,只复制 quarkus-run.jar 无法满足运行要求。Quarkus fast-jar 部署说明

如果修正配置后仍报同一个异常,继续检查:

  • %prod 配置、构建参数或环境变量是否仍将 exporter 覆盖为 otlp。
  • 应用或第三方库是否另外调用了 AutoConfiguredOpenTelemetrySdk,创建了独立的 SDK。
  • 是否经过额外的 shading、依赖裁剪或二次打包,造成 META-INF/services 注册信息与实现类不一致。
  • 实际构建目标是否为 native image;确认后,再排查 native 特有的服务加载问题。

应用恢复启动后,触发一次自定义指标更新,等待指标导出周期,再检查 Collector 的 metrics pipeline 是否收到数据。启动成功只能说明 Provider 加载问题已解决,端到端导出仍需实际验证。

备注:内容仅供参考。