diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagExemplarTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagExemplarTest.java new file mode 100644 index 0000000000000..2a4c18d5b6b18 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/UriTagExemplarTest.java @@ -0,0 +1,154 @@ +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.quarkus.micrometer.test.HelloResource; +import io.quarkus.micrometer.test.PingPongResource; +import io.quarkus.micrometer.test.ServletEndpoint; +import io.quarkus.micrometer.test.Util; +import io.quarkus.micrometer.test.VertxWebEndpoint; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class UriTagExemplarTest { + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.binder.http-client.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.enabled", "true") + .overrideConfigKey("quarkus.micrometer.binder.http-server.match-patterns", "/one=/two") + .overrideConfigKey("quarkus.micrometer.binder.http-server.ignore-patterns", "/two") + .overrideConfigKey("quarkus.micrometer.binder.vertx.enabled", "true") + .overrideConfigKey("pingpong/mp-rest/url", "${test.url}") + .overrideConfigKey("quarkus.redis.devservices.enabled", "false") + .withApplicationRoot((jar) -> jar + .addClasses(Util.class, + PingPongResource.class, + PingPongResource.PingPongRestClient.class, + ServletEndpoint.class, + VertxWebEndpoint.class, + HelloResource.class)); + + static SimpleMeterRegistry registry = new SimpleMeterRegistry(); + + @BeforeAll + static void setRegistry() { + registry = new SimpleMeterRegistry(); + Metrics.addRegistry(registry); + } + + @AfterAll() + static void removeRegistry() { + Metrics.removeRegistry(registry); + } + + @Test + public void testRequestUris() throws Exception { + RestAssured.basePath = "/"; + + // Server paths (templated) + when().get("/one").then().statusCode(200); + when().get("/two").then().statusCode(200); + when().get("/vertx/item/123").then().statusCode(200); + when().get("/vertx/item/1/123").then().statusCode(200); + when().get("/servlet/12345").then().statusCode(200); + + // Server GET vs. HEAD methods -- templated + when().get("/hello/one").then().statusCode(200); + when().get("/hello/two").then().statusCode(200); + when().head("/hello/three").then().statusCode(200); + when().head("/hello/four").then().statusCode(200); + when().get("/vertx/echo/thing1").then().statusCode(200); + when().get("/vertx/echo/thing2").then().statusCode(200); + when().head("/vertx/echo/thing3").then().statusCode(200); + when().head("/vertx/echo/thing4").then().statusCode(200); + + // Server -> Rest client -> Server (templated) + when().get("/ping/one").then().statusCode(200); + when().get("/ping/two").then().statusCode(200); + when().get("/ping/three").then().statusCode(200); + when().get("/ping/400").then().statusCode(200); + when().get("/ping/500").then().statusCode(200); + when().get("/async-ping/one").then().statusCode(200); + when().get("/async-ping/two").then().statusCode(200); + when().get("/async-ping/three").then().statusCode(200); + + // Try to let metrics gathering finish. + // Looking for server request timers: /vertx/item/{id}, /vertx/item/{id}/{sub}, /servlet/, + // /ping/{message}, /async-ping/{message}, /pong/{message}, and 2 of both /hello/{message} and /vertx/echo/{msg} + // Looking for client request: /pong/{message} + Util.waitForMeters(registry.find("http.server.requests").timers(), 10); + Util.waitForMeters(registry.find("http.client.requests").timers(), 1); + + System.out.println("Server paths\n" + Util.listMeters(registry, "http.server.requests")); + System.out.println("Client paths\n" + Util.listMeters(registry, "http.client.requests")); + + // /one should map to /two, which is ignored. Neither should exist w/ timers + Assertions.assertEquals(0, registry.find("http.server.requests").tag("uri", "/one").timers().size(), + Util.foundServerRequests(registry, "/one is mapped to /two, which should be ignored.")); + Assertions.assertEquals(0, registry.find("http.server.requests").tag("uri", "/two").timers().size(), + Util.foundServerRequests(registry, "/two should be ignored.")); + + // URIs for server: /vertx/item/{id}, /vertx/item/{id}/{sub}, /servlet/ + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/vertx/item/{id}").timers().size(), + Util.foundServerRequests(registry, + "Vert.x Web template path (/vertx/item/:id) should be detected/translated to /vertx/item/{id}.")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/vertx/item/{id}/{sub}").timers().size(), + Util.foundServerRequests(registry, + "Vert.x Web template path (/vertx/item/:id/:sub) should be detected/translated to /vertx/item/{id}/{sub}.")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/servlet").timers().size(), + Util.foundServerRequests(registry, "Servlet path (/servlet) should be used for servlet")); + + // GET and HEAD are two different methods, there should be two timers for each of these URI tag values + Assertions.assertEquals(2, registry.find("http.server.requests").tag("uri", "/hello/{message}").timers().size(), + Util.foundServerRequests(registry, "/hello/{message} should have two timers (GET and HEAD).")); + Assertions.assertEquals(2, registry.find("http.server.requests").tag("uri", "/vertx/echo/{msg}").timers().size(), + Util.foundServerRequests(registry, "/vertx/echo/{msg} should have two timers (GET and HEAD).")); + + // URIs to trigger REST request: /ping/{message}, /async-ping/{message}, + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/ping/{message}").timers().size(), + Util.foundServerRequests(registry, "/ping/{message} should be returned by JAX-RS.")); + Assertions.assertEquals(1, registry.find("http.server.requests").tag("uri", "/async-ping/{message}").timers().size(), + Util.foundServerRequests(registry, "/async-ping/{message} should be returned by JAX-RS.")); + + // Server response for /pong/{message} + Assertions.assertEquals(1, + registry.find("http.server.requests").tag("uri", "/pong/{message}").tag("outcome", "SUCCESS").timers().size(), + Util.foundServerRequests(registry, "/pong/{message} with tag outcome=SUCCESS should be returned by JAX-RS.")); + Assertions.assertEquals(1, + registry.find("http.server.requests").tag("uri", "/pong/{message}").tag("outcome", "CLIENT_ERROR").timers() + .size(), + Util.foundServerRequests(registry, + "/pong/{message} with tag outcome=CLIENT_ERROR should be returned by JAX-RS.")); + Assertions.assertEquals(1, + registry.find("http.server.requests").tag("uri", "/pong/{message}").tag("outcome", "SERVER_ERROR").timers() + .size(), + Util.foundServerRequests(registry, + "/pong/{message} with tag outcome=SERVER_ERROR should be returned by JAX-RS.")); + + // URI for outbound client request: /pong/{message} + Assertions.assertEquals(1, + registry.find("http.client.requests").tag("uri", "/pong/{message}").tag("outcome", "SUCCESS").timers().size(), + Util.foundClientRequests(registry, + "/pong/{message} with tag outcome=SUCCESS should be returned by Rest client.")); + Assertions.assertEquals(1, + registry.find("http.client.requests").tag("uri", "/pong/{message}").tag("outcome", "CLIENT_ERROR").timers() + .size(), + Util.foundClientRequests(registry, + "/pong/{message} with tag outcome=CLIENT_ERROR should be returned by Rest client.")); + Assertions.assertEquals(1, + registry.find("http.client.requests").tag("uri", "/pong/{message}").tag("outcome", "SERVER_ERROR").timers() + .size(), + Util.foundClientRequests(registry, + "/pong/{message} with tag outcome=SERVER_ERROR should be returned by Rest client.")); + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java index a7993195d294a..657a776969b91 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/vertx/VertxHttpServerMetrics.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.LongAdder; +import java.util.function.Function; import org.jboss.logging.Logger; @@ -15,11 +16,14 @@ import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.binder.http.Outcome; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; import io.quarkus.micrometer.runtime.HttpServerMetricsTagsContributor; import io.quarkus.micrometer.runtime.binder.HttpBinderConfiguration; import io.quarkus.micrometer.runtime.binder.HttpCommonTags; +import io.quarkus.opentelemetry.runtime.QuarkusContextStorage; import io.vertx.core.http.HttpMethod; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.ServerWebSocket; @@ -164,12 +168,14 @@ public void requestReset(HttpRequestMetric requestMetric) { if (path != null) { Timer.Sample sample = requestMetric.getSample(); - sample.stop(requestsTimer - .withTags(Tags.of( - VertxMetricsTags.method(requestMetric.request().method()), - HttpCommonTags.uri(path, requestMetric.initialPath, 0), - Outcome.CLIENT_ERROR.asTag(), - HttpCommonTags.STATUS_RESET))); + executeInContext(sample::stop, + requestsTimer + .withTags(Tags.of( + VertxMetricsTags.method(requestMetric.request().method()), + HttpCommonTags.uri(path, requestMetric.initialPath, 0), + Outcome.CLIENT_ERROR.asTag(), + HttpCommonTags.STATUS_RESET)), + requestMetric.request().context()); } requestMetric.requestEnded(); } @@ -207,11 +213,43 @@ public void responseEnd(HttpRequestMetric requestMetric, HttpResponse response, } } - sample.stop(requestsTimer.withTags(allTags)); + executeInContext(sample::stop, requestsTimer.withTags(allTags), requestMetric.request().context()); } requestMetric.requestEnded(); } + /** + * Called when an HTTP server response has ended. + * Makes sure exemplars are produced because they have an OTel context. + * + * @param methodReference Ex: Sample stop method reference + * @param parameter The parameter to pass to the method + * @param requestContext The request context + * @param

