From c11835963fa1f2a46b53a1156026de6efb69a98f Mon Sep 17 00:00:00 2001 From: Nikita Salnikov-Tarnovski Date: Sat, 13 Jun 2020 23:18:30 +0300 Subject: [PATCH] Vert.x instrumentation improvements (#503) * Support for Vert.x rx-java async tasks * Use Vert.x route for server span name * Move reactive Vert.x instrumentation into separate module * Test fixes * Format fixes * Polish * Fix license header * Add Vert.x to README --- README.md | 2 + .../AkkaHttpServerInstrumentationTest.groovy | 2 +- .../src/test/groovy/GrizzlyTest.groovy | 2 +- .../test/groovy/server/PlayServerTest.groovy | 2 +- .../reactive/AsyncResultConsumerWrapper.java | 57 +++++++ .../reactive/AsyncResultHandlerWrapper.java | 54 +++++++ .../vertx/reactive/VertxDecorator.java | 25 +++ .../reactive/VertxRxInstrumentation.java | 95 +++++++++++ .../vertx/reactive/package-info.java | 9 ++ .../NettyServerTestInstrumentation.java | 35 +++++ .../VertxReactivePropagationTest.groovy | 98 ++++++++++++ .../test/groovy/VertxReactiveWebServer.java | 147 ++++++++++++++++++ .../VertxRxCircuitBreakerWebClientTest.groovy | 0 .../groovy/client/VertxRxWebClientTest.groovy | 0 ...VertxRxCircuitBreakerHttpServerTest.groovy | 20 ++- .../server/VertxRxHttpServerTest.groovy} | 52 +++++-- .../vertx-reactive.gradle} | 31 ++-- .../server/VertxRxHttpServerTest.groovy | 74 --------- .../vertx/RouteInstrumentation.java | 75 +++++++++ .../vertx/RoutingContextHandlerWrapper.java | 48 ++++++ .../instrumentation/vertx/VertxDecorator.java | 25 +++ .../instrumentation/vertx/package-info.java | 14 ++ .../groovy/client/VertxHttpClientTest.groovy | 0 .../NettyServerTestInstrumentation.java | 0 .../groovy/server/VertxHttpServerTest.groovy | 82 ++++++++++ .../test/groovy/server/VertxWebServer.java | 123 +++++++++++++++ instrumentation/vertx/vertx.gradle | 40 +++++ settings.gradle | 3 +- .../auto/test/base/HttpServerTest.groovy | 9 +- 29 files changed, 1016 insertions(+), 108 deletions(-) create mode 100644 instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultConsumerWrapper.java create mode 100644 instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultHandlerWrapper.java create mode 100644 instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxDecorator.java create mode 100644 instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxRxInstrumentation.java create mode 100644 instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/package-info.java create mode 100644 instrumentation/vertx-reactive/src/test/groovy/NettyServerTestInstrumentation.java create mode 100644 instrumentation/vertx-reactive/src/test/groovy/VertxReactivePropagationTest.groovy create mode 100644 instrumentation/vertx-reactive/src/test/groovy/VertxReactiveWebServer.java rename instrumentation/{vertx-testing => vertx-reactive}/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy (100%) rename instrumentation/{vertx-testing => vertx-reactive}/src/test/groovy/client/VertxRxWebClientTest.groovy (100%) rename instrumentation/{vertx-testing => vertx-reactive}/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy (86%) rename instrumentation/{vertx-testing/src/test/groovy/server/VertxHttpServerTest.groovy => vertx-reactive/src/test/groovy/server/VertxRxHttpServerTest.groovy} (73%) rename instrumentation/{vertx-testing/vertx-testing.gradle => vertx-reactive/vertx-reactive.gradle} (54%) delete mode 100644 instrumentation/vertx-testing/src/test/groovy/server/VertxRxHttpServerTest.groovy create mode 100644 instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RouteInstrumentation.java create mode 100644 instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RoutingContextHandlerWrapper.java create mode 100644 instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/VertxDecorator.java create mode 100644 instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/package-info.java rename instrumentation/{vertx-testing => vertx}/src/test/groovy/client/VertxHttpClientTest.groovy (100%) rename instrumentation/{vertx-testing => vertx}/src/test/groovy/server/NettyServerTestInstrumentation.java (100%) create mode 100644 instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy create mode 100644 instrumentation/vertx/src/test/groovy/server/VertxWebServer.java create mode 100644 instrumentation/vertx/vertx.gradle diff --git a/README.md b/README.md index 099000a441..c4421d4c72 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,8 @@ provide the path to a JAR file including an SPI implementation using the system | [Spring Webflux](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/package-summary.html) | 5.0+ | | [Spymemcached](https://github.com/couchbase/spymemcached) | 2.12+ | | [Twilio](https://github.com/twilio/twilio-java) | 6.6+ | +| [Vert.x](https://vertx.io) | 3.0+ | +| [Vert.x RxJava2](https://vertx.io/docs/vertx-rx/java2/) | 3.5+ | ### Disabled instrumentations diff --git a/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy b/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy index 603865e357..1e5cff95d6 100644 --- a/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy +++ b/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy @@ -43,7 +43,7 @@ abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) { trace.span(index) { - operationName expectedOperationName(method) + operationName expectedOperationName(method, endpoint) spanKind SERVER errored endpoint.errored if (parentID != null) { diff --git a/instrumentation/grizzly-2.0/src/test/groovy/GrizzlyTest.groovy b/instrumentation/grizzly-2.0/src/test/groovy/GrizzlyTest.groovy index 12f11463bb..9c46643924 100644 --- a/instrumentation/grizzly-2.0/src/test/groovy/GrizzlyTest.groovy +++ b/instrumentation/grizzly-2.0/src/test/groovy/GrizzlyTest.groovy @@ -106,7 +106,7 @@ class GrizzlyTest extends HttpServerTest { } @Override - String expectedOperationName(String method) { + String expectedOperationName(String method, ServerEndpoint serverEndpoint) { return 'HttpHandler.doHandle' } } diff --git a/instrumentation/play/play-2.6/src/test/groovy/server/PlayServerTest.groovy b/instrumentation/play/play-2.6/src/test/groovy/server/PlayServerTest.groovy index 4d81ad1984..0fc0e71a24 100644 --- a/instrumentation/play/play-2.6/src/test/groovy/server/PlayServerTest.groovy +++ b/instrumentation/play/play-2.6/src/test/groovy/server/PlayServerTest.groovy @@ -111,7 +111,7 @@ class PlayServerTest extends HttpServerTest { void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) { trace.span(index) { - operationName expectedOperationName(method) + operationName expectedOperationName(method, endpoint) spanKind SERVER errored endpoint.errored if (parentID != null) { diff --git a/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultConsumerWrapper.java b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultConsumerWrapper.java new file mode 100644 index 0000000000..9cf6711671 --- /dev/null +++ b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultConsumerWrapper.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx.reactive; + +import static io.opentelemetry.auto.instrumentation.vertx.reactive.VertxDecorator.TRACER; + +import io.opentelemetry.context.Scope; +import io.opentelemetry.trace.Span; +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import java.util.function.Consumer; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AsyncResultConsumerWrapper implements Consumer>> { + private final Consumer>> delegate; + private final Span parentSpan; + + public AsyncResultConsumerWrapper( + final Consumer>> delegate, Span parentSpan) { + this.delegate = delegate; + this.parentSpan = parentSpan; + } + + @Override + public void accept(final Handler> asyncResultHandler) { + if (parentSpan != null) { + try (final Scope scope = TRACER.withSpan(parentSpan)) { + delegate.accept(asyncResultHandler); + } + } else { + delegate.accept(asyncResultHandler); + } + } + + public static Consumer>> wrapIfNeeded( + final Consumer>> delegate, final Span parentSpan) { + if (!(delegate instanceof AsyncResultConsumerWrapper)) { + log.debug("Wrapping consumer {}", delegate); + return new AsyncResultConsumerWrapper(delegate, parentSpan); + } + return delegate; + } +} diff --git a/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultHandlerWrapper.java b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultHandlerWrapper.java new file mode 100644 index 0000000000..e871cfcfd5 --- /dev/null +++ b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/AsyncResultHandlerWrapper.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx.reactive; + +import io.opentelemetry.context.Scope; +import io.opentelemetry.trace.Span; +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AsyncResultHandlerWrapper implements Handler>> { + private final Handler>> delegate; + private final Span parentSpan; + + public AsyncResultHandlerWrapper( + final Handler>> delegate, Span parentSpan) { + this.delegate = delegate; + this.parentSpan = parentSpan; + } + + @Override + public void handle(final Handler> asyncResultHandler) { + if (parentSpan != null) { + try (final Scope scope = VertxDecorator.TRACER.withSpan(parentSpan)) { + delegate.handle(asyncResultHandler); + } + } else { + delegate.handle(asyncResultHandler); + } + } + + public static Handler>> wrapIfNeeded( + final Handler>> delegate, final Span parentSpan) { + if (!(delegate instanceof AsyncResultHandlerWrapper)) { + log.debug("Wrapping handler {}", delegate); + return new AsyncResultHandlerWrapper(delegate, parentSpan); + } + return delegate; + } +} diff --git a/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxDecorator.java b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxDecorator.java new file mode 100644 index 0000000000..9f9eca0680 --- /dev/null +++ b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxDecorator.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx.reactive; + +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.BaseDecorator; +import io.opentelemetry.trace.Tracer; + +public class VertxDecorator extends BaseDecorator { + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.vertx"); +} diff --git a/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxRxInstrumentation.java b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxRxInstrumentation.java new file mode 100644 index 0000000000..48fa4e5001 --- /dev/null +++ b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/VertxRxInstrumentation.java @@ -0,0 +1,95 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx.reactive; + +import static io.opentelemetry.auto.instrumentation.vertx.reactive.VertxDecorator.TRACER; +import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import io.vertx.core.AsyncResult; +import io.vertx.core.Handler; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +/** This instrumentation allows span context propagation across Vert.x reactive executions. */ +@AutoService(Instrumenter.class) +public class VertxRxInstrumentation extends Instrumenter.Default { + + public VertxRxInstrumentation() { + super("vertx"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + // Different versions of Vert.x has this class in different packages + return hasClassesNamed("io.vertx.reactivex.core.impl.AsyncResultSingle") + .or(hasClassesNamed("io.vertx.reactivex.impl.AsyncResultSingle")); + } + + @Override + public ElementMatcher typeMatcher() { + return named("io.vertx.reactivex.core.impl.AsyncResultSingle") + .or(named("io.vertx.reactivex.impl.AsyncResultSingle")); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".AsyncResultConsumerWrapper", + packageName + ".AsyncResultHandlerWrapper", + packageName + ".VertxDecorator" + }; + } + + @Override + public Map, String> transformers() { + Map, String> result = new HashMap<>(); + result.put( + isConstructor().and(takesArgument(0, named("io.vertx.core.Handler"))), + this.getClass().getName() + "$AsyncResultSingleHandlerAdvice"); + result.put( + isConstructor().and(takesArgument(0, named("java.util.function.Consumer"))), + this.getClass().getName() + "$AsyncResultSingleConsumerAdvice"); + return result; + } + + public static class AsyncResultSingleHandlerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapHandler( + @Advice.Argument(value = 0, readOnly = false) Handler>> handler) { + handler = AsyncResultHandlerWrapper.wrapIfNeeded(handler, TRACER.getCurrentSpan()); + } + } + + public static class AsyncResultSingleConsumerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapHandler( + @Advice.Argument(value = 0, readOnly = false) Consumer>> handler) { + handler = AsyncResultConsumerWrapper.wrapIfNeeded(handler, TRACER.getCurrentSpan()); + } + } +} diff --git a/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/package-info.java b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/package-info.java new file mode 100644 index 0000000000..1f1aec3599 --- /dev/null +++ b/instrumentation/vertx-reactive/src/main/java/io/opentelemetry/auto/instrumentation/vertx/reactive/package-info.java @@ -0,0 +1,9 @@ +/** + * The majority of monitoring needs of Vert.x application is covered by generic instrumentations. + * Such as those of netty or JDBC. + * + *

{@link io.opentelemetry.auto.instrumentation.vertx.reactive.VertxRxInstrumentation} wraps + * {code AsyncResultSingle} classes from Vert.x RxJava library to ensure proper span context + * propagation in reactive Vert.x applications. + */ +package io.opentelemetry.auto.instrumentation.vertx.reactive; diff --git a/instrumentation/vertx-reactive/src/test/groovy/NettyServerTestInstrumentation.java b/instrumentation/vertx-reactive/src/test/groovy/NettyServerTestInstrumentation.java new file mode 100644 index 0000000000..b2f983feb9 --- /dev/null +++ b/instrumentation/vertx-reactive/src/test/groovy/NettyServerTestInstrumentation.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.test.base.HttpServerTestAdvice; +import io.opentelemetry.auto.tooling.Instrumenter; +import net.bytebuddy.agent.builder.AgentBuilder; + +@AutoService(Instrumenter.class) +public class NettyServerTestInstrumentation implements Instrumenter { + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type(named("io.netty.handler.codec.ByteToMessageDecoder")) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("channelRead"), HttpServerTestAdvice.ServerEntryAdvice.class.getName())); + } +} diff --git a/instrumentation/vertx-reactive/src/test/groovy/VertxReactivePropagationTest.groovy b/instrumentation/vertx-reactive/src/test/groovy/VertxReactivePropagationTest.groovy new file mode 100644 index 0000000000..54e08728e8 --- /dev/null +++ b/instrumentation/vertx-reactive/src/test/groovy/VertxReactivePropagationTest.groovy @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import io.opentelemetry.auto.instrumentation.api.MoreTags +import io.opentelemetry.auto.instrumentation.api.Tags +import io.opentelemetry.auto.test.AgentTestRunner +import io.opentelemetry.auto.test.utils.OkHttpUtils +import io.opentelemetry.auto.test.utils.PortUtils +import io.vertx.reactivex.core.Vertx +import okhttp3.OkHttpClient +import okhttp3.Request +import spock.lang.Shared + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS +import static io.opentelemetry.auto.test.utils.TraceUtils.basicSpan +import static io.opentelemetry.trace.Span.Kind.CLIENT +import static io.opentelemetry.trace.Span.Kind.SERVER + +class VertxReactivePropagationTest extends AgentTestRunner { + @Shared + OkHttpClient client = OkHttpUtils.client() + + @Shared + int port + + @Shared + Vertx server + + def setupSpec() { + port = PortUtils.randomOpenPort() + server = VertxReactiveWebServer.start(port) + } + + def cleanupSpec() { + server.close() + } + + //Verifies that context is correctly propagated and sql query span has correct parent. + //Tests io.opentelemetry.auto.instrumentation.vertx.reactive.VertxRxInstrumentation + def "should propagate context over vert.x rx-java framework"() { + setup: + def url = "http://localhost:$port/listProducts" + def request = new Request.Builder().url(url).get().build() + def response = client.newCall(request).execute() + + expect: + response.code() == SUCCESS.status + + and: + assertTraces(1) { + trace(0, 4) { + span(0) { + operationName "/listProducts" + spanKind SERVER + errored false + parent() + tags { + "$MoreTags.NET_PEER_PORT" Long + "$MoreTags.NET_PEER_IP" { it == null || it == "127.0.0.1" } // Optional + "$Tags.HTTP_URL" url + "$Tags.HTTP_METHOD" "GET" + "$Tags.HTTP_STATUS" 200 + } + } + basicSpan(it, 1, "VertxReactiveWebServer.handleListProducts", span(0)) + basicSpan(it, 2, "VertxReactiveWebServer.listProducts", span(1)) + span(3) { + operationName "SELECT id, name, price, weight FROM products" + spanKind CLIENT + childOf span(2) + errored false + tags { + "$Tags.DB_TYPE" "sql" + "$Tags.DB_INSTANCE" "test?shutdown=true" + "$Tags.DB_USER" "SA" + "$Tags.DB_STATEMENT" "SELECT id, name, price, weight FROM products" + "$Tags.DB_URL" "hsqldb:mem:" + "span.origin.type" String + } + } + } + } + } + + +} diff --git a/instrumentation/vertx-reactive/src/test/groovy/VertxReactiveWebServer.java b/instrumentation/vertx-reactive/src/test/groovy/VertxReactiveWebServer.java new file mode 100644 index 0000000000..35edf972e4 --- /dev/null +++ b/instrumentation/vertx-reactive/src/test/groovy/VertxReactiveWebServer.java @@ -0,0 +1,147 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS; + +import io.opentelemetry.contrib.auto.annotations.WithSpan; +import io.reactivex.Single; +import io.vertx.core.DeploymentOptions; +import io.vertx.core.Handler; +import io.vertx.core.VertxOptions; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import io.vertx.reactivex.core.AbstractVerticle; +import io.vertx.reactivex.core.Vertx; +import io.vertx.reactivex.core.http.HttpServerResponse; +import io.vertx.reactivex.ext.jdbc.JDBCClient; +import io.vertx.reactivex.ext.sql.SQLConnection; +import io.vertx.reactivex.ext.web.Router; +import io.vertx.reactivex.ext.web.RoutingContext; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class VertxReactiveWebServer extends AbstractVerticle { + private static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; + private static JDBCClient client; + + public static Vertx start(final int port) + throws ExecutionException, InterruptedException, TimeoutException { + /* This is highly against Vertx ideas, but our tests are synchronous + so we have to make sure server is up and running */ + final CompletableFuture future = new CompletableFuture<>(); + + final Vertx server = Vertx.vertx(new VertxOptions()); + + client = + JDBCClient.createShared( + server, + new JsonObject() + .put("url", "jdbc:hsqldb:mem:test?shutdown=true") + .put("driver_class", "org.hsqldb.jdbcDriver")); + + log.info("Starting on port {}", port); + server.deployVerticle( + VertxReactiveWebServer.class.getName(), + new DeploymentOptions().setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)), + res -> { + if (!res.succeeded()) { + final RuntimeException exception = + new RuntimeException("Cannot deploy server Verticle", res.cause()); + future.completeExceptionally(exception); + } + future.complete(null); + }); + // block until vertx server is up + future.get(30, TimeUnit.SECONDS); + + return server; + } + + @Override + public void start(final io.vertx.core.Future startFuture) { + setUpInitialData( + ready -> { + final Router router = Router.router(vertx); + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); + log.info("Listening on port {}", port); + router + .route(SUCCESS.getPath()) + .handler( + ctx -> ctx.response().setStatusCode(SUCCESS.getStatus()).end(SUCCESS.getBody())); + + router.route("/listProducts").handler(this::handleListProducts); + + vertx + .createHttpServer() + .requestHandler(router::accept) + .listen(port, h -> startFuture.complete()); + }); + } + + @WithSpan + private void handleListProducts(final RoutingContext routingContext) { + final HttpServerResponse response = routingContext.response(); + final Single jsonArraySingle = listProducts(); + + jsonArraySingle.subscribe( + arr -> response.putHeader("content-type", "application/json").end(arr.encode())); + } + + @WithSpan + private Single listProducts() { + return client + .rxQuery("SELECT id, name, price, weight FROM products") + .flatMap( + result -> { + Thread.dumpStack(); + final JsonArray arr = new JsonArray(); + result.getRows().forEach(arr::add); + return Single.just(arr); + }); + } + + private void setUpInitialData(final Handler done) { + client.getConnection( + res -> { + if (res.failed()) { + throw new RuntimeException(res.cause()); + } + + final SQLConnection conn = res.result(); + + conn.execute( + "CREATE TABLE IF NOT EXISTS products(id INT IDENTITY, name VARCHAR(255), price FLOAT, weight INT)", + ddl -> { + if (ddl.failed()) { + throw new RuntimeException(ddl.cause()); + } + + conn.execute( + "INSERT INTO products (name, price, weight) VALUES ('Egg Whisk', 3.99, 150), ('Tea Cosy', 5.99, 100), ('Spatula', 1.00, 80)", + fixtures -> { + if (fixtures.failed()) { + throw new RuntimeException(fixtures.cause()); + } + + done.handle(null); + }); + }); + }); + } +} diff --git a/instrumentation/vertx-testing/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy b/instrumentation/vertx-reactive/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy similarity index 100% rename from instrumentation/vertx-testing/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy rename to instrumentation/vertx-reactive/src/test/groovy/client/VertxRxCircuitBreakerWebClientTest.groovy diff --git a/instrumentation/vertx-testing/src/test/groovy/client/VertxRxWebClientTest.groovy b/instrumentation/vertx-reactive/src/test/groovy/client/VertxRxWebClientTest.groovy similarity index 100% rename from instrumentation/vertx-testing/src/test/groovy/client/VertxRxWebClientTest.groovy rename to instrumentation/vertx-reactive/src/test/groovy/client/VertxRxWebClientTest.groovy diff --git a/instrumentation/vertx-testing/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy b/instrumentation/vertx-reactive/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy similarity index 86% rename from instrumentation/vertx-testing/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy rename to instrumentation/vertx-reactive/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy index 1c1332c45a..57f0060daf 100644 --- a/instrumentation/vertx-testing/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy +++ b/instrumentation/vertx-reactive/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy @@ -17,17 +17,19 @@ package server import io.opentelemetry.auto.test.base.HttpServerTest import io.vertx.circuitbreaker.CircuitBreakerOptions +import io.vertx.core.Future import io.vertx.reactivex.circuitbreaker.CircuitBreaker import io.vertx.reactivex.core.AbstractVerticle import io.vertx.reactivex.ext.web.Router import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS -class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest { +class VertxRxCircuitBreakerHttpServerTest extends VertxRxHttpServerTest { @Override protected Class verticle() { @@ -37,7 +39,7 @@ class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest { static class VertxRxCircuitBreakerWebTestServer extends AbstractVerticle { @Override - void start(final io.vertx.core.Future startFuture) { + void start(final Future startFuture) { final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT) final Router router = Router.router(super.@vertx) final CircuitBreaker breaker = @@ -114,6 +116,20 @@ class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest { } }) } + router.route("/path/:id/param").handler { ctx -> + breaker.executeCommand({ future -> + future.complete(PATH_PARAM) + }, { + if (it.failed()) { + throw it.cause() + } + HttpServerTest.ServerEndpoint endpoint = it.result() + controller(endpoint) { + ctx.response().setStatusCode(endpoint.status).end(ctx.request().getParam("id")) + } + }) + } + super.@vertx.createHttpServer() .requestHandler { router.accept(it) } diff --git a/instrumentation/vertx-testing/src/test/groovy/server/VertxHttpServerTest.groovy b/instrumentation/vertx-reactive/src/test/groovy/server/VertxRxHttpServerTest.groovy similarity index 73% rename from instrumentation/vertx-testing/src/test/groovy/server/VertxHttpServerTest.groovy rename to instrumentation/vertx-reactive/src/test/groovy/server/VertxRxHttpServerTest.groovy index f2eb5f2b92..686a8ffb98 100644 --- a/instrumentation/vertx-testing/src/test/groovy/server/VertxHttpServerTest.groovy +++ b/instrumentation/vertx-reactive/src/test/groovy/server/VertxRxHttpServerTest.groovy @@ -16,33 +16,34 @@ package server import io.opentelemetry.auto.test.base.HttpServerTest -import io.vertx.core.AbstractVerticle import io.vertx.core.DeploymentOptions import io.vertx.core.Future import io.vertx.core.Vertx import io.vertx.core.VertxOptions import io.vertx.core.json.JsonObject -import io.vertx.ext.web.Router - +import io.vertx.reactivex.core.AbstractVerticle +import io.vertx.reactivex.ext.web.Router import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS -class VertxHttpServerTest extends HttpServerTest { +class VertxRxHttpServerTest extends HttpServerTest { public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port" @Override Vertx startServer(int port) { - def server = Vertx.vertx(new VertxOptions() + Vertx server = Vertx.vertx(new VertxOptions() // Useful for debugging: // .setBlockedThreadCheckInterval(Integer.MAX_VALUE) .setClusterPort(port)) final CompletableFuture future = new CompletableFuture<>() - server.deployVerticle(verticle().name, + server.deployVerticle(verticle().getName(), new DeploymentOptions() .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) .setInstances(3)) { res -> @@ -52,14 +53,10 @@ class VertxHttpServerTest extends HttpServerTest { future.complete(null) } - future.get() + future.get(30, TimeUnit.SECONDS) return server } - protected Class verticle() { - return VertxWebTestServer - } - @Override void stopServer(Vertx server) { server.close() @@ -67,15 +64,34 @@ class VertxHttpServerTest extends HttpServerTest { @Override boolean testExceptionBody() { - false + return false } - static class VertxWebTestServer extends AbstractVerticle { + @Override + boolean testPathParam() { + return true + } + + @Override + boolean testNotFound() { + return false + } + + @Override + String expectedOperationName(String method, ServerEndpoint endpoint) { + return endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.getPath() + } + + protected Class verticle() { + return VertxReactiveWebServer + } + + static class VertxReactiveWebServer extends AbstractVerticle { @Override void start(final Future startFuture) { final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT) - final Router router = Router.router(vertx) + final Router router = Router.router(super.@vertx) router.route(SUCCESS.path).handler { ctx -> controller(SUCCESS) { @@ -102,8 +118,14 @@ class VertxHttpServerTest extends HttpServerTest { throw new Exception(EXCEPTION.body) } } + router.route("/path/:id/param").handler { ctx -> + controller(PATH_PARAM) { + ctx.response().setStatusCode(PATH_PARAM.status).end(ctx.request().getParam("id")) + } + } - vertx.createHttpServer() + + super.@vertx.createHttpServer() .requestHandler { router.accept(it) } .listen(port) { startFuture.complete() } } diff --git a/instrumentation/vertx-testing/vertx-testing.gradle b/instrumentation/vertx-reactive/vertx-reactive.gradle similarity index 54% rename from instrumentation/vertx-testing/vertx-testing.gradle rename to instrumentation/vertx-reactive/vertx-reactive.gradle index a4c2f74721..959ed0e472 100644 --- a/instrumentation/vertx-testing/vertx-testing.gradle +++ b/instrumentation/vertx-reactive/vertx-reactive.gradle @@ -1,4 +1,3 @@ -// Set properties before any plugins get loaded ext { minJavaVersionForTests = JavaVersion.VERSION_1_8 } @@ -6,30 +5,44 @@ ext { apply from: "$rootDir/gradle/instrumentation.gradle" apply plugin: 'org.unbroken-dome.test-sets' +muzzle { + pass { + group = 'io.vertx' + module = 'vertx-rx-java2' + versions = "[3.5.0,)" + } +} + testSets { latestDepTest { dirName = 'test' } } -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +//The first Vert.x version that uses rx-java 2 +ext.vertxVersion = '3.5.0' dependencies { -// compileOnly group: 'io.vertx', name: 'vertx-web', version: '3.5.0' + compileOnly group: 'io.vertx', name: 'vertx-web', version: vertxVersion + compileOnly group: 'io.vertx', name: 'vertx-rx-java2', version: vertxVersion + testCompile project(':instrumentation:jdbc') testCompile project(':instrumentation:netty:netty-4.1') testCompile project(':instrumentation:trace-annotation') + testCompile project(':instrumentation:vertx') + + testCompile group: 'io.vertx', name: 'vertx-web', version: vertxVersion + testCompile group: 'io.vertx', name: 'vertx-web-client', version: vertxVersion + testCompile group: 'io.vertx', name: 'vertx-jdbc-client', version: vertxVersion + testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: vertxVersion + testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: vertxVersion + testCompile 'org.hsqldb:hsqldb:2.3.4' - // Tests seem to fail before 3.5... maybe a problem with some of the tests? - testCompile group: 'io.vertx', name: 'vertx-web', version: '3.5.0' - testCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.5.0' - testCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.5.0' - testCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.5.0' // Vert.x 4.0 is incompatible with our tests. latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '3.+' latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-jdbc-client', version: '3.+' latestDepTestCompile group: 'io.vertx', name: 'vertx-circuit-breaker', version: '3.+' latestDepTestCompile group: 'io.vertx', name: 'vertx-rx-java2', version: '3.+' } diff --git a/instrumentation/vertx-testing/src/test/groovy/server/VertxRxHttpServerTest.groovy b/instrumentation/vertx-testing/src/test/groovy/server/VertxRxHttpServerTest.groovy deleted file mode 100644 index 687a1bcf38..0000000000 --- a/instrumentation/vertx-testing/src/test/groovy/server/VertxRxHttpServerTest.groovy +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package server - - -import io.vertx.core.Future -import io.vertx.reactivex.core.AbstractVerticle -import io.vertx.reactivex.ext.web.Router - -import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR -import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION -import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT -import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS - -class VertxRxHttpServerTest extends VertxHttpServerTest { - - @Override - protected Class verticle() { - return VertxRxWebTestServer - } - - static class VertxRxWebTestServer extends AbstractVerticle { - - @Override - void start(final Future startFuture) { - final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT) - final Router router = Router.router(super.@vertx) - - router.route(SUCCESS.path).handler { ctx -> - controller(SUCCESS) { - ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body) - } - } - router.route(QUERY_PARAM.path).handler { ctx -> - controller(QUERY_PARAM) { - ctx.response().setStatusCode(QUERY_PARAM.status).end(ctx.request().query()) - } - } - router.route(REDIRECT.path).handler { ctx -> - controller(REDIRECT) { - ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body).end() - } - } - router.route(ERROR.path).handler { ctx -> - controller(ERROR) { - ctx.response().setStatusCode(ERROR.status).end(ERROR.body) - } - } - router.route(EXCEPTION.path).handler { ctx -> - controller(EXCEPTION) { - throw new Exception(EXCEPTION.body) - } - } - - super.@vertx.createHttpServer() - .requestHandler { router.accept(it) } - .listen(port) { startFuture.complete() } - } - } -} diff --git a/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RouteInstrumentation.java b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RouteInstrumentation.java new file mode 100644 index 0000000000..725e59f676 --- /dev/null +++ b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RouteInstrumentation.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx; + +import static io.opentelemetry.auto.tooling.ClassLoaderMatcher.hasClassesNamed; +import static io.opentelemetry.auto.tooling.bytebuddy.matcher.AgentElementMatchers.safeHasSuperType; +import static java.util.Collections.singletonMap; +import static net.bytebuddy.matcher.ElementMatchers.isInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.not; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import com.google.auto.service.AutoService; +import io.opentelemetry.auto.tooling.Instrumenter; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +@AutoService(Instrumenter.class) +public final class RouteInstrumentation extends Instrumenter.Default { + + public RouteInstrumentation() { + super("vertx"); + } + + @Override + public ElementMatcher classLoaderMatcher() { + return hasClassesNamed("io.vertx.ext.web.Route"); + } + + @Override + public ElementMatcher typeMatcher() { + return not(isInterface()).and(safeHasSuperType(named("io.vertx.ext.web.Route"))); + } + + @Override + public String[] helperClassNames() { + return new String[] { + packageName + ".RoutingContextHandlerWrapper", packageName + ".VertxDecorator", + }; + } + + @Override + public Map, String> transformers() { + return singletonMap( + isMethod().and(named("handler")).and(takesArgument(0, named("io.vertx.core.Handler"))), + RouteInstrumentation.class.getName() + "$RouteAdvice"); + } + + public static class RouteAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void wrapHandler( + @Advice.Argument(value = 0, readOnly = false) Handler handler) { + handler = new RoutingContextHandlerWrapper(handler); + } + } +} diff --git a/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RoutingContextHandlerWrapper.java b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RoutingContextHandlerWrapper.java new file mode 100644 index 0000000000..a1bc0c8329 --- /dev/null +++ b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/RoutingContextHandlerWrapper.java @@ -0,0 +1,48 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx; + +import static io.opentelemetry.auto.instrumentation.vertx.VertxDecorator.TRACER; + +import io.opentelemetry.trace.Span; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; +import lombok.extern.slf4j.Slf4j; + +/** This is used to wrap Vert.x Handlers to provide nice user-friendly SERVER span names */ +@Slf4j +public final class RoutingContextHandlerWrapper implements Handler { + private final Handler handler; + + public RoutingContextHandlerWrapper(final Handler handler) { + this.handler = handler; + } + + @Override + public void handle(RoutingContext context) { + try { + Span currentSpan = TRACER.getCurrentSpan(); + if (currentSpan.getContext().isValid()) { + // TODO should update only SERVER span using + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/465 + currentSpan.updateName(context.currentRoute().getPath()); + } + } catch (Exception ex) { + log.error("Failed to update server span name with vert.x route", ex); + } + handler.handle(context); + } +} diff --git a/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/VertxDecorator.java b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/VertxDecorator.java new file mode 100644 index 0000000000..605c870edd --- /dev/null +++ b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/VertxDecorator.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.opentelemetry.auto.instrumentation.vertx; + +import io.opentelemetry.OpenTelemetry; +import io.opentelemetry.auto.bootstrap.instrumentation.decorator.BaseDecorator; +import io.opentelemetry.trace.Tracer; + +public class VertxDecorator extends BaseDecorator { + public static final Tracer TRACER = + OpenTelemetry.getTracerProvider().get("io.opentelemetry.auto.vertx"); +} diff --git a/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/package-info.java b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/package-info.java new file mode 100644 index 0000000000..bfb8122911 --- /dev/null +++ b/instrumentation/vertx/src/main/java/io/opentelemetry/auto/instrumentation/vertx/package-info.java @@ -0,0 +1,14 @@ +/** + * The majority of monitoring needs of Vert.x application is covered by generic instrumentations. + * Such as those of netty or JDBC. + * + *

{@link io.opentelemetry.auto.instrumentation.vertx.RouteInstrumentation} wraps all Vert.x + * route handlers in order to update the name of the currently active SERVER span with the name of + * route. This is, arguably, a much more user-friendly name that defaults provided by HTTP server + * instrumentations. + * + *

{@link io.opentelemetry.auto.instrumentation.vertx.reactive.VertxRxInstrumentation} wraps + * {code AsyncResultSingle} classes from Vert.x RxJava library to ensure proper span context + * propagation in reactive Vert.x applications. + */ +package io.opentelemetry.auto.instrumentation.vertx; diff --git a/instrumentation/vertx-testing/src/test/groovy/client/VertxHttpClientTest.groovy b/instrumentation/vertx/src/test/groovy/client/VertxHttpClientTest.groovy similarity index 100% rename from instrumentation/vertx-testing/src/test/groovy/client/VertxHttpClientTest.groovy rename to instrumentation/vertx/src/test/groovy/client/VertxHttpClientTest.groovy diff --git a/instrumentation/vertx-testing/src/test/groovy/server/NettyServerTestInstrumentation.java b/instrumentation/vertx/src/test/groovy/server/NettyServerTestInstrumentation.java similarity index 100% rename from instrumentation/vertx-testing/src/test/groovy/server/NettyServerTestInstrumentation.java rename to instrumentation/vertx/src/test/groovy/server/NettyServerTestInstrumentation.java diff --git a/instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy b/instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy new file mode 100644 index 0000000000..ebb6824e86 --- /dev/null +++ b/instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package server + +import io.opentelemetry.auto.test.base.HttpServerTest +import io.vertx.core.AbstractVerticle +import io.vertx.core.DeploymentOptions +import io.vertx.core.Vertx +import io.vertx.core.VertxOptions +import io.vertx.core.json.JsonObject +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM + +class VertxHttpServerTest extends HttpServerTest { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port" + + @Override + Vertx startServer(int port) { + Vertx server = Vertx.vertx(new VertxOptions() + // Useful for debugging: + // .setBlockedThreadCheckInterval(Integer.MAX_VALUE) + .setClusterPort(port)) + final CompletableFuture future = new CompletableFuture<>() + server.deployVerticle(verticle().getName(), + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3)) { res -> + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()) + } + future.complete(null) + } + + future.get(30, TimeUnit.SECONDS) + return server + } + + protected Class verticle() { + return VertxWebServer + } + + @Override + void stopServer(Vertx server) { + server.close() + } + + @Override + boolean testExceptionBody() { + return false + } + + @Override + boolean testPathParam() { + return true + } + + @Override + boolean testNotFound() { + return false + } + + @Override + String expectedOperationName(String method, ServerEndpoint endpoint) { + return endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.getPath() + } + +} diff --git a/instrumentation/vertx/src/test/groovy/server/VertxWebServer.java b/instrumentation/vertx/src/test/groovy/server/VertxWebServer.java new file mode 100644 index 0000000000..d8cbc098b9 --- /dev/null +++ b/instrumentation/vertx/src/test/groovy/server/VertxWebServer.java @@ -0,0 +1,123 @@ +/* + * Copyright The OpenTelemetry Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package server; + +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.ERROR; +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.EXCEPTION; +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM; +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM; +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.REDIRECT; +import static io.opentelemetry.auto.test.base.HttpServerTest.ServerEndpoint.SUCCESS; + +import io.opentelemetry.auto.test.base.HttpServerTest; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.Future; +import io.vertx.core.Handler; +import io.vertx.ext.web.Router; +import io.vertx.ext.web.RoutingContext; + +public class VertxWebServer extends AbstractVerticle { + + @Override + public void start(final Future startFuture) { + final int port = config().getInteger(VertxHttpServerTest.CONFIG_HTTP_SERVER_PORT); + final Router router = Router.router(vertx); + + //noinspection Convert2Lambda + router + .route(SUCCESS.getPath()) + .handler( + // This is not a closure/lambda on purpose to verify how do we instrument actual Handler + // classes + new Handler() { + @Override + public void handle(RoutingContext ctx) { + HttpServerTest.controller( + SUCCESS, + () -> { + ctx.response().setStatusCode(SUCCESS.getStatus()).end(SUCCESS.getBody()); + return null; + }); + } + }); + router + .route(QUERY_PARAM.getPath()) + .handler( + ctx -> { + HttpServerTest.controller( + QUERY_PARAM, + () -> { + ctx.response() + .setStatusCode(QUERY_PARAM.getStatus()) + .end(ctx.request().query()); + return null; + }); + }); + router + .route(REDIRECT.getPath()) + .handler( + ctx -> { + HttpServerTest.controller( + REDIRECT, + () -> { + ctx.response() + .setStatusCode(REDIRECT.getStatus()) + .putHeader("location", REDIRECT.getBody()) + .end(); + return null; + }); + }); + router + .route(ERROR.getPath()) + .handler( + ctx -> { + HttpServerTest.controller( + ERROR, + () -> { + ctx.response().setStatusCode(ERROR.getStatus()).end(ERROR.getBody()); + return null; + }); + }); + router + .route(EXCEPTION.getPath()) + .handler( + ctx -> { + HttpServerTest.controller( + EXCEPTION, + () -> { + throw new Exception(EXCEPTION.getBody()); + }); + }); + router + .route("/path/:id/param") + .handler( + ctx -> { + HttpServerTest.controller( + PATH_PARAM, + () -> { + ctx.response() + .setStatusCode(PATH_PARAM.getStatus()) + .end(ctx.request().getParam("id")); + return null; + }); + }); + + vertx + .createHttpServer() + .requestHandler(router::accept) + .listen(port, it -> startFuture.complete()); + } +} diff --git a/instrumentation/vertx/vertx.gradle b/instrumentation/vertx/vertx.gradle new file mode 100644 index 0000000000..7e0492cc15 --- /dev/null +++ b/instrumentation/vertx/vertx.gradle @@ -0,0 +1,40 @@ +// Set properties before any plugins get loaded +ext { + minJavaVersionForTests = JavaVersion.VERSION_1_8 +} + +apply from: "${rootDir}/gradle/instrumentation.gradle" +apply plugin: 'org.unbroken-dome.test-sets' + +muzzle { + pass { + group = 'io.vertx' + module = 'vertx-web' + versions = "[3.0.0,)" + } +} + +testSets { + latestDepTest { + dirName = 'test' + } +} + +ext.vertxVersion = '3.0.0' + +dependencies { + compileOnly group: 'io.vertx', name: 'vertx-web', version: vertxVersion + + //We need both version as different versions of Vert.x use different versions of Netty + testCompile project(':instrumentation:netty:netty-4.0') + testCompile project(':instrumentation:netty:netty-4.1') + testCompile project(':instrumentation:jdbc') + testCompile project(':instrumentation:trace-annotation') + + testCompile group: 'io.vertx', name: 'vertx-web', version: vertxVersion + testCompile group: 'io.vertx', name: 'vertx-jdbc-client', version: vertxVersion + + // Vert.x 4.0 is incompatible with our tests. + latestDepTestCompile group: 'io.vertx', name: 'vertx-web', version: '3.+' + latestDepTestCompile group: 'io.vertx', name: 'vertx-web-client', version: '3.+' +} diff --git a/settings.gradle b/settings.gradle index fa1c6a97d6..ccdb1a8942 100644 --- a/settings.gradle +++ b/settings.gradle @@ -145,7 +145,8 @@ include ':instrumentation:spring-webflux-5.0' include ':instrumentation:spymemcached-2.12' include ':instrumentation:trace-annotation' include ':instrumentation:twilio-6.6' -include ':instrumentation:vertx-testing' +include ':instrumentation:vertx' +include ':instrumentation:vertx-reactive' include ':instrumentation-core:aws-sdk:aws-sdk-2.2-core' include ':instrumentation-core:spring' diff --git a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy index 7fa6920ce8..a460f0eb30 100644 --- a/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy +++ b/testing/src/main/groovy/io/opentelemetry/auto/test/base/HttpServerTest.groovy @@ -25,6 +25,7 @@ import io.opentelemetry.auto.test.utils.OkHttpUtils import io.opentelemetry.auto.test.utils.PortUtils import io.opentelemetry.sdk.trace.data.SpanData import io.opentelemetry.trace.Span +import java.util.concurrent.Callable import okhttp3.HttpUrl import okhttp3.OkHttpClient import okhttp3.Request @@ -96,7 +97,7 @@ abstract class HttpServerTest extends AgentTestRunner { abstract void stopServer(SERVER server) - String expectedOperationName(String method) { + String expectedOperationName(String method, ServerEndpoint endpoint) { return method != null ? "HTTP $method" : HttpServerDecorator.DEFAULT_SPAN_NAME } @@ -207,10 +208,10 @@ abstract class HttpServerTest extends AgentTestRunner { .method(method, body) } - static T controller(ServerEndpoint endpoint, Closure closure) { + static T controller(ServerEndpoint endpoint, Callable closure) { assert TEST_TRACER.getCurrentSpan().getContext().isValid(): "Controller should have a parent span." if (endpoint == NOT_FOUND) { - return closure() + return closure.call() } return runUnderTrace("controller", closure) } @@ -453,7 +454,7 @@ abstract class HttpServerTest extends AgentTestRunner { // parent span must be cast otherwise it breaks debugging classloading (junit loads it early) void serverSpan(TraceAssert trace, int index, String traceID = null, String parentID = null, String method = "GET", ServerEndpoint endpoint = SUCCESS) { trace.span(index) { - operationName expectedOperationName(method) + operationName expectedOperationName(method, endpoint) spanKind Span.Kind.SERVER // can't use static import because of SERVER type parameter errored endpoint.errored if (parentID != null) {