Skip to content

Commit

Permalink
Merge pull request #34647 from geoand/#32238
Browse files Browse the repository at this point in the history
Replace OkHttp tracing backend with Vert.x
  • Loading branch information
geoand authored Jul 18, 2023
2 parents 4dee32e + bd6cfd6 commit abbbe5f
Show file tree
Hide file tree
Showing 22 changed files with 1,175 additions and 43 deletions.
9 changes: 9 additions & 0 deletions bom/test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

<rxjava1.version>1.3.8</rxjava1.version>
<strimzi-test-container.version>0.100.0</strimzi-test-container.version>

<opentelemetry-proto.version>0.20.0-alpha</opentelemetry-proto.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -66,6 +68,13 @@
<artifactId>jaxb-api</artifactId>
<version>${jaxb-api.version}</version>
</dependency>

<dependency>
<groupId>io.opentelemetry.proto</groupId>
<artifactId>opentelemetry-proto</artifactId>
<version>${opentelemetry-proto.version}</version>
</dependency>

</dependencies>
</dependencyManagement>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.opentelemetry.deployment;

import static io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem.SPI_ROOT;
import static io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder.OPEN_TELEMETRY_DRIVER;
import static java.util.stream.Collectors.toList;

Expand All @@ -17,6 +18,7 @@

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider;
import io.opentelemetry.instrumentation.annotations.SpanAttribute;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
Expand All @@ -40,10 +42,14 @@
import io.quarkus.deployment.annotations.Produce;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.RemovedResourceBuildItem;
import io.quarkus.deployment.builditem.ShutdownContextBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.deployment.util.ServiceUtil;
import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.opentelemetry.runtime.OpenTelemetryProducer;
import io.quarkus.opentelemetry.runtime.OpenTelemetryRecorder;
import io.quarkus.opentelemetry.runtime.QuarkusContextStorage;
Expand Down Expand Up @@ -73,11 +79,30 @@ AdditionalBeanBuildItem ensureProducerIsRetained() {
}