The parameter type is a type of metric, ex: Timer + * @param The return type of the method pointed by the methodReference + * @return The result of the method + */ + static R executeInContext(Function methodReference, P parameter, io.vertx.core.Context requestContext) { + if (requestContext == null) { + return methodReference.apply(parameter); + } + + Context newContext = QuarkusContextStorage.getContext(requestContext); + + if (newContext == null) { + return methodReference.apply(parameter); + } + + io.opentelemetry.context.Context oldContext = QuarkusContextStorage.INSTANCE.current(); + try (Scope scope = QuarkusContextStorage.INSTANCE.attach(newContext)) { + return methodReference.apply(parameter); + } finally { + if (oldContext != null) { + QuarkusContextStorage.INSTANCE.attach(oldContext); + } + } + } + /** * Called when a server web socket connects. * diff --git a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java index bdbb88e786c94..1f9d8ab6704ef 100644 --- a/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java +++ b/extensions/opentelemetry/deployment/src/main/java/io/quarkus/opentelemetry/deployment/tracing/instrumentation/InstrumentationProcessor.java @@ -68,6 +68,16 @@ public boolean getAsBoolean() { } } + static class VertxHttpAvailable implements BooleanSupplier { + private static final boolean IS_VERTX_HTTP_AVAILABLE = isClassPresentAtRuntime( + "io.quarkus.vertx.http.runtime.VertxHttpRecorder"); + + @Override + public boolean getAsBoolean() { + return IS_VERTX_HTTP_AVAILABLE; + } + } + @BuildStep(onlyIf = GrpcExtensionAvailable.class) void grpcTracers(BuildProducer additionalBeans, OTelBuildConfig config) { if (config.instrument().grpc()) { @@ -100,10 +110,16 @@ void registerReactiveMessagingMessageDecorator( } } - @BuildStep(onlyIfNot = MetricsExtensionAvailable.class) + @BuildStep(onlyIfNot = MetricsExtensionAvailable.class, onlyIf = VertxHttpAvailable.class) + @Record(ExecutionTime.STATIC_INIT) + VertxOptionsConsumerBuildItem vertxHttpMetricsOptions(InstrumentationRecorder recorder) { + return new VertxOptionsConsumerBuildItem(recorder.getVertxHttpMetricsOptions(), LIBRARY_AFTER + 1); + } + + @BuildStep(onlyIfNot = { MetricsExtensionAvailable.class, VertxHttpAvailable.class }) @Record(ExecutionTime.STATIC_INIT) - VertxOptionsConsumerBuildItem vertxTracingMetricsOptions(InstrumentationRecorder recorder) { - return new VertxOptionsConsumerBuildItem(recorder.getVertxTracingMetricsOptions(), LIBRARY_AFTER + 1); + VertxOptionsConsumerBuildItem vertxMetricsOptions(InstrumentationRecorder recorder) { + return new VertxOptionsConsumerBuildItem(recorder.getVertxMetricsOptions(), LIBRARY_AFTER + 1); } @BuildStep diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java index abef2653061ee..a803c75acf465 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/InstrumentationRecorder.java @@ -11,6 +11,7 @@ import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.EventBusInstrumenterVertxTracer; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.HttpInstrumenterVertxTracer; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.InstrumenterVertxTracer; +import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxHttpMetricsFactory; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxMetricsFactory; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxTracer; import io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx.OpenTelemetryVertxTracingFactory; @@ -74,7 +75,15 @@ public void setupVertxTracer(BeanContainer beanContainer, boolean sqlClientAvail } /* STATIC INIT */ - public Consumer getVertxTracingMetricsOptions() { + public Consumer getVertxHttpMetricsOptions() { + MetricsOptions metricsOptions = new MetricsOptions() + .setEnabled(true) + .setFactory(new OpenTelemetryVertxHttpMetricsFactory()); + return vertxOptions -> vertxOptions.setMetricsOptions(metricsOptions); + } + + /* STATIC INIT */ + public Consumer getVertxMetricsOptions() { MetricsOptions metricsOptions = new MetricsOptions() .setEnabled(true) .setFactory(new OpenTelemetryVertxMetricsFactory()); diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/MetricRequest.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/MetricRequest.java new file mode 100644 index 0000000000000..f76c13d47ab32 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/MetricRequest.java @@ -0,0 +1,27 @@ +package io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx; + +import java.util.Optional; + +import io.vertx.core.Context; +import io.vertx.core.http.impl.HttpServerRequestInternal; +import io.vertx.core.spi.observability.HttpRequest; + +public final class MetricRequest { + private final HttpRequest request; + + MetricRequest(final HttpRequest request) { + this.request = request; + } + + Optional getContext() { + if (request instanceof HttpServerRequestInternal) { + return Optional.of(((HttpServerRequestInternal) request).context()); + } else { + return Optional.empty(); + } + } + + static MetricRequest request(final HttpRequest httpRequest) { + return new MetricRequest(httpRequest); + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxHttpMetricsFactory.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxHttpMetricsFactory.java new file mode 100644 index 0000000000000..756a6fc807cf5 --- /dev/null +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxHttpMetricsFactory.java @@ -0,0 +1,56 @@ +package io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx; + +import io.quarkus.vertx.http.runtime.ExtendedQuarkusVertxHttpMetrics; +import io.vertx.core.VertxOptions; +import io.vertx.core.http.HttpServerOptions; +import io.vertx.core.net.SocketAddress; +import io.vertx.core.spi.VertxMetricsFactory; +import io.vertx.core.spi.metrics.HttpServerMetrics; +import io.vertx.core.spi.metrics.VertxMetrics; +import io.vertx.core.spi.observability.HttpRequest; + +/** + * This is used to retrieve the route name from Vert.x. This is useful for OpenTelemetry to generate the Span name and + * http.route attribute. Right now, there is no other way to retrieve the route name from Vert.x using the + * Telemetry SPI, so we need to rely on the Metrics SPI. + *

+ * Right now, it is not possible to register multiple VertxMetrics, meaning that only a single one is + * available per Quarkus instance. To avoid clashing with other extensions that provide Metrics data (like the + * Micrometer extension), we only register the {@link OpenTelemetryVertxHttpMetricsFactory} if the + * VertxHttpServerMetrics is not available in the runtime. + */ +public class OpenTelemetryVertxHttpMetricsFactory implements VertxMetricsFactory { + @Override + public VertxMetrics metrics(final VertxOptions options) { + return new OpenTelemetryVertxHttpServerMetrics(); + } + + public static class OpenTelemetryVertxHttpServerMetrics + implements HttpServerMetrics, + VertxMetrics, ExtendedQuarkusVertxHttpMetrics { + + @Override + public HttpServerMetrics createHttpServerMetrics(final HttpServerOptions options, + final SocketAddress localAddress) { + return this; + } + + @Override + public MetricRequest requestBegin(final Object socketMetric, final HttpRequest request) { + return MetricRequest.request(request); + } + + @Override + public void requestRouted(final MetricRequest requestMetric, final String route) { + if (route != null) { + requestMetric.getContext().ifPresent(context -> context.putLocal("VertxRoute", route)); + } + } + + @Override + public ConnectionTracker getHttpConnectionTracker() { + // To be implemented if we decide to instrument with OpenTelemetry. See VertxMeterBinderAdapter for an example. + return ExtendedQuarkusVertxHttpMetrics.NOOP_CONNECTION_TRACKER; + } + } +} diff --git a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java index 32e7d006de6ec..6d030c609b780 100644 --- a/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java +++ b/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/tracing/intrumentation/vertx/OpenTelemetryVertxMetricsFactory.java @@ -1,12 +1,7 @@ package io.quarkus.opentelemetry.runtime.tracing.intrumentation.vertx; -import java.util.Optional; - -import io.quarkus.vertx.http.runtime.ExtendedQuarkusVertxHttpMetrics; -import io.vertx.core.Context; import io.vertx.core.VertxOptions; import io.vertx.core.http.HttpServerOptions; -import io.vertx.core.http.impl.HttpServerRequestInternal; import io.vertx.core.net.SocketAddress; import io.vertx.core.spi.VertxMetricsFactory; import io.vertx.core.spi.metrics.HttpServerMetrics; @@ -17,7 +12,7 @@ * This is used to retrieve the route name from Vert.x. This is useful for OpenTelemetry to generate the Span name and * http.route attribute. Right now, there is no other way to retrieve the route name from Vert.x using the * Telemetry SPI, so we need to rely on the Metrics SPI. - *

+ * * Right now, it is not possible to register multiple VertxMetrics, meaning that only a single one is * available per Quarkus instance. To avoid clashing with other extensions that provide Metrics data (like the * Micrometer extension), we only register the {@link OpenTelemetryVertxMetricsFactory} if the @@ -26,19 +21,17 @@ public class OpenTelemetryVertxMetricsFactory implements VertxMetricsFactory { @Override public VertxMetrics metrics(final VertxOptions options) { - return new OpenTelemetryHttpServerMetrics(); + return new VertxMetrics() { + @Override + public HttpServerMetrics createHttpServerMetrics(final HttpServerOptions options, + final SocketAddress localAddress) { + return new OpenTelemetryVertxServerMetrics(); + } + }; } - public static class OpenTelemetryHttpServerMetrics - implements HttpServerMetrics, - VertxMetrics, ExtendedQuarkusVertxHttpMetrics { - - @Override - public HttpServerMetrics createHttpServerMetrics(final HttpServerOptions options, - final SocketAddress localAddress) { - return this; - } - + public static class OpenTelemetryVertxServerMetrics + implements HttpServerMetrics { @Override public MetricRequest requestBegin(final Object socketMetric, final HttpRequest request) { return MetricRequest.request(request); @@ -50,31 +43,5 @@ public void requestRouted(final MetricRequest requestMetric, final String route) requestMetric.getContext().ifPresent(context -> context.putLocal("VertxRoute", route)); } } - - @Override - public ConnectionTracker getHttpConnectionTracker() { - // To be implemented if we decide to instrument with OpenTelemetry. See VertxMeterBinderAdapter for an example. - return ExtendedQuarkusVertxHttpMetrics.NOOP_CONNECTION_TRACKER; - } - - static final class MetricRequest { - private final HttpRequest request; - - MetricRequest(final HttpRequest request) { - this.request = request; - } - - Optional getContext() { - if (request instanceof HttpServerRequestInternal) { - return Optional.of(((HttpServerRequestInternal) request).context()); - } else { - return Optional.empty(); - } - } - - static MetricRequest request(final HttpRequest httpRequest) { - return new MetricRequest(httpRequest); - } - } } } diff --git a/integration-tests/opentelemetry-minimal/pom.xml b/integration-tests/opentelemetry-minimal/pom.xml new file mode 100644 index 0000000000000..7dccd1b87b7a5 --- /dev/null +++ b/integration-tests/opentelemetry-minimal/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + + + quarkus-integration-test-opentelemetry-minimal + Quarkus - Integration Tests - OpenTelemetry minimal (No HTTP) + + + + io.quarkus + quarkus-vertx + + + io.quarkus + quarkus-opentelemetry + + + io.opentelemetry + opentelemetry-sdk-testing + + + + io.quarkus + quarkus-junit5 + test + + + org.awaitility + awaitility + test + + + + + io.quarkus + + quarkus-vertx-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 + + + + + true + + + + + + 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 + + + + + + + + + + + + \ No newline at end of file diff --git a/integration-tests/opentelemetry-minimal/src/main/java/io/quarkus/it/opentelemetry/minimal/HelloService.java b/integration-tests/opentelemetry-minimal/src/main/java/io/quarkus/it/opentelemetry/minimal/HelloService.java new file mode 100644 index 0000000000000..3535ed97271e6 --- /dev/null +++ b/integration-tests/opentelemetry-minimal/src/main/java/io/quarkus/it/opentelemetry/minimal/HelloService.java @@ -0,0 +1,42 @@ +package io.quarkus.it.opentelemetry.minimal; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter; +import io.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Vertx; + +@ApplicationScoped +public class HelloService { + + @Inject + Vertx vertx; + + @Inject + InMemoryMetricExporter inMemoryMetricExporter; + + void onStart(@Observes StartupEvent ev) { + // Code executed during application startup + System.out.println("Application is starting..."); + } + + public Uni getMetricCount() { + return Uni.createFrom().emitter(e -> { + vertx.setTimer(100, x -> e.complete(inMemoryMetricExporter.getFinishedMetricItems().size())); + }); + } + + @ApplicationScoped + static class InMemoryMetricExporterProducer { + @Produces + @Singleton + InMemoryMetricExporter inMemoryMetricsExporter() { + return InMemoryMetricExporter.create(); + } + } +} diff --git a/integration-tests/opentelemetry-minimal/src/main/resources/application.properties b/integration-tests/opentelemetry-minimal/src/main/resources/application.properties new file mode 100644 index 0000000000000..ee272da7ecfae --- /dev/null +++ b/integration-tests/opentelemetry-minimal/src/main/resources/application.properties @@ -0,0 +1,8 @@ +# Setting these for tests explicitly. Not required in normal application +quarkus.application.name=opentelemetry-minimal +# speed up build +quarkus.otel.bsp.schedule.delay=100 +quarkus.otel.bsp.export.timeout=5s +quarkus.otel.metrics.enabled=true +quarkus.otel.metric.export.interval=100ms +#quarkus.otel.logs.enabled=true diff --git a/integration-tests/opentelemetry-minimal/src/test/java/io/quarkus/it/opentelemetry/minimal/HelloServiceIT.java b/integration-tests/opentelemetry-minimal/src/test/java/io/quarkus/it/opentelemetry/minimal/HelloServiceIT.java new file mode 100644 index 0000000000000..fc08b42ddb2d3 --- /dev/null +++ b/integration-tests/opentelemetry-minimal/src/test/java/io/quarkus/it/opentelemetry/minimal/HelloServiceIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.opentelemetry.minimal; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class HelloServiceIT extends HelloServiceTest { + +} diff --git a/integration-tests/opentelemetry-minimal/src/test/java/io/quarkus/it/opentelemetry/minimal/HelloServiceTest.java b/integration-tests/opentelemetry-minimal/src/test/java/io/quarkus/it/opentelemetry/minimal/HelloServiceTest.java new file mode 100644 index 0000000000000..3053dbab2ee92 --- /dev/null +++ b/integration-tests/opentelemetry-minimal/src/test/java/io/quarkus/it/opentelemetry/minimal/HelloServiceTest.java @@ -0,0 +1,26 @@ +package io.quarkus.it.opentelemetry.minimal; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; + +import java.time.Duration; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class HelloServiceTest { + + @Inject + HelloService helloService; + + @Test + public void testHello() { + Integer result = helloService.getMetricCount().await().atMost(Duration.ofSeconds(5)); + System.out.println("Metric count: " + result); + assertThat(result, greaterThanOrEqualTo(1)); + } +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 57e80f86b112c..cf618d0ab6e3c 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -360,6 +360,7 @@ opentelemetry-quickstart opentelemetry-spi opentelemetry-jdbc-instrumentation + opentelemetry-minimal opentelemetry-mongodb-client-instrumentation opentelemetry-quartz opentelemetry-scheduler