diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java index 4813bbcf61c1a..6eddefe11db8e 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/config/OTelFallbackConfigSourceInterceptor.java @@ -42,7 +42,7 @@ public OTelFallbackConfigSourceInterceptor() { @Override public ConfigValue getValue(final ConfigSourceInterceptorContext context, final String name) { ConfigValue value = super.getValue(context, name); - if (name.equals("quarkus.otel.traces.sampler")) { + if (value != null && name.equals("quarkus.otel.traces.sampler")) { return value.withValue(LEGACY_SAMPLER_NAME_CONVERTER.convert(value.getValue())); } return value; diff --git a/integration-tests/opentelemetry-quickstart/pom.xml b/integration-tests/opentelemetry-quickstart/pom.xml new file mode 100644 index 0000000000000..b824643f4ecd0 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/pom.xml @@ -0,0 +1,188 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + + + quarkus-integration-test-opentelemetry-quickstart + Quarkus - Integration Tests - OpenTelemetry quickstart + + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-opentelemetry + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-resteasy-reactive + + + + + + io.opentelemetry + opentelemetry-sdk-testing + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.awaitility + awaitility + test + + + + + io.quarkus + quarkus-arc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-resteasy-reactive-jackson-deployment + ${project.version} + pom + test + + + * + * + + + + + + io.quarkus + quarkus-opentelemetry-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + false + + + + + + + + + native-image + + + native + + + + + native + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${native.surefire.skip} + + false + + + + + + maven-failsafe-plugin + + + + integration-test + verify + + + + false + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + + diff --git a/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/ExporterResource.java b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/ExporterResource.java new file mode 100644 index 0000000000000..a611fda7c2c7b --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/ExporterResource.java @@ -0,0 +1,46 @@ +package io.quarkus.it.opentelemetry; + +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.data.SpanData; + +@Path("") +public class ExporterResource { + @Inject + InMemorySpanExporter inMemorySpanExporter; + + @GET + @Path("/reset") + public Response reset() { + inMemorySpanExporter.reset(); + return Response.ok().build(); + } + + @GET + @Path("/export") + public List export() { + return inMemorySpanExporter.getFinishedSpanItems() + .stream() + .filter(sd -> !sd.getName().contains("export") && !sd.getName().contains("reset")) + .collect(Collectors.toList()); + } + + @ApplicationScoped + static class InMemorySpanExporterProducer { + @Produces + @Singleton + InMemorySpanExporter inMemorySpanExporter() { + return InMemorySpanExporter.create(); + } + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/GreetingResource.java b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/GreetingResource.java new file mode 100644 index 0000000000000..3874e37f85302 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/GreetingResource.java @@ -0,0 +1,16 @@ +package io.quarkus.it.opentelemetry; + +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 RESTEasy Reactive"; + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/output/SpanDataModuleSerializer.java b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/output/SpanDataModuleSerializer.java new file mode 100644 index 0000000000000..83564dfe092bb --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/output/SpanDataModuleSerializer.java @@ -0,0 +1,19 @@ +package io.quarkus.it.opentelemetry.output; + +import jakarta.inject.Singleton; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import io.opentelemetry.sdk.trace.data.SpanData; +import io.quarkus.jackson.ObjectMapperCustomizer; + +@Singleton +public class SpanDataModuleSerializer implements ObjectMapperCustomizer { + @Override + public void customize(ObjectMapper objectMapper) { + SimpleModule simpleModule = new SimpleModule(); + simpleModule.addSerializer(SpanData.class, new SpanDataSerializer()); + objectMapper.registerModule(simpleModule); + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/output/SpanDataSerializer.java b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/output/SpanDataSerializer.java new file mode 100644 index 0000000000000..c546ef284625e --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/main/java/io/quarkus/it/opentelemetry/output/SpanDataSerializer.java @@ -0,0 +1,55 @@ +package io.quarkus.it.opentelemetry.output; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; + +import io.opentelemetry.sdk.trace.data.SpanData; + +public class SpanDataSerializer extends StdSerializer { + public SpanDataSerializer() { + this(null); + } + + public SpanDataSerializer(Class type) { + super(type); + } + + @Override + public void serialize(SpanData spanData, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) + throws IOException { + jsonGenerator.writeStartObject(); + + jsonGenerator.writeStringField("spanId", spanData.getSpanId()); + jsonGenerator.writeStringField("traceId", spanData.getTraceId()); + jsonGenerator.writeStringField("name", spanData.getName()); + jsonGenerator.writeStringField("kind", spanData.getKind().name()); + jsonGenerator.writeBooleanField("ended", spanData.hasEnded()); + + jsonGenerator.writeStringField("parentSpanId", spanData.getParentSpanContext().getSpanId()); + jsonGenerator.writeStringField("parent_spanId", spanData.getParentSpanContext().getSpanId()); + jsonGenerator.writeStringField("parent_traceId", spanData.getParentSpanContext().getTraceId()); + jsonGenerator.writeBooleanField("parent_remote", spanData.getParentSpanContext().isRemote()); + jsonGenerator.writeBooleanField("parent_valid", spanData.getParentSpanContext().isValid()); + + spanData.getAttributes().forEach((k, v) -> { + try { + jsonGenerator.writeStringField("attr_" + k.getKey(), v.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + spanData.getResource().getAttributes().forEach((k, v) -> { + try { + jsonGenerator.writeStringField("resource_" + k.getKey(), v.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + jsonGenerator.writeEndObject(); + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/main/resources/META-INF/resources/test.html b/integration-tests/opentelemetry-quickstart/src/main/resources/META-INF/resources/test.html new file mode 100644 index 0000000000000..d3e7968fdf060 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/main/resources/META-INF/resources/test.html @@ -0,0 +1 @@ +Test diff --git a/integration-tests/opentelemetry-quickstart/src/main/resources/application.properties b/integration-tests/opentelemetry-quickstart/src/main/resources/application.properties new file mode 100644 index 0000000000000..5a8972253198d --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# speed up build +quarkus.otel.bsp.schedule.delay=0 +quarkus.otel.bsp.export.timeout=5s diff --git a/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/BaseTest.java b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/BaseTest.java new file mode 100644 index 0000000000000..549d7a9b21304 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/BaseTest.java @@ -0,0 +1,20 @@ +package io.quarkus.it.opentelemetry; + +import static io.restassured.RestAssured.get; + +import java.util.List; +import java.util.Map; + +import io.restassured.common.mapper.TypeRef; + +public class BaseTest { + + protected List> getSpans() { + return get("/export").body().as(new TypeRef<>() { + }); + } + + protected void buildGlobalTelemetryInstance() { + // Do nothing in JVM mode + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryDisabledIT.java b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryDisabledIT.java new file mode 100644 index 0000000000000..205816851ed98 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryDisabledIT.java @@ -0,0 +1,19 @@ +package io.quarkus.it.opentelemetry; + +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class OpenTelemetryDisabledIT extends OpenTelemetryDisabledTest { + @Override + protected void buildGlobalTelemetryInstance() { + // When running native tests the test class is outside the Quarkus application, + // so we need to set the propagator on the GlobalOpenTelemetry instance + OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); + builder.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())); + builder.buildAndRegisterGlobal(); + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryDisabledTest.java b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryDisabledTest.java new file mode 100644 index 0000000000000..413fd1f41fd60 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryDisabledTest.java @@ -0,0 +1,39 @@ +package io.quarkus.it.opentelemetry; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; + +@QuarkusTest +@TestProfile(OpenTelemetryDisabledTest.MyProfile.class) +public class OpenTelemetryDisabledTest extends BaseTest { + + @Test + void buildTimeDisabled() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(is("Hello from RESTEasy Reactive")); + // Service will start nevertheless. + await().atMost(200, MILLISECONDS).until(() -> getSpans().size() == 0); + } + + public static class MyProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Map.of("quarkus.otel.enabled", "false"); + } + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryIT.java b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryIT.java new file mode 100644 index 0000000000000..a7e516388cfd1 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryIT.java @@ -0,0 +1,19 @@ +package io.quarkus.it.opentelemetry; + +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.OpenTelemetrySdkBuilder; +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class OpenTelemetryIT extends OpenTelemetryTest { + @Override + protected void buildGlobalTelemetryInstance() { + // When running native tests the test class is outside the Quarkus application, + // so we need to set the propagator on the GlobalOpenTelemetry instance + OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder(); + builder.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())); + builder.buildAndRegisterGlobal(); + } +} diff --git a/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java new file mode 100644 index 0000000000000..51a58984e53c3 --- /dev/null +++ b/integration-tests/opentelemetry-quickstart/src/test/java/io/quarkus/it/opentelemetry/OpenTelemetryTest.java @@ -0,0 +1,24 @@ +package io.quarkus.it.opentelemetry; + +import static io.restassured.RestAssured.get; +import static io.restassured.RestAssured.given; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class OpenTelemetryTest extends BaseTest { + @Test + void buildTimeEnabled() { + given() + .when().get("/hello") + .then() + .statusCode(200) + .body(is("Hello from RESTEasy Reactive")); + await().atMost(5, SECONDS).until(() -> getSpans().size() == 1); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index ad79ab03ce3c6..c03b3703e342b 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -374,6 +374,7 @@ micrometer-mp-metrics micrometer-prometheus opentelemetry + opentelemetry-quickstart opentelemetry-spi opentelemetry-jdbc-instrumentation opentelemetry-quartz