@BuildStep
void registerNativeImageResources(BuildProducer<ServiceProviderBuildItem> services) throws IOException {
services.produce(ServiceProviderBuildItem.allProvidersFromClassPath(
ConfigurableSpanExporterProvider.class.getName()));
void registerNativeImageResources(BuildProducer<ServiceProviderBuildItem> services,
BuildProducer<RemovedResourceBuildItem> removedResources,
BuildProducer<RuntimeReinitializedClassBuildItem> runtimeReinitialized) throws IOException {

List<String> spanExporterProviders = ServiceUtil.classNamesNamedIn(
Thread.currentThread().getContextClassLoader(),
SPI_ROOT + ConfigurableSpanExporterProvider.class.getName())
.stream()
.filter(p -> !OtlpSpanExporterProvider.class.getName().equals(p)).collect(toList()); // filter out OtlpSpanExporterProvider since it depends on OkHttp
if (!spanExporterProviders.isEmpty()) {
services.produce(
new ServiceProviderBuildItem(ConfigurableSpanExporterProvider.class.getName(), spanExporterProviders));
}
// remove the service file that contains OtlpSpanExporterProvider
removedResources.produce(new RemovedResourceBuildItem(
ArtifactKey.fromString("io.opentelemetry:opentelemetry-exporter-otlp"),
Set.of("META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider")));

runtimeReinitialized.produce(
new RuntimeReinitializedClassBuildItem("io.opentelemetry.sdk.autoconfigure.TracerProviderConfiguration"));

services.produce(ServiceProviderBuildItem.allProvidersFromClassPath(
ConfigurableSamplerProvider.class.getName()));

// The following are added but not officially supported, yet.
services.produce(ServiceProviderBuildItem.allProvidersFromClassPath(
AutoConfigurationCustomizerProvider.class.getName()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig;
import io.quarkus.opentelemetry.runtime.exporter.otlp.OtlpExporterProvider;
import io.quarkus.opentelemetry.runtime.exporter.otlp.OtlpRecorder;
import io.quarkus.vertx.deployment.VertxBuildItem;

@BuildSteps(onlyIf = OtlpExporterProcessor.OtlpExporterEnabled.class)
public class OtlpExporterProcessor {
Expand Down Expand Up @@ -47,9 +48,11 @@ void installBatchSpanProcessorForOtlp(
LaunchModeBuildItem launchModeBuildItem,
OTelRuntimeConfig otelRuntimeConfig,
OtlpExporterRuntimeConfig exporterRuntimeConfig,
VertxBuildItem vertxBuildItem,
BeanContainerBuildItem beanContainerBuildItem) {
recorder.installBatchSpanProcessorForOtlp(otelRuntimeConfig,
exporterRuntimeConfig,
vertxBuildItem.getVertx(),
launchModeBuildItem.getLaunchMode());
}
}
34 changes: 34 additions & 0 deletions extensions/opentelemetry/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
Expand All @@ -125,6 +129,36 @@
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-grpc-client</artifactId>
<exclusions>
<exclusion>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-annotations</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</exclusion>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
<exclusion>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.android</groupId>
<artifactId>annotations</artifactId>
</exclusion>
</exclusions>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,6 @@ public interface OtlpExporterTracesConfig {
@WithDefault(DEFAULT_GRPC_BASE_URI)
Optional<String> legacyEndpoint();

// /**
// * Sets the certificate chain to use for verifying servers when TLS is enabled. The {@code byte[]}
// * should contain an X.509 certificate collection in PEM format. If not set, TLS connections will
// * use the system default trusted certificates.
// */
// @ConfigItem()
// public Optional<byte[]> certificate;

// /**
// * Sets ths client key and the certificate chain to use for verifying client when TLS is enabled.
// * The key must be PKCS8, and both must be in PEM format.
// */
// @ConfigItem()
// public Optional<OtlpExporterRuntimeConfig.ClientTlsConfig> client;

/**
* Key-value pairs to be used as headers associated with gRPC requests.
* The format is similar to the {@code OTEL_EXPORTER_OTLP_HEADERS} environment variable,
Expand Down Expand Up @@ -73,6 +58,37 @@ public interface OtlpExporterTracesConfig {
@WithDefault(Protocol.HTTP_PROTOBUF)
Optional<String> protocol();

/**
* Key/cert configuration in the PEM format.
*/
@WithName("key-cert")
KeyCert keyCert();

/**
* Trust configuration in the PEM format.
*/
@WithName("trust-cert")
TrustCert trustCert();

interface KeyCert {
/**
* Comma-separated list of the path to the key files (Pem format).
*/
Optional<List<String>> keys();

/**
* Comma-separated list of the path to the certificate files (Pem format).
*/
Optional<List<String>> certs();
}

interface TrustCert {
/**
* Comma-separated list of the trust certificate files (Pem format).
*/
Optional<List<String>> certs();
}

public static class Protocol {
public static final String GRPC = "grpc";
public static final String HTTP_PROTOBUF = "http/protobuf";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,33 @@
import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig.DEFAULT_GRPC_BASE_URI;
import static io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig.Protocol.HTTP_PROTOBUF;

import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;

import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.spi.CDI;

import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.CompressionType;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.core.net.PemTrustOptions;

@Recorder
public class OtlpRecorder {
Expand All @@ -39,22 +52,23 @@ private static boolean excludeDefaultEndpoint(String endpoint) {
public void installBatchSpanProcessorForOtlp(
OTelRuntimeConfig otelRuntimeConfig,
OtlpExporterRuntimeConfig exporterRuntimeConfig,
RuntimeValue<Vertx> vertx,
LaunchMode launchMode) {

if (otelRuntimeConfig.sdkDisabled()) {
return;
}
String endpoint = resolveEndpoint(exporterRuntimeConfig).trim();
String grpcBaseUri = resolveEndpoint(exporterRuntimeConfig).trim();

// Only create the OtlpGrpcSpanExporter if an endpoint was set in runtime config
if (endpoint.length() > 0) {
if (grpcBaseUri.length() > 0) {
try {
// Load span exporter if provided by user
SpanExporter spanExporter = CDI.current()
.select(SpanExporter.class, Any.Literal.INSTANCE).stream().findFirst().orElse(null);
// CDI exporter was already added to a processor by OTEL
if (spanExporter == null) {
spanExporter = createOtlpGrpcSpanExporter(exporterRuntimeConfig, endpoint);
spanExporter = createOtlpGrpcSpanExporter(exporterRuntimeConfig, grpcBaseUri, vertx.getValue());

// Create BatchSpanProcessor for OTLP and install into LateBoundBatchSpanProcessor
LateBoundBatchSpanProcessor delayedProcessor = CDI.current()
Expand All @@ -76,17 +90,26 @@ public void installBatchSpanProcessorForOtlp(
}
}

private OtlpGrpcSpanExporter createOtlpGrpcSpanExporter(OtlpExporterRuntimeConfig exporterRuntimeConfig, String endpoint) {
OtlpGrpcSpanExporterBuilder exporterBuilder = OtlpGrpcSpanExporter.builder()
.setEndpoint(endpoint)
.setTimeout(exporterRuntimeConfig.traces().timeout());
private SpanExporter createOtlpGrpcSpanExporter(OtlpExporterRuntimeConfig exporterRuntimeConfig, String endpoint,
Vertx vertx) {

// FIXME TLS Support. Was not available before but will be available soon.
// exporterRuntimeConfig.traces.certificate.ifPresent(exporterBuilder::setTrustedCertificates);
// exporterRuntimeConfig.client.ifPresent(exporterBuilder::setClientTls);
OtlpExporterTracesConfig tracesConfig = exporterRuntimeConfig.traces();
if (tracesConfig.protocol().isPresent()) {
if (!tracesConfig.protocol().get().equals(HTTP_PROTOBUF)) {
throw new IllegalStateException("Only the GRPC Exporter is currently supported. " +
"Please check `quarkus.otel.exporter.otlp.traces.protocol` property");
}
}

if (exporterRuntimeConfig.traces().headers().isPresent()) {
List<String> headers = exporterRuntimeConfig.traces().headers().get();
boolean compressionEnabled = false;
if (tracesConfig.compression().isPresent()) {
compressionEnabled = (tracesConfig.compression().get() == CompressionType.GZIP);
}

Map<String, String> headersMap = new HashMap<>();
OtlpUserAgent.addUserAgentHeader(headersMap::put);
if (tracesConfig.headers().isPresent()) {
List<String> headers = tracesConfig.headers().get();
if (!headers.isEmpty()) {
for (String header : headers) {
if (header.isEmpty()) {
Expand All @@ -95,22 +118,65 @@ private OtlpGrpcSpanExporter createOtlpGrpcSpanExporter(OtlpExporterRuntimeConfi
String[] parts = header.split("=", 2);
String key = parts[0].trim();
String value = parts[1].trim();
exporterBuilder.addHeader(key, value);
headersMap.put(key, value);
}
}
}

if (exporterRuntimeConfig.traces().compression().isPresent()) {
exporterBuilder.setCompression(exporterRuntimeConfig.traces().compression().get().getValue());
}
URI grpcBaseUri = ExporterBuilderUtil.validateEndpoint(endpoint);
return new VertxGrpcExporter(
"otlp", // use the same as OTel does
"span", // use the same as OTel does
MeterProvider::noop,
grpcBaseUri,
compressionEnabled,
tracesConfig.timeout(),
headersMap,
new Consumer<>() {
@Override
public void accept(HttpClientOptions options) {
configureTLS(options);
}

if (exporterRuntimeConfig.traces().protocol().isPresent()) {
if (!exporterRuntimeConfig.traces().protocol().get().equals(HTTP_PROTOBUF)) {
throw new IllegalStateException("Only the GRPC Exporter is currently supported. " +
"Please check `quarkus.otel.exporter.otlp.traces.protocol` property");
}
}
private void configureTLS(HttpClientOptions options) {
// TODO: this can reuse existing stuff when https://github.com/quarkusio/quarkus/pull/33228 is in
options.setKeyCertOptions(toPemKeyCertOptions(tracesConfig));
options.setPemTrustOptions(toPemTrustOptions(tracesConfig));

if (VertxGrpcExporter.isHttps(grpcBaseUri)) {
options.setSsl(true);
options.setUseAlpn(true);
}
}

private KeyCertOptions toPemKeyCertOptions(OtlpExporterTracesConfig configuration) {
PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions();
OtlpExporterTracesConfig.KeyCert keyCert = configuration.keyCert();
if (keyCert.certs().isPresent()) {
for (String cert : keyCert.certs().get()) {
pemKeyCertOptions.addCertPath(cert);
}
}
if (keyCert.keys().isPresent()) {
for (String cert : keyCert.keys().get()) {
pemKeyCertOptions.addKeyPath(cert);
}
}
return pemKeyCertOptions;
}

private PemTrustOptions toPemTrustOptions(OtlpExporterTracesConfig configuration) {
PemTrustOptions pemTrustOptions = new PemTrustOptions();
OtlpExporterTracesConfig.TrustCert trustCert = configuration.trustCert();
if (trustCert.certs().isPresent()) {
for (String cert : trustCert.certs().get()) {
pemTrustOptions.addCertPath(cert);
}
}
return pemTrustOptions;
}
},
vertx);

return exporterBuilder.build();
}
}
Loading

0 comments on commit abbbe5f

Please sign in to comment.