From e92ecc02bc83dfc79318a61bc5ae70398ad4daa1 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 4 Aug 2021 16:21:36 +0900 Subject: [PATCH] Add library instrumentation for Ratpack server (#3749) * Add Ratpack server library instrumentation * Finish * Back to 1.4 * Drift * Cocaine * Update instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracingBuilder.java Co-authored-by: Mateusz Rzeszutek * Cleanup Co-authored-by: Mateusz Rzeszutek --- .../ratpack-1.4/javaagent/build.gradle.kts | 2 + .../src/test/groovy/RatpackOtherTest.groovy | 116 ----------- .../server/RatpackAsyncHttpServerTest.groovy | 112 +---------- .../server/RatpackForkedHttpServerTest.groovy | 168 +--------------- .../server/RatpackHttpServerTest.groovy | 131 +------------ .../groovy/server/RatpackRoutesTest.groovy | 21 ++ .../ratpack-1.4/library/build.gradle.kts | 17 ++ .../ratpack/OpenTelemetryExecInterceptor.java | 57 ++++++ .../OpenTelemetryFallbackErrorHandler.java | 85 ++++++++ .../OpenTelemetryServerErrorHandler.java | 35 ++++ .../ratpack/OpenTelemetryServerHandler.java | 76 ++++++++ .../ratpack/RatpackGetter.java | 29 +++ .../RatpackHttpAttributesExtractor.java | 130 +++++++++++++ .../ratpack/RatpackTracing.java | 54 ++++++ .../ratpack/RatpackTracingBuilder.java | 60 ++++++ .../RatpackNetAttributesExtractor.java | 37 ++++ .../server/RatpackAsyncHttpServerTest.groovy | 35 ++++ .../server/RatpackForkedHttpServerTest.groovy | 35 ++++ .../server/RatpackHttpServerTest.groovy | 36 ++++ .../ratpack/server/RatpackRoutesTest.groovy | 36 ++++ .../ratpack-1.4/testing/build.gradle.kts | 13 ++ .../AbstractRatpackAsyncHttpServerTest.groovy | 119 ++++++++++++ ...AbstractRatpackForkedHttpServerTest.groovy | 183 ++++++++++++++++++ .../AbstractRatpackHttpServerTest.groovy | 146 ++++++++++++++ .../server/AbstractRatpackRoutesTest.groovy | 171 ++++++++++++++++ settings.gradle.kts | 2 + .../test/base/HttpServerTest.groovy | 31 +++ 27 files changed, 1424 insertions(+), 513 deletions(-) delete mode 100644 instrumentation/ratpack-1.4/javaagent/src/test/groovy/RatpackOtherTest.groovy create mode 100644 instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackRoutesTest.groovy create mode 100644 instrumentation/ratpack-1.4/library/build.gradle.kts create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryExecInterceptor.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryFallbackErrorHandler.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerErrorHandler.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerHandler.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackGetter.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracing.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracingBuilder.java create mode 100644 instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/internal/RatpackNetAttributesExtractor.java create mode 100644 instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackAsyncHttpServerTest.groovy create mode 100644 instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackForkedHttpServerTest.groovy create mode 100644 instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackHttpServerTest.groovy create mode 100644 instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackRoutesTest.groovy create mode 100644 instrumentation/ratpack-1.4/testing/build.gradle.kts create mode 100644 instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackAsyncHttpServerTest.groovy create mode 100644 instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackForkedHttpServerTest.groovy create mode 100644 instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackHttpServerTest.groovy create mode 100644 instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackRoutesTest.groovy diff --git a/instrumentation/ratpack-1.4/javaagent/build.gradle.kts b/instrumentation/ratpack-1.4/javaagent/build.gradle.kts index 2bf1aac58e..806e28de42 100644 --- a/instrumentation/ratpack-1.4/javaagent/build.gradle.kts +++ b/instrumentation/ratpack-1.4/javaagent/build.gradle.kts @@ -15,6 +15,8 @@ dependencies { implementation(project(":instrumentation:netty:netty-4.1:javaagent")) + testImplementation(project(":instrumentation:ratpack-1.4:testing")) + testLibrary("io.ratpack:ratpack-test:1.4.0") if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) { diff --git a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/RatpackOtherTest.groovy b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/RatpackOtherTest.groovy deleted file mode 100644 index 7925b40611..0000000000 --- a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/RatpackOtherTest.groovy +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.api.trace.SpanKind.SERVER - -import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.trace.attributes.SemanticAttributes -import io.opentelemetry.testing.internal.armeria.client.WebClient -import ratpack.path.PathBinding -import ratpack.server.RatpackServer -import spock.lang.Shared - -class RatpackOtherTest extends AgentInstrumentationSpecification { - - @Shared - RatpackServer app = RatpackServer.start { - it.serverConfig { - it.port(PortUtils.findOpenPort()) - it.address(InetAddress.getByName("localhost")) - } - it.handlers { - it.prefix("a") { - it.all {context -> - context.render(context.get(PathBinding).description) - } - } - it.prefix("b/::\\d+") { - it.all {context -> - context.render(context.get(PathBinding).description) - } - } - it.prefix("c/:val?") { - it.all {context -> - context.render(context.get(PathBinding).description) - } - } - it.prefix("d/:val") { - it.all {context -> - context.render(context.get(PathBinding).description) - } - } - it.prefix("e/:val?:\\d+") { - it.all {context -> - context.render(context.get(PathBinding).description) - } - } - it.prefix("f/:val:\\d+") { - it.all {context -> - context.render(context.get(PathBinding).description) - } - } - } - } - - // Force HTTP/1 with h1c to prevent tracing of upgrade request. - @Shared - WebClient client = WebClient.of("h1c://localhost:${app.bindPort}") - - def cleanupSpec() { - app.stop() - } - - def "test bindings for #path"() { - when: - def resp = client.get(path).aggregate().join() - - then: - resp.status().code() == 200 - resp.contentUtf8() == route - - assertTraces(1) { - trace(0, 2) { - span(0) { - name "/$route" - kind SERVER - hasNoParent() - attributes { - "${SemanticAttributes.NET_PEER_IP.key}" "127.0.0.1" - "${SemanticAttributes.NET_PEER_PORT.key}" Long - "${SemanticAttributes.HTTP_URL.key}" "http://localhost:${app.bindPort}/${path}" - "${SemanticAttributes.HTTP_METHOD.key}" "GET" - "${SemanticAttributes.HTTP_STATUS_CODE.key}" 200 - "${SemanticAttributes.HTTP_FLAVOR.key}" "1.1" - "${SemanticAttributes.HTTP_USER_AGENT.key}" String - "${SemanticAttributes.HTTP_CLIENT_IP.key}" "127.0.0.1" - } - } - span(1) { - name "/$route" - kind INTERNAL - childOf span(0) - attributes { - } - } - } - } - - where: - path | route - "a" | "a" - "b/123" | "b/::\\d+" - "c" | "c/:val?" - "c/123" | "c/:val?" - "c/foo" | "c/:val?" - "d/123" | "d/:val" - "d/foo" | "d/:val" - "e" | "e/:val?:\\d+" - "e/123" | "e/:val?:\\d+" - "e/foo" | "e/:val?:\\d+" - "f/123" | "f/:val:\\d+" - } -} diff --git a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackAsyncHttpServerTest.groovy b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackAsyncHttpServerTest.groovy index 017249e42b..093601cd8a 100644 --- a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackAsyncHttpServerTest.groovy +++ b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackAsyncHttpServerTest.groovy @@ -5,114 +5,12 @@ package server -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS - -import ratpack.error.ServerErrorHandler -import ratpack.exec.Promise -import ratpack.server.RatpackServer - -class RatpackAsyncHttpServerTest extends RatpackHttpServerTest { +import io.opentelemetry.instrumentation.ratpack.server.AbstractRatpackAsyncHttpServerTest +import io.opentelemetry.instrumentation.test.AgentTestTrait +import ratpack.server.RatpackServerSpec +class RatpackAsyncHttpServerTest extends AbstractRatpackAsyncHttpServerTest implements AgentTestTrait { @Override - RatpackServer startServer(int bindPort) { - def ratpack = RatpackServer.start { - it.serverConfig { - it.port(bindPort) - it.address(InetAddress.getByName("localhost")) - } - it.handlers { - it.register { - it.add(ServerErrorHandler, new TestErrorHandler()) - } - it.prefix(SUCCESS.rawPath()) { - it.all {context -> - Promise.sync { - SUCCESS - } then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(endpoint.body) - } - } - } - } - it.prefix(INDEXED_CHILD.rawPath()) { - it.all {context -> - Promise.sync { - INDEXED_CHILD - } then { - controller(INDEXED_CHILD) { - INDEXED_CHILD.collectSpanAttributes { context.request.queryParams.get(it) } - context.response.status(INDEXED_CHILD.status).send() - } - } - } - } - it.prefix(QUERY_PARAM.rawPath()) { - it.all { context -> - Promise.sync { - QUERY_PARAM - } then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(context.request.query) - } - } - } - } - it.prefix(REDIRECT.rawPath()) { - it.all {context -> - Promise.sync { - REDIRECT - } then { endpoint -> - controller(endpoint) { - context.redirect(endpoint.body) - } - } - } - } - it.prefix(ERROR.rawPath()) { - it.all {context -> - Promise.sync { - ERROR - } then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(endpoint.body) - } - } - } - } - it.prefix(EXCEPTION.rawPath()) { - it.all { - Promise.sync { - EXCEPTION - } then { endpoint -> - controller(endpoint) { - throw new Exception(endpoint.body) - } - } - } - } - it.prefix("path/:id/param") { - it.all {context -> - Promise.sync { - PATH_PARAM - } then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(context.pathTokens.id) - } - } - } - } - } - } - - assert ratpack.bindPort == bindPort - assert ratpack.bindHost == 'localhost' - return ratpack + void configure(RatpackServerSpec serverSpec) { } } diff --git a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackForkedHttpServerTest.groovy b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackForkedHttpServerTest.groovy index 86fd1d2588..0cf2ae9635 100644 --- a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackForkedHttpServerTest.groovy +++ b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackForkedHttpServerTest.groovy @@ -5,170 +5,12 @@ package server -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest -import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse -import io.opentelemetry.testing.internal.armeria.common.HttpMethod -import ratpack.error.ServerErrorHandler -import ratpack.exec.Execution -import ratpack.exec.Promise -import ratpack.exec.Result -import ratpack.exec.util.ParallelBatch -import ratpack.server.RatpackServer - -class RatpackForkedHttpServerTest extends RatpackHttpServerTest { +import io.opentelemetry.instrumentation.ratpack.server.AbstractRatpackForkedHttpServerTest +import io.opentelemetry.instrumentation.test.AgentTestTrait +import ratpack.server.RatpackServerSpec +class RatpackForkedHttpServerTest extends AbstractRatpackForkedHttpServerTest implements AgentTestTrait { @Override - RatpackServer startServer(int bindPort) { - - def ratpack = RatpackServer.start { - it.serverConfig { - it.port(bindPort) - it.address(InetAddress.getByName("localhost")) - } - it.handlers { - it.register { - it.add(ServerErrorHandler, new TestErrorHandler()) - } - it.prefix(SUCCESS.rawPath()) { - it.all {context -> - Promise.sync { - SUCCESS - }.fork().then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(endpoint.body) - } - } - } - } - it.prefix(INDEXED_CHILD.rawPath()) { - it.all {context -> - Promise.sync { - INDEXED_CHILD - }.fork().then { - controller(INDEXED_CHILD) { - INDEXED_CHILD.collectSpanAttributes { context.request.queryParams.get(it) } - context.response.status(INDEXED_CHILD.status).send() - } - } - } - } - it.prefix(QUERY_PARAM.rawPath()) { - it.all { context -> - Promise.sync { - QUERY_PARAM - }.fork().then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(context.request.query) - } - } - } - } - it.prefix(REDIRECT.rawPath()) { - it.all {context -> - Promise.sync { - REDIRECT - }.fork().then { endpoint -> - controller(endpoint) { - context.redirect(endpoint.body) - } - } - } - } - it.prefix(ERROR.rawPath()) { - it.all {context -> - Promise.sync { - ERROR - }.fork().then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(endpoint.body) - } - } - } - } - it.prefix(EXCEPTION.rawPath()) { - it.all { - Promise.sync { - EXCEPTION - }.fork().then { endpoint -> - controller(endpoint) { - throw new Exception(endpoint.body) - } - } - } - } - it.prefix("path/:id/param") { - it.all {context -> - Promise.sync { - PATH_PARAM - }.fork().then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(context.pathTokens.id) - } - } - } - } - it.prefix("fork_and_yieldAll") { - it.all {context -> - def promise = Promise.async { upstream -> - Execution.fork().start({ - upstream.accept(Result.success(SUCCESS)) - }) - } - ParallelBatch.of(promise).yieldAll().flatMap { list -> - Promise.sync { list.get(0).value } - } then { endpoint -> - controller(endpoint) { - context.response.status(endpoint.status).send(endpoint.body) - } - } - } - } - } - } - - assert ratpack.bindPort == bindPort - assert ratpack.bindHost == 'localhost' - return ratpack - } - - def "test fork and yieldAll"() { - setup: - def url = address.resolve("fork_and_yieldAll").toString() - url = url.replace("http://", "h1c://") - def request = AggregatedHttpRequest.of(HttpMethod.GET, url) - AggregatedHttpResponse response = client.execute(request).aggregate().join() - - expect: - response.status().code() == SUCCESS.status - response.contentUtf8() == SUCCESS.body - - assertTraces(1) { - trace(0, 3) { - span(0) { - name "/fork_and_yieldAll" - kind SpanKind.SERVER - hasNoParent() - } - span(1) { - name "/fork_and_yieldAll" - kind SpanKind.INTERNAL - childOf span(0) - } - span(2) { - name "controller" - kind SpanKind.INTERNAL - childOf span(1) - } - } - } + void configure(RatpackServerSpec serverSpec) { } } diff --git a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackHttpServerTest.groovy b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackHttpServerTest.groovy index 1cc774acec..bef9fabf2e 100644 --- a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackHttpServerTest.groovy +++ b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackHttpServerTest.groovy @@ -5,136 +5,13 @@ package server -import static io.opentelemetry.api.trace.SpanKind.INTERNAL -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT -import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS -import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.instrumentation.ratpack.server.AbstractRatpackHttpServerTest import io.opentelemetry.instrumentation.test.AgentTestTrait -import io.opentelemetry.instrumentation.test.asserts.TraceAssert -import io.opentelemetry.instrumentation.test.base.HttpServerTest -import io.opentelemetry.sdk.trace.data.SpanData -import ratpack.error.ServerErrorHandler -import ratpack.handling.Context -import ratpack.server.RatpackServer - -class RatpackHttpServerTest extends HttpServerTest implements AgentTestTrait { +import ratpack.server.RatpackServerSpec +class RatpackHttpServerTest extends AbstractRatpackHttpServerTest implements AgentTestTrait { @Override - RatpackServer startServer(int bindPort) { - def ratpack = RatpackServer.start { - it.serverConfig { - it.port(bindPort) - it.address(InetAddress.getByName("localhost")) - } - it.handlers { - it.register { - it.add(ServerErrorHandler, new TestErrorHandler()) - } - it.prefix(SUCCESS.rawPath()) { - it.all {context -> - controller(SUCCESS) { - context.response.status(SUCCESS.status).send(SUCCESS.body) - } - } - } - it.prefix(INDEXED_CHILD.rawPath()) { - it.all {context -> - controller(INDEXED_CHILD) { - INDEXED_CHILD.collectSpanAttributes { context.request.queryParams.get(it) } - context.response.status(INDEXED_CHILD.status).send() - } - } - } - it.prefix(QUERY_PARAM.rawPath()) { - it.all { context -> - controller(QUERY_PARAM) { - context.response.status(QUERY_PARAM.status).send(context.request.query) - } - } - } - it.prefix(REDIRECT.rawPath()) { - it.all {context -> - controller(REDIRECT) { - context.redirect(REDIRECT.body) - } - } - } - it.prefix(ERROR.rawPath()) { - it.all {context -> - controller(ERROR) { - context.response.status(ERROR.status).send(ERROR.body) - } - } - } - it.prefix(EXCEPTION.rawPath()) { - it.all { - controller(EXCEPTION) { - throw new Exception(EXCEPTION.body) - } - } - } - it.prefix("path/:id/param") { - it.all {context -> - controller(PATH_PARAM) { - context.response.status(PATH_PARAM.status).send(context.pathTokens.id) - } - } - } - } - } - - assert ratpack.bindPort == bindPort - return ratpack - } - - static class TestErrorHandler implements ServerErrorHandler { - @Override - void error(Context context, Throwable throwable) throws Exception { - context.response.status(500).send(throwable.message) - } - } - - @Override - void stopServer(RatpackServer server) { - server.stop() - } - - @Override - boolean hasHandlerSpan(ServerEndpoint endpoint) { - true - } - - @Override - boolean testPathParam() { - true - } - - @Override - boolean testConcurrency() { - true - } - - @Override - void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { - trace.span(index) { - name endpoint.status == 404 ? "/" : endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.path - kind INTERNAL - childOf((SpanData) parent) - if (endpoint == EXCEPTION) { - status StatusCode.ERROR - errorEvent(Exception, EXCEPTION.body) - } - } - } - - @Override - String expectedServerSpanName(ServerEndpoint endpoint) { - return endpoint.status == 404 ? "/" : endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.path + void configure(RatpackServerSpec serverSpec) { } } diff --git a/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackRoutesTest.groovy b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackRoutesTest.groovy new file mode 100644 index 0000000000..e52bd4f93c --- /dev/null +++ b/instrumentation/ratpack-1.4/javaagent/src/test/groovy/server/RatpackRoutesTest.groovy @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package server + +import io.opentelemetry.instrumentation.ratpack.server.AbstractRatpackRoutesTest +import io.opentelemetry.instrumentation.test.AgentTestTrait +import ratpack.server.RatpackServerSpec + +class RatpackRoutesTest extends AbstractRatpackRoutesTest implements AgentTestTrait { + @Override + void configure(RatpackServerSpec serverSpec) { + } + + @Override + boolean hasHandlerSpan() { + return true + } +} diff --git a/instrumentation/ratpack-1.4/library/build.gradle.kts b/instrumentation/ratpack-1.4/library/build.gradle.kts new file mode 100644 index 0000000000..47690306fb --- /dev/null +++ b/instrumentation/ratpack-1.4/library/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("otel.library-instrumentation") + id("otel.nullaway-conventions") +} + +dependencies { + library("io.ratpack:ratpack-core:1.4.0") + + testImplementation(project(":instrumentation:ratpack-1.4:testing")) + + if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) { + testImplementation("com.sun.activation:jakarta.activation:1.2.2") + } +} + +// Requires old Guava. Can't use enforcedPlatform since predates BOM +configurations.testRuntimeClasspath.resolutionStrategy.force("com.google.guava:guava:19.0") diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryExecInterceptor.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryExecInterceptor.java new file mode 100644 index 0000000000..9838f46e9d --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryExecInterceptor.java @@ -0,0 +1,57 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import ratpack.exec.ExecInterceptor; +import ratpack.exec.Execution; +import ratpack.func.Block; + +final class OpenTelemetryExecInterceptor implements ExecInterceptor { + + static final ExecInterceptor INSTANCE = new OpenTelemetryExecInterceptor(); + + @Override + public void intercept(Execution execution, ExecType type, Block continuation) throws Exception { + Context otelCtx = execution.maybeGet(Context.class).orElse(null); + if (otelCtx == null) { + // There is no OTel Context yet meaning this is the beginning of an Execution, before running + // the handler chain, which includes OpenTelemetryServerHandler. Run the chain. + executeHandlerChainAndThenCloseScope(execution, continuation); + } else { + // Execution already has a context, this is an asynchronous resumption and we need to make + // the context current. + executeContinuationWithContext(continuation, otelCtx); + } + } + + private static void executeHandlerChainAndThenCloseScope(Execution execution, Block continuation) + throws Exception { + try { + continuation.execute(); + } finally { + // The handler chain, including OpenTelemetryServerHandler, has finished and we are about + // to unbind the Execution from its thread. As such, we need to make sure to close the + // thread-local Scope that was created by OpenTelemetryServerHandler. The Execution still + // has an OTel Context, so if it happens to resume because the user used an asynchronous + // flow, the interceptor will run again and instead make the context current by + // calling executeContinuationWithContext. + Scope scope = execution.maybeGet(Scope.class).orElse(null); + if (scope != null) { + scope.close(); + execution.remove(Scope.class); + } + } + } + + private static void executeContinuationWithContext(Block continuation, Context otelCtx) + throws Exception { + try (Scope ignored = otelCtx.makeCurrent()) { + continuation.execute(); + } + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryFallbackErrorHandler.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryFallbackErrorHandler.java new file mode 100644 index 0000000000..80d5bedc17 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryFallbackErrorHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +// Includes work from: +/* + * Copyright 2014 the original author or 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.instrumentation.ratpack; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ratpack.error.ClientErrorHandler; +import ratpack.error.ServerErrorHandler; +import ratpack.handling.Context; + +// Copied from +// https://github.com/ratpack/ratpack/blob/master/ratpack-core/src/main/java/ratpack/core/error/internal/DefaultProductionErrorHandler.java +// since it is internal and has had breaking changes. +final class OpenTelemetryFallbackErrorHandler implements ClientErrorHandler, ServerErrorHandler { + + static final OpenTelemetryFallbackErrorHandler INSTANCE = new OpenTelemetryFallbackErrorHandler(); + + private static final Logger logger = + LoggerFactory.getLogger(OpenTelemetryFallbackErrorHandler.class); + + OpenTelemetryFallbackErrorHandler() {} + + @Override + public void error(Context context, int statusCode) { + if (logger.isWarnEnabled()) { + WarnOnce.execute(); + logger.warn(getMsg(ClientErrorHandler.class, "client error", context)); + } + context.getResponse().status(statusCode).send(); + } + + @Override + public void error(Context context, Throwable throwable) { + if (logger.isWarnEnabled()) { + WarnOnce.execute(); + logger.warn(getMsg(ServerErrorHandler.class, "server error", context) + "\n", throwable); + } + context.getResponse().status(500).send(); + } + + private static String getMsg(Class handlerClass, String type, Context context) { + return "Default production error handler used to render " + + type + + ", please add a " + + handlerClass.getName() + + " instance to your application " + + "(method: " + + context.getRequest().getMethod() + + ", uri: " + + context.getRequest().getRawUri() + + ")"; + } + + private static class WarnOnce { + static { + logger.warn( + "Logging error using OpenTelemetryFallbackErrorHandler. This indicates " + + "OpenTelemetry could not find a registered error handler which is not expected. " + + "Log messages will only be outputed to console."); + } + + // Warned once in static initializer, this is just to trigger classload. + static void execute() {} + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerErrorHandler.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerErrorHandler.java new file mode 100644 index 0000000000..21d2500cb5 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerErrorHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import ratpack.error.ServerErrorHandler; +import ratpack.handling.Context; + +final class OpenTelemetryServerErrorHandler implements ServerErrorHandler { + + static final ServerErrorHandler INSTANCE = new OpenTelemetryServerErrorHandler(); + + private OpenTelemetryServerErrorHandler() {} + + @Override + public void error(Context context, Throwable throwable) throws Exception { + context + .getExecution() + .add( + OpenTelemetryServerHandler.ErrorHolder.class, + new OpenTelemetryServerHandler.ErrorHolder(throwable)); + + ServerErrorHandler delegate = OpenTelemetryFallbackErrorHandler.INSTANCE; + for (ServerErrorHandler errorHandler : context.getAll(ServerErrorHandler.class)) { + if (errorHandler != INSTANCE) { + delegate = errorHandler; + break; + } + } + + delegate.error(context, throwable); + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerHandler.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerHandler.java new file mode 100644 index 0000000000..0ba650aa28 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/OpenTelemetryServerHandler.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import ratpack.error.ServerErrorHandler; +import ratpack.handling.Context; +import ratpack.handling.Handler; +import ratpack.http.Request; +import ratpack.http.Response; + +final class OpenTelemetryServerHandler implements Handler { + + private final Instrumenter instrumenter; + + OpenTelemetryServerHandler(Instrumenter instrumenter) { + this.instrumenter = instrumenter; + } + + @Override + public void handle(Context context) { + Request request = context.getRequest(); + + io.opentelemetry.context.Context parentOtelCtx = io.opentelemetry.context.Context.current(); + if (!instrumenter.shouldStart(parentOtelCtx, request)) { + context.next(); + return; + } + + io.opentelemetry.context.Context otelCtx = instrumenter.start(parentOtelCtx, request); + context.getExecution().add(io.opentelemetry.context.Context.class, otelCtx); + context.onClose( + outcome -> { + // Route not available in beginning of request so handle it manually here. + String route = '/' + context.getPathBinding().getDescription(); + Span span = Span.fromContext(otelCtx); + span.updateName(route); + span.setAttribute(SemanticAttributes.HTTP_ROUTE, route); + + Throwable error = + context.getExecution().maybeGet(ErrorHolder.class).map(ErrorHolder::get).orElse(null); + + instrumenter.end(otelCtx, outcome.getRequest(), context.getResponse(), error); + }); + + // An execution continues to execute synchronously until it is unbound from a thread. We need + // to make the context current here to make it available to the next handler (possibly user + // code) but close the scope at the end of the ExecInterceptor, which corresponds to when the + // execution is about to be unbound from the thread. + Scope scope = otelCtx.makeCurrent(); + context.getExecution().add(Scope.class, scope); + + // A user may have defined their own ServerErrorHandler, so we add ours to the Execution which + // has higher precedence. + context.getExecution().add(ServerErrorHandler.class, OpenTelemetryServerErrorHandler.INSTANCE); + context.next(); + } + + static final class ErrorHolder { + private final Throwable throwable; + + ErrorHolder(Throwable throwable) { + this.throwable = throwable; + } + + Throwable get() { + return throwable; + } + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackGetter.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackGetter.java new file mode 100644 index 0000000000..38207808ba --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackGetter.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import io.opentelemetry.context.propagation.TextMapGetter; +import javax.annotation.Nullable; +import ratpack.http.Request; + +final class RatpackGetter implements TextMapGetter { + + RatpackGetter() {} + + @Override + public Iterable keys(Request request) { + return request.getHeaders().getNames(); + } + + @Nullable + @Override + public String get(@Nullable Request request, String key) { + if (request == null) { + return null; + } + return request.getHeaders().get(key); + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java new file mode 100644 index 0000000000..083039e638 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackHttpAttributesExtractor.java @@ -0,0 +1,130 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpAttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.checkerframework.checker.nullness.qual.Nullable; +import ratpack.handling.Context; +import ratpack.http.Request; +import ratpack.http.Response; +import ratpack.server.PublicAddress; + +final class RatpackHttpAttributesExtractor extends HttpAttributesExtractor { + @Override + protected String method(Request request) { + return request.getMethod().getName(); + } + + @Override + @Nullable + protected String url(Request request) { + // TODO(anuraaga): We should probably just not fill this + // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/3700 + Context ratpackContext = request.get(Context.class); + if (ratpackContext == null) { + return null; + } + PublicAddress publicAddress = ratpackContext.get(PublicAddress.class); + if (publicAddress == null) { + return null; + } + return publicAddress + .builder() + .path(request.getPath()) + .params(request.getQueryParams()) + .build() + .toString(); + } + + @Override + protected String target(Request request) { + // Uri is the path + query string, not a full URL + return request.getUri(); + } + + @Override + @Nullable + protected String host(Request request) { + return null; + } + + @Override + @Nullable + protected String route(Request request) { + // Ratpack route not available at the beginning of request. + return null; + } + + @Override + @Nullable + protected String scheme(Request request) { + return null; + } + + @Override + @Nullable + protected String userAgent(Request request) { + return request.getHeaders().get("user-agent"); + } + + @Override + @Nullable + protected Long requestContentLength(Request request, @Nullable Response response) { + return null; + } + + @Override + @Nullable + protected Long requestContentLengthUncompressed(Request request, @Nullable Response response) { + return null; + } + + @Override + @Nullable + protected String flavor(Request request, @Nullable Response response) { + switch (request.getProtocol()) { + case "HTTP/1.0": + return SemanticAttributes.HttpFlavorValues.HTTP_1_0; + case "HTTP/1.1": + return SemanticAttributes.HttpFlavorValues.HTTP_1_1; + case "HTTP/2.0": + return SemanticAttributes.HttpFlavorValues.HTTP_2_0; + default: + // fall through + } + return null; + } + + @Override + @Nullable + protected String serverName(Request request, @Nullable Response response) { + return null; + } + + @Override + @Nullable + protected String clientIp(Request request, @Nullable Response response) { + return null; + } + + @Override + protected Integer statusCode(Request request, Response response) { + return response.getStatus().getCode(); + } + + @Override + @Nullable + protected Long responseContentLength(Request request, Response response) { + return null; + } + + @Override + @Nullable + protected Long responseContentLengthUncompressed(Request request, Response response) { + return null; + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracing.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracing.java new file mode 100644 index 0000000000..c34f3be1c5 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracing.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import ratpack.handling.HandlerDecorator; +import ratpack.http.Request; +import ratpack.http.Response; +import ratpack.registry.RegistrySpec; + +/** + * Entrypoint for tracing Ratpack servers. To apply OpenTelemetry to a server, configure the {@link + * RegistrySpec} using {@link #configureServerRegistry(RegistrySpec)}. + * + *
{@code
+ * RatpackTracing tracing = RatpackTracing.create(OpenTelemetrySdk.builder()
+ *   ...
+ *   .build());
+ * RatpackServer.start(server -> {
+ *   server.registryOf(tracing::configureServerRegistry);
+ *   server.handlers(chain -> ...);
+ * });
+ * }
+ */ +public final class RatpackTracing { + + /** Returns a new {@link RatpackTracing} configured with the given {@link OpenTelemetry}. */ + public static RatpackTracing create(OpenTelemetry openTelemetry) { + return newBuilder(openTelemetry).build(); + } + + /** + * Returns a new {@link RatpackTracingBuilder} configured with the given {@link OpenTelemetry}. + */ + public static RatpackTracingBuilder newBuilder(OpenTelemetry openTelemetry) { + return new RatpackTracingBuilder(openTelemetry); + } + + private final OpenTelemetryServerHandler serverHandler; + + RatpackTracing(Instrumenter serverInstrumenter) { + serverHandler = new OpenTelemetryServerHandler(serverInstrumenter); + } + + /** Configures the {@link RegistrySpec} with OpenTelemetry. */ + public void configureServerRegistry(RegistrySpec registry) { + registry.add(HandlerDecorator.prepend(serverHandler)); + registry.add(OpenTelemetryExecInterceptor.INSTANCE); + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracingBuilder.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracingBuilder.java new file mode 100644 index 0000000000..3b789d8cac --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/RatpackTracingBuilder.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanNameExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.http.HttpSpanStatusExtractor; +import io.opentelemetry.instrumentation.ratpack.internal.RatpackNetAttributesExtractor; +import java.util.ArrayList; +import java.util.List; +import ratpack.http.Request; +import ratpack.http.Response; + +/** A builder for {@link RatpackTracing}. */ +public final class RatpackTracingBuilder { + + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.ratpack-1.4"; + + private final OpenTelemetry openTelemetry; + + private final List> additionalExtractors = + new ArrayList<>(); + + RatpackTracingBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + /** + * Adds an additional {@link AttributesExtractor} to invoke to set attributes to instrumented + * items. The {@link AttributesExtractor} will be executed after all default extractors. + */ + public RatpackTracingBuilder addAttributeExtractor( + AttributesExtractor attributesExtractor) { + additionalExtractors.add(attributesExtractor); + return this; + } + + /** Returns a new {@link RatpackTracing} with the configuration of this builder. */ + public RatpackTracing build() { + RatpackNetAttributesExtractor netAttributes = new RatpackNetAttributesExtractor(); + RatpackHttpAttributesExtractor httpAttributes = new RatpackHttpAttributesExtractor(); + + InstrumenterBuilder builder = + Instrumenter.newBuilder( + openTelemetry, INSTRUMENTATION_NAME, HttpSpanNameExtractor.create(httpAttributes)); + + builder.setSpanStatusExtractor(HttpSpanStatusExtractor.create(httpAttributes)); + builder.addAttributesExtractor(netAttributes); + builder.addAttributesExtractor(httpAttributes); + builder.addAttributesExtractors(additionalExtractors); + + return new RatpackTracing(builder.newServerInstrumenter(new RatpackGetter())); + } +} diff --git a/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/internal/RatpackNetAttributesExtractor.java b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/internal/RatpackNetAttributesExtractor.java new file mode 100644 index 0000000000..f43f19e011 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/main/java/io/opentelemetry/instrumentation/ratpack/internal/RatpackNetAttributesExtractor.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.internal; + +import io.opentelemetry.instrumentation.api.instrumenter.net.NetAttributesExtractor; +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes; +import org.checkerframework.checker.nullness.qual.Nullable; +import ratpack.http.Request; +import ratpack.http.Response; + +public final class RatpackNetAttributesExtractor extends NetAttributesExtractor { + @Override + @Nullable + public String transport(Request request) { + return SemanticAttributes.NetTransportValues.IP_TCP; + } + + @Override + @Nullable + public String peerName(Request request, @Nullable Response response) { + return null; + } + + @Override + public Integer peerPort(Request request, @Nullable Response response) { + return request.getRemoteAddress().getPort(); + } + + @Override + @Nullable + public String peerIp(Request request, @Nullable Response response) { + return null; + } +} diff --git a/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackAsyncHttpServerTest.groovy b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackAsyncHttpServerTest.groovy new file mode 100644 index 0000000000..432b526386 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackAsyncHttpServerTest.groovy @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.ratpack.RatpackTracing +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import ratpack.server.RatpackServerSpec + +class RatpackAsyncHttpServerTest extends AbstractRatpackAsyncHttpServerTest implements LibraryTestTrait { + @Override + void configure(RatpackServerSpec serverSpec) { + RatpackTracing tracing = RatpackTracing.create(openTelemetry) + serverSpec.registryOf { + tracing.configureServerRegistry(it) + } + } + @Override + boolean hasHandlerSpan(ServerEndpoint endpoint) { + false + } + + @Override + List> extraAttributes() { + return [ + SemanticAttributes.HTTP_ROUTE, + SemanticAttributes.HTTP_TARGET, + SemanticAttributes.NET_TRANSPORT, + ] + } +} diff --git a/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackForkedHttpServerTest.groovy b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackForkedHttpServerTest.groovy new file mode 100644 index 0000000000..1ad3c2ebb7 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackForkedHttpServerTest.groovy @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.ratpack.RatpackTracing +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import ratpack.server.RatpackServerSpec + +class RatpackForkedHttpServerTest extends AbstractRatpackForkedHttpServerTest implements LibraryTestTrait { + @Override + void configure(RatpackServerSpec serverSpec) { + RatpackTracing tracing = RatpackTracing.create(openTelemetry) + serverSpec.registryOf { + tracing.configureServerRegistry(it) + } + } + @Override + boolean hasHandlerSpan(ServerEndpoint endpoint) { + false + } + + @Override + List> extraAttributes() { + return [ + SemanticAttributes.HTTP_ROUTE, + SemanticAttributes.HTTP_TARGET, + SemanticAttributes.NET_TRANSPORT, + ] + } +} diff --git a/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackHttpServerTest.groovy b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackHttpServerTest.groovy new file mode 100644 index 0000000000..19190f6fd7 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackHttpServerTest.groovy @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.ratpack.RatpackTracing +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import ratpack.server.RatpackServerSpec + +class RatpackHttpServerTest extends AbstractRatpackHttpServerTest implements LibraryTestTrait { + @Override + void configure(RatpackServerSpec serverSpec) { + RatpackTracing tracing = RatpackTracing.create(openTelemetry) + serverSpec.registryOf { + tracing.configureServerRegistry(it) + } + } + + @Override + boolean hasHandlerSpan(ServerEndpoint endpoint) { + false + } + + @Override + List> extraAttributes() { + return [ + SemanticAttributes.HTTP_ROUTE, + SemanticAttributes.HTTP_TARGET, + SemanticAttributes.NET_TRANSPORT, + ] + } +} diff --git a/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackRoutesTest.groovy b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackRoutesTest.groovy new file mode 100644 index 0000000000..14a31dffa0 --- /dev/null +++ b/instrumentation/ratpack-1.4/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/server/RatpackRoutesTest.groovy @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.ratpack.RatpackTracing +import io.opentelemetry.instrumentation.test.LibraryTestTrait +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import ratpack.server.RatpackServerSpec + +class RatpackRoutesTest extends AbstractRatpackRoutesTest implements LibraryTestTrait { + @Override + void configure(RatpackServerSpec serverSpec) { + RatpackTracing tracing = RatpackTracing.create(openTelemetry) + serverSpec.registryOf { + tracing.configureServerRegistry(it) + } + } + + @Override + boolean hasHandlerSpan() { + return false + } + + @Override + List> extraAttributes() { + return [ + SemanticAttributes.HTTP_ROUTE, + SemanticAttributes.HTTP_TARGET, + SemanticAttributes.NET_TRANSPORT, + ] + } +} diff --git a/instrumentation/ratpack-1.4/testing/build.gradle.kts b/instrumentation/ratpack-1.4/testing/build.gradle.kts new file mode 100644 index 0000000000..2996c87c95 --- /dev/null +++ b/instrumentation/ratpack-1.4/testing/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { + api(project(":testing-common")) + + api("io.ratpack:ratpack-core:1.4.0") + + implementation("org.codehaus.groovy:groovy-all") + implementation("io.opentelemetry:opentelemetry-api") + implementation("org.spockframework:spock-core") +} diff --git a/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackAsyncHttpServerTest.groovy b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackAsyncHttpServerTest.groovy new file mode 100644 index 0000000000..a79eb1e190 --- /dev/null +++ b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackAsyncHttpServerTest.groovy @@ -0,0 +1,119 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +import ratpack.error.ServerErrorHandler +import ratpack.exec.Promise +import ratpack.server.RatpackServer + +abstract class AbstractRatpackAsyncHttpServerTest extends AbstractRatpackHttpServerTest { + + @Override + RatpackServer startServer(int bindPort) { + def ratpack = RatpackServer.start { + it.serverConfig { + it.port(bindPort) + it.address(InetAddress.getByName("localhost")) + } + it.handlers { + it.register { + it.add(ServerErrorHandler, new TestErrorHandler()) + } + it.prefix(SUCCESS.rawPath()) { + it.all {context -> + Promise.sync { + SUCCESS + } then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(endpoint.body) + } + } + } + } + it.prefix(INDEXED_CHILD.rawPath()) { + it.all {context -> + Promise.sync { + INDEXED_CHILD + } then { + controller(INDEXED_CHILD) { + INDEXED_CHILD.collectSpanAttributes { context.request.queryParams.get(it) } + context.response.status(INDEXED_CHILD.status).send() + } + } + } + } + it.prefix(QUERY_PARAM.rawPath()) { + it.all { context -> + Promise.sync { + QUERY_PARAM + } then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(context.request.query) + } + } + } + } + it.prefix(REDIRECT.rawPath()) { + it.all {context -> + Promise.sync { + REDIRECT + } then { endpoint -> + controller(endpoint) { + context.redirect(endpoint.body) + } + } + } + } + it.prefix(ERROR.rawPath()) { + it.all {context -> + Promise.sync { + ERROR + } then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(endpoint.body) + } + } + } + } + it.prefix(EXCEPTION.rawPath()) { + it.all { + Promise.sync { + EXCEPTION + } then { endpoint -> + controller(endpoint) { + throw new Exception(endpoint.body) + } + } + } + } + it.prefix("path/:id/param") { + it.all {context -> + Promise.sync { + PATH_PARAM + } then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(context.pathTokens.id) + } + } + } + } + } + configure(it) + } + + assert ratpack.bindPort == bindPort + assert ratpack.bindHost == 'localhost' + return ratpack + } +} diff --git a/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackForkedHttpServerTest.groovy b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackForkedHttpServerTest.groovy new file mode 100644 index 0000000000..aa9bab8ee7 --- /dev/null +++ b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackForkedHttpServerTest.groovy @@ -0,0 +1,183 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpRequest +import io.opentelemetry.testing.internal.armeria.common.AggregatedHttpResponse +import io.opentelemetry.testing.internal.armeria.common.HttpMethod +import ratpack.error.ServerErrorHandler +import ratpack.exec.Execution +import ratpack.exec.Promise +import ratpack.exec.Result +import ratpack.exec.util.ParallelBatch +import ratpack.server.RatpackServer + +abstract class AbstractRatpackForkedHttpServerTest extends AbstractRatpackHttpServerTest { + + @Override + RatpackServer startServer(int bindPort) { + + def ratpack = RatpackServer.start { + it.serverConfig { + it.port(bindPort) + it.address(InetAddress.getByName("localhost")) + } + it.handlers { + it.register { + it.add(ServerErrorHandler, new TestErrorHandler()) + } + it.prefix(SUCCESS.rawPath()) { + it.all {context -> + Promise.sync { + SUCCESS + }.fork().then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(endpoint.body) + } + } + } + } + it.prefix(INDEXED_CHILD.rawPath()) { + it.all {context -> + Promise.sync { + INDEXED_CHILD + }.fork().then { + controller(INDEXED_CHILD) { + INDEXED_CHILD.collectSpanAttributes { context.request.queryParams.get(it) } + context.response.status(INDEXED_CHILD.status).send() + } + } + } + } + it.prefix(QUERY_PARAM.rawPath()) { + it.all { context -> + Promise.sync { + QUERY_PARAM + }.fork().then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(context.request.query) + } + } + } + } + it.prefix(REDIRECT.rawPath()) { + it.all {context -> + Promise.sync { + REDIRECT + }.fork().then { endpoint -> + controller(endpoint) { + context.redirect(endpoint.body) + } + } + } + } + it.prefix(ERROR.rawPath()) { + it.all {context -> + Promise.sync { + ERROR + }.fork().then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(endpoint.body) + } + } + } + } + it.prefix(EXCEPTION.rawPath()) { + it.all { + Promise.sync { + EXCEPTION + }.fork().then { endpoint -> + controller(endpoint) { + throw new Exception(endpoint.body) + } + } + } + } + it.prefix("path/:id/param") { + it.all {context -> + Promise.sync { + PATH_PARAM + }.fork().then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(context.pathTokens.id) + } + } + } + } + it.prefix("fork_and_yieldAll") { + it.all {context -> + def promise = Promise.async { upstream -> + Execution.fork().start({ + upstream.accept(Result.success(SUCCESS)) + }) + } + ParallelBatch.of(promise).yieldAll().flatMap { list -> + Promise.sync { list.get(0).value } + } then { endpoint -> + controller(endpoint) { + context.response.status(endpoint.status).send(endpoint.body) + } + } + } + } + } + configure(it) + } + + assert ratpack.bindPort == bindPort + assert ratpack.bindHost == 'localhost' + return ratpack + } + + def "test fork and yieldAll"() { + setup: + def url = address.resolve("fork_and_yieldAll").toString() + url = url.replace("http://", "h1c://") + def request = AggregatedHttpRequest.of(HttpMethod.GET, url) + AggregatedHttpResponse response = client.execute(request).aggregate().join() + + expect: + response.status().code() == SUCCESS.status + response.contentUtf8() == SUCCESS.body + + assertTraces(1) { + trace(0, 2 + (hasHandlerSpan(SUCCESS) ? 1 : 0)) { + span(0) { + name "/fork_and_yieldAll" + kind SpanKind.SERVER + hasNoParent() + } + if (hasHandlerSpan(SUCCESS)) { + span(1) { + name "/fork_and_yieldAll" + kind SpanKind.INTERNAL + childOf span(0) + } + span(2) { + name "controller" + kind SpanKind.INTERNAL + childOf span(1) + } + } else { + span(1) { + name "controller" + kind SpanKind.INTERNAL + childOf span(0) + } + } + } + } + } +} diff --git a/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackHttpServerTest.groovy b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackHttpServerTest.groovy new file mode 100644 index 0000000000..1a7a28ff21 --- /dev/null +++ b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackHttpServerTest.groovy @@ -0,0 +1,146 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.ERROR +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.INDEXED_CHILD +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.PATH_PARAM +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.QUERY_PARAM +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static io.opentelemetry.instrumentation.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.instrumentation.test.asserts.TraceAssert +import io.opentelemetry.instrumentation.test.base.HttpServerTest +import io.opentelemetry.sdk.trace.data.SpanData +import ratpack.error.ServerErrorHandler +import ratpack.handling.Context +import ratpack.server.RatpackServer +import ratpack.server.RatpackServerSpec + +abstract class AbstractRatpackHttpServerTest extends HttpServerTest { + + abstract void configure(RatpackServerSpec serverSpec) + + @Override + RatpackServer startServer(int bindPort) { + def ratpack = RatpackServer.start { + it.serverConfig { + it.port(bindPort) + it.address(InetAddress.getByName("localhost")) + } + it.handlers { + it.register { + it.add(ServerErrorHandler, new TestErrorHandler()) + } + it.prefix(SUCCESS.rawPath()) { + it.all {context -> + controller(SUCCESS) { + context.response.status(SUCCESS.status).send(SUCCESS.body) + } + } + } + it.prefix(INDEXED_CHILD.rawPath()) { + it.all {context -> + controller(INDEXED_CHILD) { + INDEXED_CHILD.collectSpanAttributes { context.request.queryParams.get(it) } + context.response.status(INDEXED_CHILD.status).send() + } + } + } + it.prefix(QUERY_PARAM.rawPath()) { + it.all { context -> + controller(QUERY_PARAM) { + context.response.status(QUERY_PARAM.status).send(context.request.query) + } + } + } + it.prefix(REDIRECT.rawPath()) { + it.all {context -> + controller(REDIRECT) { + context.redirect(REDIRECT.body) + } + } + } + it.prefix(ERROR.rawPath()) { + it.all {context -> + controller(ERROR) { + context.response.status(ERROR.status).send(ERROR.body) + } + } + } + it.prefix(EXCEPTION.rawPath()) { + it.all { + controller(EXCEPTION) { + throw new Exception(EXCEPTION.body) + } + } + } + it.prefix("path/:id/param") { + it.all {context -> + controller(PATH_PARAM) { + context.response.status(PATH_PARAM.status).send(context.pathTokens.id) + } + } + } + } + configure(it) + } + + assert ratpack.bindPort == bindPort + return ratpack + } + + // TODO(anuraaga): The default Ratpack error handler also returns a 500 which is all we test, so + // we don't actually have test coverage ensuring our instrumentation correctly delegates to this + // user registered handler. + static class TestErrorHandler implements ServerErrorHandler { + @Override + void error(Context context, Throwable throwable) throws Exception { + context.response.status(500).send(throwable.message) + } + } + + @Override + void stopServer(RatpackServer server) { + server.stop() + } + + @Override + boolean hasHandlerSpan(ServerEndpoint endpoint) { + true + } + + @Override + boolean testPathParam() { + true + } + + @Override + boolean testConcurrency() { + true + } + + @Override + void handlerSpan(TraceAssert trace, int index, Object parent, String method = "GET", ServerEndpoint endpoint = SUCCESS) { + trace.span(index) { + name endpoint.status == 404 ? "/" : endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.path + kind INTERNAL + childOf((SpanData) parent) + if (endpoint == EXCEPTION) { + status StatusCode.ERROR + errorEvent(Exception, EXCEPTION.body) + } + } + } + + @Override + String expectedServerSpanName(ServerEndpoint endpoint) { + return endpoint.status == 404 ? "/" : endpoint == PATH_PARAM ? "/path/:id/param" : endpoint.path + } +} diff --git a/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackRoutesTest.groovy b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackRoutesTest.groovy new file mode 100644 index 0000000000..31e493d08f --- /dev/null +++ b/instrumentation/ratpack-1.4/testing/src/main/groovy/io/opentelemetry/instrumentation/ratpack/server/AbstractRatpackRoutesTest.groovy @@ -0,0 +1,171 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.server + +import static io.opentelemetry.api.trace.SpanKind.INTERNAL +import static io.opentelemetry.api.trace.SpanKind.SERVER +import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.NetTransportValues.IP_TCP + +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.instrumentation.test.InstrumentationSpecification +import io.opentelemetry.instrumentation.test.utils.PortUtils +import io.opentelemetry.semconv.trace.attributes.SemanticAttributes +import io.opentelemetry.testing.internal.armeria.client.WebClient +import ratpack.path.PathBinding +import ratpack.server.RatpackServer +import ratpack.server.RatpackServerSpec +import spock.lang.Shared +import spock.lang.Unroll + +@Unroll +abstract class AbstractRatpackRoutesTest extends InstrumentationSpecification { + + abstract void configure(RatpackServerSpec serverSpec) + + @Shared + RatpackServer app + + // Force HTTP/1 with h1c to prevent tracing of upgrade request. + @Shared + WebClient client + + def setupSpec() { + app = RatpackServer.start { + it.serverConfig { + it.port(PortUtils.findOpenPort()) + it.address(InetAddress.getByName("localhost")) + } + it.handlers { + it.prefix("a") { + it.all { context -> + context.render(context.get(PathBinding).description) + } + } + it.prefix("b/::\\d+") { + it.all { context -> + context.render(context.get(PathBinding).description) + } + } + it.prefix("c/:val?") { + it.all { context -> + context.render(context.get(PathBinding).description) + } + } + it.prefix("d/:val") { + it.all { context -> + context.render(context.get(PathBinding).description) + } + } + it.prefix("e/:val?:\\d+") { + it.all { context -> + context.render(context.get(PathBinding).description) + } + } + it.prefix("f/:val:\\d+") { + it.all { context -> + context.render(context.get(PathBinding).description) + } + } + } + configure(it) + } + client = WebClient.of("h1c://localhost:${app.bindPort}") + } + + def cleanupSpec() { + app.stop() + } + + abstract boolean hasHandlerSpan() + + List> extraAttributes() { + [] + } + + def "test bindings for #path"() { + when: + def resp = client.get(path).aggregate().join() + + then: + resp.status().code() == 200 + resp.contentUtf8() == route + + def extraAttributes = extraAttributes() + + assertTraces(1) { + trace(0, 1 + (hasHandlerSpan() ? 1 : 0)) { + span(0) { + name "/$route" + kind SERVER + hasNoParent() + attributes { + "${SemanticAttributes.NET_PEER_IP.key}" { it == null || it == "127.0.0.1" } + "${SemanticAttributes.NET_PEER_PORT.key}" Long + "${SemanticAttributes.HTTP_URL.key}" "http://localhost:${app.bindPort}/${path}" + "${SemanticAttributes.HTTP_METHOD.key}" "GET" + "${SemanticAttributes.HTTP_STATUS_CODE.key}" 200 + "${SemanticAttributes.HTTP_FLAVOR.key}" "1.1" + "${SemanticAttributes.HTTP_USER_AGENT.key}" String + "${SemanticAttributes.HTTP_CLIENT_IP.key}" { it == null || it == "127.0.0.1" } + + if (extraAttributes.contains(SemanticAttributes.HTTP_HOST)) { + "${SemanticAttributes.HTTP_HOST}" "localhost:${app.bindPort}" + } + if (extraAttributes.contains(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH)) { + "${SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH}" Long + } + if (extraAttributes.contains(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH)) { + "${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH}" Long + } + if (extraAttributes.contains(SemanticAttributes.HTTP_ROUTE)) { + // TODO(anuraaga): Revisit this when applying instrumenters to more libraries, Armeria + // currently reports '/*' which is a fallback route. + "${SemanticAttributes.HTTP_ROUTE}" String + } + if (extraAttributes.contains(SemanticAttributes.HTTP_SCHEME)) { + "${SemanticAttributes.HTTP_SCHEME}" "http" + } + if (extraAttributes.contains(SemanticAttributes.HTTP_SERVER_NAME)) { + "${SemanticAttributes.HTTP_SERVER_NAME}" String + } + if (extraAttributes.contains(SemanticAttributes.HTTP_TARGET)) { + "${SemanticAttributes.HTTP_TARGET}" "/$path" + } + if (extraAttributes.contains(SemanticAttributes.NET_PEER_NAME)) { + "${SemanticAttributes.NET_PEER_NAME}" "localhost" + } + if (extraAttributes.contains(SemanticAttributes.NET_TRANSPORT)) { + "${SemanticAttributes.NET_TRANSPORT}" IP_TCP + } + } + } + if (hasHandlerSpan()) { + span(1) { + name "/$route" + kind INTERNAL + childOf span(0) + attributes { + } + } + } + } + } + + where: + path | route + "a" | "a" + "b/123" | "b/::\\d+" + "c" | "c/:val?" + "c/123" | "c/:val?" + "c/foo" | "c/:val?" + "d/123" | "d/:val" + "d/foo" | "d/:val" + "e" | "e/:val?:\\d+" + "e/123" | "e/:val?:\\d+" + "e/foo" | "e/:val?:\\d+" + "f/123" | "f/:val:\\d+" + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index f3c67abd74..d9a1933610 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -260,6 +260,8 @@ include(":instrumentation:play-ws:play-ws-common:javaagent") include(":instrumentation:play-ws:play-ws-testing") include(":instrumentation:rabbitmq-2.7:javaagent") include(":instrumentation:ratpack-1.4:javaagent") +include(":instrumentation:ratpack-1.4:library") +include(":instrumentation:ratpack-1.4:testing") include(":instrumentation:reactor-3.1:javaagent") include(":instrumentation:reactor-3.1:library") include(":instrumentation:reactor-3.1:testing") diff --git a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/HttpServerTest.groovy b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/HttpServerTest.groovy index 4f2d2b6995..7f7871e4be 100644 --- a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/HttpServerTest.groovy +++ b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/HttpServerTest.groovy @@ -608,6 +608,7 @@ abstract class HttpServerTest extends InstrumentationSpecification imple } void indexedServerSpan(TraceAssert trace, Object parent, int requestId) { + def extraAttributes = extraAttributes() ServerEndpoint endpoint = INDEXED_CHILD trace.span(1) { name expectedServerSpanName(endpoint) @@ -622,6 +623,36 @@ abstract class HttpServerTest extends InstrumentationSpecification imple "${SemanticAttributes.HTTP_STATUS_CODE.key}" 200 "${SemanticAttributes.HTTP_FLAVOR.key}" "1.1" "${SemanticAttributes.HTTP_USER_AGENT.key}" TEST_USER_AGENT + + if (extraAttributes.contains(SemanticAttributes.HTTP_HOST)) { + "${SemanticAttributes.HTTP_HOST}" "localhost:${port}" + } + if (extraAttributes.contains(SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH)) { + "${SemanticAttributes.HTTP_REQUEST_CONTENT_LENGTH}" Long + } + if (extraAttributes.contains(SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH)) { + "${SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH}" Long + } + if (extraAttributes.contains(SemanticAttributes.HTTP_ROUTE)) { + // TODO(anuraaga): Revisit this when applying instrumenters to more libraries, Armeria + // currently reports '/*' which is a fallback route. + "${SemanticAttributes.HTTP_ROUTE}" String + } + if (extraAttributes.contains(SemanticAttributes.HTTP_SCHEME)) { + "${SemanticAttributes.HTTP_SCHEME}" "http" + } + if (extraAttributes.contains(SemanticAttributes.HTTP_SERVER_NAME)) { + "${SemanticAttributes.HTTP_SERVER_NAME}" String + } + if (extraAttributes.contains(SemanticAttributes.HTTP_TARGET)) { + "${SemanticAttributes.HTTP_TARGET}" endpoint.path + "?id=$requestId" + } + if (extraAttributes.contains(SemanticAttributes.NET_PEER_NAME)) { + "${SemanticAttributes.NET_PEER_NAME}" "localhost" + } + if (extraAttributes.contains(SemanticAttributes.NET_TRANSPORT)) { + "${SemanticAttributes.NET_TRANSPORT}" IP_TCP + } } } }