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 加载问题已解决,端到端导出仍需实际验证。
备注:内容仅供参考。
