diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackFunctionalTest.groovy b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackFunctionalTest.groovy deleted file mode 100644 index c20bb5a3589f..000000000000 --- a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackFunctionalTest.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.ratpack.v1_7 - -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter -import io.opentelemetry.sdk.trace.export.SpanExporter -import ratpack.guice.BindingsImposition -import ratpack.impose.ForceDevelopmentImposition -import ratpack.impose.ImpositionsSpec -import ratpack.impose.UserRegistryImposition -import ratpack.registry.Registry -import ratpack.test.MainClassApplicationUnderTest -import ratpack.test.embed.EmbeddedApp - -class RatpackFunctionalTest extends MainClassApplicationUnderTest { - - Registry registry - @Lazy - InMemorySpanExporter spanExporter = registry.get(SpanExporter) as InMemorySpanExporter - EmbeddedApp app = EmbeddedApp.of { server -> - server.handlers { chain -> - chain.get("other") { ctx -> ctx.render("hi-other") } - } - } - - RatpackFunctionalTest(Class mainClass) { - super(mainClass) - getAddress() - } - - @Override - void addImpositions(ImpositionsSpec impositions) { - impositions.add(ForceDevelopmentImposition.of(false)) - impositions.add(UserRegistryImposition.of { r -> - registry = r - registry - }) - impositions.add(BindingsImposition.of { - it.bindInstance(URI, app.address.resolve("other")) - }) - } -} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy deleted file mode 100644 index bd4f7c1a57d4..000000000000 --- a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.ratpack.v1_7.client - -import io.opentelemetry.api.OpenTelemetry -import io.opentelemetry.api.trace.StatusCode -import io.opentelemetry.api.trace.Tracer -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator -import io.opentelemetry.context.Context -import io.opentelemetry.context.propagation.ContextPropagators -import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackClientTelemetry -import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackServerTelemetry -import io.opentelemetry.sdk.OpenTelemetrySdk -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter -import io.opentelemetry.sdk.trace.SdkTracerProvider -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor -import io.opentelemetry.semconv.UrlAttributes -import ratpack.exec.Execution -import ratpack.exec.Promise -import ratpack.func.Action -import ratpack.guice.Guice -import ratpack.http.client.HttpClient -import ratpack.service.Service -import ratpack.service.StartEvent -import ratpack.test.embed.EmbeddedApp -import spock.lang.Specification -import spock.util.concurrent.PollingConditions - -import java.time.Duration -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.SpanKind.SERVER -import static io.opentelemetry.semconv.HttpAttributes.* - -class InstrumentedHttpClientTest extends Specification { - - def spanExporter = InMemorySpanExporter.create() - def tracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) - .build() - - def openTelemetry = OpenTelemetrySdk.builder() - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .setTracerProvider(tracerProvider).build() - - RatpackClientTelemetry telemetry = RatpackClientTelemetry.create(openTelemetry) - RatpackServerTelemetry serverTelemetry = RatpackServerTelemetry.create(openTelemetry) - - def cleanup() { - spanExporter.reset() - } - - def "propagate trace with http calls"() { - expect: - def otherApp = EmbeddedApp.of { spec -> - spec.registry( - Guice.registry { bindings -> - serverTelemetry.configureRegistry(bindings) - } - ) - spec.handlers { - it.get("bar") { ctx -> ctx.render("foo") } - } - } - - def app = EmbeddedApp.of { spec -> - spec.registry( - Guice.registry { bindings -> - serverTelemetry.configureRegistry(bindings) - bindings.bindInstance(HttpClient, telemetry.instrument(HttpClient.of(Action.noop()))) - } - ) - - spec.handlers { chain -> - chain.get("foo") { ctx -> - HttpClient instrumentedHttpClient = ctx.get(HttpClient) - instrumentedHttpClient.get(new URI("${otherApp.address}bar")) - .then { ctx.render("bar") } - } - } - } - - app.test { httpClient -> - assert "bar" == httpClient.get("foo").body.text - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" && it.kind == CLIENT } - def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "GET /bar" && it.kind == SERVER } - - spanData.traceId == spanClientData.traceId - spanData.traceId == spanDataApi.traceId - - spanData.kind == SERVER - spanClientData.kind == CLIENT - def atts = spanClientData.attributes.asMap() - atts[HTTP_ROUTE] == "/bar" - atts[HTTP_REQUEST_METHOD] == "GET" - atts[HTTP_RESPONSE_STATUS_CODE] == 200L - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/foo" - attributes[UrlAttributes.URL_PATH] == "/foo" - attributes[HTTP_REQUEST_METHOD] == "GET" - attributes[HTTP_RESPONSE_STATUS_CODE] == 200L - - def attsApi = spanDataApi.attributes.asMap() - attsApi[HTTP_ROUTE] == "/bar" - attsApi[UrlAttributes.URL_PATH] == "/bar" - attsApi[HTTP_REQUEST_METHOD] == "GET" - attsApi[HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "add spans for multiple concurrent client calls"() { - expect: - def latch = new CountDownLatch(2) - - def otherApp = EmbeddedApp.of { spec -> - spec.handlers { chain -> - chain.get("foo") { ctx -> ctx.render("bar") } - chain.get("bar") { ctx -> ctx.render("foo") } - } - } - - def app = EmbeddedApp.of { spec -> - spec.registry( - Guice.registry { bindings -> - serverTelemetry.configureRegistry(bindings) - bindings.bindInstance(HttpClient, telemetry.instrument(HttpClient.of(Action.noop()))) - } - ) - - spec.handlers { chain -> - chain.get("path-name") { ctx -> - ctx.render("hello") - def instrumentedHttpClient = ctx.get(HttpClient) - instrumentedHttpClient.get(new URI("${otherApp.address}foo")).then { latch.countDown() } - instrumentedHttpClient.get(new URI("${otherApp.address}bar")).then { latch.countDown() } - } - } - } - - app.test { httpClient -> - assert "hello" == httpClient.get("path-name").body.text - latch.await(1, TimeUnit.SECONDS) - - new PollingConditions().eventually { - spanExporter.finishedSpanItems.size() == 3 - def spanData = spanExporter.finishedSpanItems.find { spanData -> spanData.name == "GET /path-name" } - def spanClientData1 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/foo" } - def spanClientData2 = spanExporter.finishedSpanItems.find { s -> s.name == "GET" && s.attributes.asMap()[HTTP_ROUTE] == "/bar" } - - spanData.traceId == spanClientData1.traceId - spanData.traceId == spanClientData2.traceId - - spanData.kind == SERVER - - spanClientData1.kind == CLIENT - def atts = spanClientData1.attributes.asMap() - atts[HTTP_ROUTE] == "/foo" - atts[HTTP_REQUEST_METHOD] == "GET" - atts[HTTP_RESPONSE_STATUS_CODE] == 200L - - spanClientData2.kind == CLIENT - def atts2 = spanClientData2.attributes.asMap() - atts2[HTTP_ROUTE] == "/bar" - atts2[HTTP_REQUEST_METHOD] == "GET" - atts2[HTTP_RESPONSE_STATUS_CODE] == 200L - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/path-name" - attributes[UrlAttributes.URL_PATH] == "/path-name" - attributes[HTTP_REQUEST_METHOD] == "GET" - attributes[HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "handling exception errors in http client"() { - expect: - def otherApp = EmbeddedApp.of { spec -> - spec.handlers { - it.get("foo") { ctx -> - Promise.value("bar").defer(Duration.ofSeconds(1L)) - .then { ctx.render("bar") } - } - } - } - - def app = EmbeddedApp.of { spec -> - spec.registry( - Guice.registry { bindings -> - serverTelemetry.configureRegistry(bindings) - bindings.bindInstance(HttpClient, telemetry.instrument( - HttpClient.of { s -> s.readTimeout(Duration.ofMillis(10)) }) - ) - } - ) - - spec.handlers { chain -> - chain.get("path-name") { ctx -> - def instrumentedHttpClient = ctx.get(HttpClient) - instrumentedHttpClient.get(new URI("${otherApp.address}foo")) - .onError { ctx.render("error") } - .then { ctx.render("hello") } - } - } - } - - app.test { httpClient -> - assert "error" == httpClient.get("path-name").body.text - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /path-name" } - def spanClientData = spanExporter.finishedSpanItems.find { it.name == "GET" } - - spanData.traceId == spanClientData.traceId - - spanData.kind == SERVER - spanClientData.kind == CLIENT - def atts = spanClientData.attributes.asMap() - atts[HTTP_ROUTE] == "/foo" - atts[HTTP_REQUEST_METHOD] == "GET" - atts[HTTP_RESPONSE_STATUS_CODE] == null - spanClientData.status.statusCode == StatusCode.ERROR - spanClientData.events.first().name == "exception" - - def attributes = spanData.attributes.asMap() - attributes[HTTP_ROUTE] == "/path-name" - attributes[UrlAttributes.URL_PATH] == "/path-name" - attributes[HTTP_REQUEST_METHOD] == "GET" - attributes[HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "propagate http trace in ratpack services with compute thread"() { - expect: - def latch = new CountDownLatch(1) - - def otherApp = EmbeddedApp.of { spec -> - spec.handlers { - it.get("foo") { ctx -> ctx.render("bar") } - } - } - - def app = EmbeddedApp.of { spec -> - spec.registry( - Guice.registry { bindings -> - serverTelemetry.configureRegistry(bindings) - bindings.bindInstance(HttpClient, telemetry.instrument(HttpClient.of(Action.noop()))) - bindings.bindInstance(new BarService(latch, "${otherApp.address}foo", openTelemetry)) - }, - ) - spec.handlers { chain -> - chain.get("foo") { ctx -> ctx.render("bar") } - } - } - - app.address - latch.await() - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "a-span" } - def trace = spanExporter.finishedSpanItems.findAll { it.traceId == spanData.traceId } - - trace.size() == 3 - } - } - - def "propagate http trace in ratpack services with fork executions"() { - expect: - def latch = new CountDownLatch(1) - - def otherApp = EmbeddedApp.of { spec -> - spec.handlers { - it.get("foo") { ctx -> ctx.render("bar") } - } - } - - def app = EmbeddedApp.of { spec -> - spec.registry( - Guice.registry { bindings -> - serverTelemetry.configureRegistry(bindings) - bindings.bindInstance(HttpClient, telemetry.instrument(HttpClient.of(Action.noop()))) - bindings.bindInstance(new BarForkService(latch, "${otherApp.address}foo", openTelemetry)) - }, - ) - spec.handlers { chain -> - chain.get("foo") { ctx -> ctx.render("bar") } - } - } - - app.address - latch.await() - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "a-span" } - def trace = spanExporter.finishedSpanItems.findAll { it.traceId == spanData.traceId } - - trace.size() == 3 - } - } -} - -class BarService implements Service { - private final String url - private final CountDownLatch latch - private final OpenTelemetry openTelemetry - - BarService(CountDownLatch latch, String url, OpenTelemetry openTelemetry) { - this.latch = latch - this.url = url - this.openTelemetry = openTelemetry - } - - private Tracer tracer = openTelemetry.tracerProvider.tracerBuilder("testing").build() - - void onStart(StartEvent event) { - def parentContext = Context.current() - def span = tracer.spanBuilder("a-span") - .setParent(parentContext) - .startSpan() - - Context otelContext = parentContext.with(span) - otelContext.makeCurrent().withCloseable { - Execution.current().add(Context, otelContext) - def httpClient = event.registry.get(HttpClient) - httpClient.get(new URI(url)) - .flatMap { httpClient.get(new URI(url)) } - .then { - span.end() - latch.countDown() - } - } - } -} - -class BarForkService implements Service { - private final String url - private final CountDownLatch latch - private final OpenTelemetry openTelemetry - - BarForkService(CountDownLatch latch, String url, OpenTelemetry openTelemetry) { - this.latch = latch - this.url = url - this.openTelemetry = openTelemetry - } - - private Tracer tracer = openTelemetry.tracerProvider.tracerBuilder("testing").build() - - void onStart(StartEvent event) { - Execution.fork().start { - def parentContext = Context.current() - def span = tracer.spanBuilder("a-span") - .setParent(parentContext) - .startSpan() - - Context otelContext = parentContext.with(span) - otelContext.makeCurrent().withCloseable { - Execution.current().add(Context, otelContext) - def httpClient = event.registry.get(HttpClient) - httpClient.get(new URI(url)) - .flatMap { httpClient.get(new URI(url)) } - .then { - span.end() - latch.countDown() - } - } - } - } -} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.groovy b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.groovy deleted file mode 100644 index d2ca21755a9c..000000000000 --- a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.groovy +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.ratpack.v1_7.server - -import com.google.inject.AbstractModule -import com.google.inject.Provides -import groovy.transform.CompileStatic -import io.opentelemetry.api.OpenTelemetry -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackClientTelemetry -import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackFunctionalTest -import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackServerTelemetry -import io.opentelemetry.sdk.OpenTelemetrySdk -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter -import io.opentelemetry.sdk.trace.SdkTracerProvider -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor -import io.opentelemetry.sdk.trace.export.SpanExporter -import ratpack.exec.ExecInitializer -import ratpack.exec.ExecInterceptor -import ratpack.guice.Guice -import ratpack.handling.Handler -import ratpack.http.client.HttpClient -import ratpack.server.RatpackServer -import spock.lang.Specification -import spock.util.concurrent.PollingConditions - -import javax.inject.Singleton - -import static io.opentelemetry.semconv.HttpAttributes.* -import static io.opentelemetry.semconv.UrlAttributes.URL_PATH - -class RatpackServerApplicationTest extends Specification { - - def app = new RatpackFunctionalTest(RatpackApp) - - def "add span on handlers"() { - expect: - app.test { httpClient -> - assert "hi-foo" == httpClient.get("foo").body.text - - new PollingConditions().eventually { - def spanData = app.spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def attributes = spanData.attributes.asMap() - - spanData.kind == SpanKind.SERVER - attributes[HTTP_ROUTE] == "/foo" - attributes[URL_PATH] == "/foo" - attributes[HTTP_REQUEST_METHOD] == "GET" - attributes[HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "propagate trace to http calls"() { - expect: - app.test { httpClient -> - assert "hi-bar" == httpClient.get("bar").body.text - - new PollingConditions().eventually { - def spanData = app.spanExporter.finishedSpanItems.find { it.name == "GET /bar" } - def spanDataClient = app.spanExporter.finishedSpanItems.find { it.name == "GET" } - def attributes = spanData.attributes.asMap() - - spanData.traceId == spanDataClient.traceId - - spanData.kind == SpanKind.SERVER - attributes[HTTP_ROUTE] == "/bar" - attributes[URL_PATH] == "/bar" - attributes[HTTP_REQUEST_METHOD] == "GET" - attributes[HTTP_RESPONSE_STATUS_CODE] == 200L - - spanDataClient.kind == SpanKind.CLIENT - def attributesClient = spanDataClient.attributes.asMap() - attributesClient[HTTP_ROUTE] == "/other" - attributesClient[HTTP_REQUEST_METHOD] == "GET" - attributesClient[HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "ignore handlers before OpenTelemetryServerHandler"() { - expect: - app.test { httpClient -> - assert "ignored" == httpClient.get("ignore").body.text - - new PollingConditions(initialDelay: 0.1, timeout: 0.3).eventually { - !app.spanExporter.finishedSpanItems.any { it.name == "GET /ignore" } - } - } - } -} - - -@CompileStatic -class OpenTelemetryModule extends AbstractModule { - @Override - protected void configure() { - bind(SpanExporter).toInstance(InMemorySpanExporter.create()) - } - - @Singleton - @Provides - RatpackClientTelemetry ratpackClientTelemetry(OpenTelemetry openTelemetry) { - return RatpackClientTelemetry.create(openTelemetry) - } - - @Singleton - @Provides - RatpackServerTelemetry ratpackServerTelemetry(OpenTelemetry openTelemetry) { - return RatpackServerTelemetry.create(openTelemetry) - } - - @Singleton - @Provides - Handler ratpackServerHandler(RatpackServerTelemetry ratpackTracing) { - return ratpackTracing.getHandler() - } - - @Singleton - @Provides - ExecInterceptor ratpackExecInterceptor(RatpackServerTelemetry ratpackTracing) { - return ratpackTracing.getExecInterceptor() - } - - @Provides - @Singleton - OpenTelemetry providesOpenTelemetry(SpanExporter spanExporter) { - def tracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) - .build() - return OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build() - } - - @Singleton - @Provides - HttpClient instrumentedHttpClient(RatpackClientTelemetry ratpackTracing) { - return ratpackTracing.instrument(HttpClient.of {}) - } - - @Singleton - @Provides - ExecInitializer ratpackExecInitializer(RatpackServerTelemetry ratpackTracing) { - return ratpackTracing.getExecInitializer() - } -} - -@CompileStatic -class RatpackApp { - - static void main(String... args) { - RatpackServer.start { server -> - server - .registry(Guice.registry { b -> b.module(OpenTelemetryModule) }) - .handlers { chain -> - chain - .get("ignore") { ctx -> ctx.render("ignored") } - .all(Handler) - .get("foo") { ctx -> ctx.render("hi-foo") } - .get("bar") { ctx -> - ctx.get(HttpClient).get(ctx.get(URI)) - .then { ctx.render("hi-bar") } - } - } - } - } -} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.groovy b/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.groovy deleted file mode 100644 index 19cb6d22e5ac..000000000000 --- a/instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.groovy +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.ratpack.v1_7.server - -import io.opentelemetry.api.trace.SpanKind -import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator -import io.opentelemetry.context.propagation.ContextPropagators -import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackServerTelemetry -import io.opentelemetry.sdk.OpenTelemetrySdk -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter -import io.opentelemetry.sdk.trace.SdkTracerProvider -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor -import io.opentelemetry.semconv.HttpAttributes -import io.opentelemetry.semconv.UrlAttributes -import ratpack.exec.Blocking -import ratpack.registry.Registry -import ratpack.test.embed.EmbeddedApp -import spock.lang.Specification -import spock.util.concurrent.PollingConditions - -class RatpackServerTest extends Specification { - - def spanExporter = InMemorySpanExporter.create() - def tracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) - .build() - - def openTelemetry = OpenTelemetrySdk.builder() - .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) - .setTracerProvider(tracerProvider).build() - - def telemetry = RatpackServerTelemetry.create(openTelemetry) - - def cleanup() { - spanExporter.reset() - } - - def "add span on handlers"() { - given: - def app = EmbeddedApp.of { spec -> - spec.registry { Registry.of { telemetry.configureRegistry(it) } } - spec.handlers { chain -> - chain.get("foo") { ctx -> ctx.render("hi-foo") } - } - } - - expect: - app.test { httpClient -> - assert "hi-foo" == httpClient.get("foo").body.text - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def attributes = spanData.attributes.asMap() - - spanData.kind == SpanKind.SERVER - attributes[HttpAttributes.HTTP_ROUTE] == "/foo" - attributes[UrlAttributes.URL_PATH] == "/foo" - attributes[HttpAttributes.HTTP_REQUEST_METHOD] == "GET" - attributes[HttpAttributes.HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "propagate trace with instrumented async operations"() { - expect: - def app = EmbeddedApp.of { spec -> - spec.registry { Registry.of { telemetry.configureRegistry(it) } } - spec.handlers { chain -> - chain.get("foo") { ctx -> - ctx.render("hi-foo") - Blocking.op { - def span = openTelemetry.getTracer("any-tracer").spanBuilder("a-span").startSpan() - span.makeCurrent().withCloseable { - span.addEvent("an-event") - span.end() - } - }.then() - } - } - } - - app.test { httpClient -> - assert "hi-foo" == httpClient.get("foo").body.text - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def spanDataChild = spanExporter.finishedSpanItems.find { it.name == "a-span" } - - spanData.kind == SpanKind.SERVER - spanData.traceId == spanDataChild.traceId - spanDataChild.parentSpanId == spanData.spanId - spanDataChild.events.any { it.name == "an-event" } - - def attributes = spanData.attributes.asMap() - attributes[HttpAttributes.HTTP_ROUTE] == "/foo" - attributes[UrlAttributes.URL_PATH] == "/foo" - attributes[HttpAttributes.HTTP_REQUEST_METHOD] == "GET" - attributes[HttpAttributes.HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } - - def "propagate trace with instrumented async concurrent operations"() { - expect: - def app = EmbeddedApp.of { spec -> - spec.registry { Registry.of { telemetry.configureRegistry(it) } } - spec.handlers { chain -> - chain.get("bar") { ctx -> - ctx.render("hi-bar") - Blocking.op { - def span = openTelemetry.getTracer("any-tracer").spanBuilder("another-span").startSpan() - span.makeCurrent().withCloseable { - span.addEvent("an-event") - span.end() - } - }.then() - } - chain.get("foo") { ctx -> - ctx.render("hi-foo") - Blocking.op { - def span = openTelemetry.getTracer("any-tracer").spanBuilder("a-span").startSpan() - span.makeCurrent().withCloseable { - span.addEvent("an-event") - span.end() - } - }.then() - } - } - } - - app.test { httpClient -> - assert "hi-foo" == httpClient.get("foo").body.text - assert "hi-bar" == httpClient.get("bar").body.text - - new PollingConditions().eventually { - def spanData = spanExporter.finishedSpanItems.find { it.name == "GET /foo" } - def spanDataChild = spanExporter.finishedSpanItems.find { it.name == "a-span" } - - def spanData2 = spanExporter.finishedSpanItems.find { it.name == "GET /bar" } - def spanDataChild2 = spanExporter.finishedSpanItems.find { it.name == "another-span" } - - spanData.kind == SpanKind.SERVER - spanData.traceId == spanDataChild.traceId - spanDataChild.parentSpanId == spanData.spanId - spanDataChild.events.any { it.name == "an-event" } - - spanData2.kind == SpanKind.SERVER - spanData2.traceId == spanDataChild2.traceId - spanDataChild2.parentSpanId == spanData2.spanId - spanDataChild2.events.any { it.name == "an-event" } - - def attributes = spanData.attributes.asMap() - attributes[HttpAttributes.HTTP_ROUTE] == "/foo" - attributes[UrlAttributes.URL_PATH] == "/foo" - attributes[HttpAttributes.HTTP_REQUEST_METHOD] == "GET" - attributes[HttpAttributes.HTTP_RESPONSE_STATUS_CODE] == 200L - } - } - } -} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarForkService.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarForkService.java new file mode 100644 index 000000000000..666c23ee65f5 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarForkService.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.client; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import ratpack.exec.Execution; +import ratpack.exec.Operation; +import ratpack.service.StartEvent; + +public class BarForkService extends BarService { + + public BarForkService(String url, InstrumentationExtension testing) { + super(url, testing); + } + + @Override + public void onStart(StartEvent event) { + Execution.fork().start(Operation.of(() -> generateSpan(event))); + } +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarService.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarService.java new file mode 100644 index 000000000000..80ea6133259f --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarService.java @@ -0,0 +1,52 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.client; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.net.URI; +import java.net.URISyntaxException; +import ratpack.exec.Execution; +import ratpack.http.client.HttpClient; +import ratpack.service.Service; +import ratpack.service.StartEvent; + +public class BarService implements Service { + private final String url; + private final InstrumentationExtension testing; + + public BarService(String url, InstrumentationExtension testing) { + this.url = url; + this.testing = testing; + } + + protected void generateSpan(StartEvent event) { + Context parentContext = Context.current(); + testing.runWithSpan( + "a-span", + () -> { + Span span = Span.current(); + Context otelContext = parentContext.with(span); + try (Scope scope = otelContext.makeCurrent()) { + Execution.current().add(Context.class, otelContext); + HttpClient httpClient = event.getRegistry().get(HttpClient.class); + httpClient + .get(new URI(url)) + .flatMap(response -> httpClient.get(new URI(url))) + .then(response -> span.end()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void onStart(StartEvent event) { + generateSpan(event); + } +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.java new file mode 100644 index 000000000000..dbac9f36b6e4 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.java @@ -0,0 +1,367 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.client; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.ErrorAttributes.ERROR_TYPE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; +import static io.opentelemetry.semconv.UrlAttributes.URL_QUERY; +import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Named.named; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackClientTelemetry; +import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackServerTelemetry; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.net.URI; +import java.time.Duration; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import ratpack.exec.Promise; +import ratpack.func.Action; +import ratpack.guice.Guice; +import ratpack.http.client.HttpClient; +import ratpack.http.client.HttpClientReadTimeoutException; +import ratpack.http.client.ReceivedResponse; +import ratpack.test.embed.EmbeddedApp; + +class InstrumentedHttpClientTest { + + @RegisterExtension + private static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + private static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + private static final RatpackClientTelemetry clientTelemetry = + RatpackClientTelemetry.create(testing.getOpenTelemetry()); + + private static final RatpackServerTelemetry serverTelemetry = + RatpackServerTelemetry.create(testing.getOpenTelemetry()); + + @Test + void testPropagateTraceWithHttpCalls() throws Exception { + EmbeddedApp otherApp = + EmbeddedApp.of( + spec -> { + spec.registry(Guice.registry(serverTelemetry::configureRegistry)); + spec.handlers(chain -> chain.get("bar", ctx -> ctx.render("foo"))); + }); + cleanup.deferCleanup(otherApp); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, + clientTelemetry.instrument(HttpClient.of(Action.noop()))); + })); + + spec.handlers( + chain -> + chain.get( + "foo", + ctx -> { + HttpClient instrumentedHttpClient = ctx.get(HttpClient.class); + instrumentedHttpClient + .get(new URI(otherApp.getAddress() + "bar")) + .then(response -> ctx.render("bar")); + })); + }); + cleanup.deferCleanup(app); + + assertThat(app.getHttpClient().get("foo").getBody().getText()).isEqualTo("bar"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /foo") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_PATH, "/foo"), + equalTo(URL_SCHEME, "http"), + equalTo(URL_QUERY, "")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/bar"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(URL_FULL, otherApp.getAddress() + "bar")), + span -> + span.hasName("GET /bar") + .hasParent(trace.getSpan(1)) + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/bar"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(SERVER_PORT, otherApp.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_PATH, "/bar"), + equalTo(URL_SCHEME, "http"), + equalTo(URL_QUERY, "")))); + } + + @Test + void testAddSpansForMultipleConcurrentClientCalls() throws Exception { + EmbeddedApp otherApp = + EmbeddedApp.of( + spec -> + spec.handlers( + chain -> { + chain.get("foo", ctx -> ctx.render("bar")); + chain.get("bar", ctx -> ctx.render("foo")); + })); + cleanup.deferCleanup(otherApp); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, + clientTelemetry.instrument(HttpClient.of(Action.noop()))); + })); + + spec.handlers( + chain -> + chain.get( + "path-name", + ctx -> { + ctx.render("hello"); + HttpClient instrumentedHttpClient = ctx.get(HttpClient.class); + + instrumentedHttpClient + .get(new URI(otherApp.getAddress() + "foo")) + .then(ReceivedResponse::getBody); + + instrumentedHttpClient + .get(new URI(otherApp.getAddress() + "bar")) + .then(ReceivedResponse::getBody); + })); + }); + cleanup.deferCleanup(app); + + assertThat(app.getHttpClient().get("path-name").getBody().getText()).isEqualTo("hello"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /path-name") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/path-name"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(URL_QUERY, ""), + equalTo(URL_PATH, "/path-name"), + equalTo(URL_SCHEME, "http"), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(URL_FULL, otherApp.getAddress() + "foo")), + span -> + span.hasName("GET") + .hasParent(trace.getSpan(0)) + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/bar"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(URL_FULL, otherApp.getAddress() + "bar")))); + } + + @Test + void testHandlingExceptionErrorsInHttpClient() throws Exception { + EmbeddedApp otherApp = + EmbeddedApp.of( + spec -> + spec.handlers( + chain -> + chain.get( + "foo", + ctx -> + Promise.value("bar") + .defer(Duration.ofSeconds(1L)) + .then(value -> ctx.render("bar"))))); + cleanup.deferCleanup(otherApp); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, + clientTelemetry.instrument( + HttpClient.of(s -> s.readTimeout(Duration.ofMillis(10))))); + })); + + spec.handlers( + chain -> + chain.get( + "error-path-name", + ctx -> { + HttpClient instrumentedHttpClient = ctx.get(HttpClient.class); + instrumentedHttpClient + .get(new URI(otherApp.getAddress() + "foo")) + .onError(throwable -> ctx.render("error")) + .then(response -> ctx.render("hello")); + })); + }); + cleanup.deferCleanup(app); + + assertThat(app.getHttpClient().get("error-path-name").getBody().getText()).isEqualTo("error"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /error-path-name") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/error-path-name"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_PATH, "/error-path-name"), + equalTo(URL_SCHEME, "http"), + equalTo(URL_QUERY, "")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasStatus(StatusData.error()) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(ERROR_TYPE, HttpClientReadTimeoutException.class.getName()), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(URL_FULL, otherApp.getAddress() + "foo")))); + } + + private static Stream provideArguments() { + return Stream.of( + Arguments.of(named("Compute Thread", BarService.class)), + Arguments.of(named("Fork Executions", BarForkService.class))); + } + + @ParameterizedTest + @MethodSource("provideArguments") + void propagateHttpTraceInRatpackServicesWithForkExecutions( + Class serviceClass) throws Exception { + EmbeddedApp otherApp = + EmbeddedApp.of(spec -> spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("bar")))); + cleanup.deferCleanup(otherApp); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, + clientTelemetry.instrument(HttpClient.of(Action.noop()))); + bindings.bindInstance( + serviceClass + .getConstructor(String.class, InstrumentationExtension.class) + .newInstance(otherApp.getAddress() + "foo", testing)); + })); + + spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("bar"))); + }); + cleanup.deferCleanup(app); + + app.getAddress(); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("a-span").hasNoParent().hasAttributes(Attributes.empty()), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(URL_FULL, otherApp.getAddress() + "foo")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(URL_FULL, otherApp.getAddress() + "foo")))); + } +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/OpenTelemetryModule.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/OpenTelemetryModule.java new file mode 100644 index 000000000000..a9f152da9c34 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/OpenTelemetryModule.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +import com.google.inject.AbstractModule; +import com.google.inject.Provides; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackClientTelemetry; +import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackServerTelemetry; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import javax.inject.Singleton; +import org.junit.jupiter.api.extension.RegisterExtension; +import ratpack.exec.ExecInitializer; +import ratpack.exec.ExecInterceptor; +import ratpack.handling.Handler; +import ratpack.http.client.HttpClient; + +public class OpenTelemetryModule extends AbstractModule { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected void configure() { + bind(OpenTelemetry.class).toInstance(testing.getOpenTelemetry()); + } + + @Singleton + @Provides + RatpackClientTelemetry ratpackClientTelemetry(OpenTelemetry openTelemetry) { + return RatpackClientTelemetry.create(openTelemetry); + } + + @Singleton + @Provides + RatpackServerTelemetry ratpackServerTelemetry(OpenTelemetry openTelemetry) { + return RatpackServerTelemetry.create(openTelemetry); + } + + @Singleton + @Provides + Handler ratpackServerHandler(RatpackServerTelemetry ratpackTracing) { + return ratpackTracing.getHandler(); + } + + @Singleton + @Provides + ExecInterceptor ratpackExecInterceptor(RatpackServerTelemetry ratpackTracing) { + return ratpackTracing.getExecInterceptor(); + } + + @Singleton + @Provides + HttpClient instrumentedHttpClient(RatpackClientTelemetry ratpackTracing) throws Exception { + return ratpackTracing.instrument(HttpClient.of(spec -> {})); + } + + @Singleton + @Provides + ExecInitializer ratpackExecInitializer(RatpackServerTelemetry ratpackTracing) { + return ratpackTracing.getExecInitializer(); + } +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackApp.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackApp.java new file mode 100644 index 000000000000..92eee76a3a94 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackApp.java @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +import java.net.URI; +import ratpack.guice.Guice; +import ratpack.handling.Handler; +import ratpack.http.client.HttpClient; +import ratpack.server.RatpackServer; + +public class RatpackApp { + + public static void main(String... args) throws Exception { + RatpackServer.start( + server -> + server + .registry(Guice.registry(b -> b.module(OpenTelemetryModule.class))) + .handlers( + chain -> + chain + .get("ignore", ctx -> ctx.render("ignored")) + .all(Handler.class) + .get("foo", ctx -> ctx.render("hi-foo")) + .get( + "bar", + ctx -> + ctx.get(HttpClient.class) + .get(ctx.get(URI.class)) + .then(response -> ctx.render("hi-bar"))))); + } + + private RatpackApp() {} +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackFunctionalTest.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackFunctionalTest.java new file mode 100644 index 000000000000..9f367a528adf --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackFunctionalTest.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +import java.net.URI; +import ratpack.guice.BindingsImposition; +import ratpack.impose.ForceDevelopmentImposition; +import ratpack.impose.ImpositionsSpec; +import ratpack.test.MainClassApplicationUnderTest; +import ratpack.test.embed.EmbeddedApp; + +public class RatpackFunctionalTest extends MainClassApplicationUnderTest { + + private final EmbeddedApp app = + EmbeddedApp.of( + server -> server.handlers(chain -> chain.get("other", ctx -> ctx.render("hi-other")))); + + public RatpackFunctionalTest(Class mainClass) throws Exception { + super(mainClass); + getAddress(); + } + + @Override + protected void addImpositions(ImpositionsSpec impositions) { + impositions.add(ForceDevelopmentImposition.of(false)); + impositions.add( + BindingsImposition.of( + bindings -> bindings.bindInstance(URI.class, app.getAddress().resolve("other")))); + } + + public int getAppPort() { + return app.getServer().getBindPort(); + } +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.java new file mode 100644 index 000000000000..df83b1580b95 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.java @@ -0,0 +1,120 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.UrlAttributes.URL_FULL; +import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; +import static io.opentelemetry.semconv.UrlAttributes.URL_QUERY; +import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class RatpackServerApplicationTest { + @RegisterExtension + private static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + private static RatpackFunctionalTest app; + + @BeforeAll + static void setup() throws Exception { + app = new RatpackFunctionalTest(RatpackApp.class); + } + + @AfterAll + static void cleanup() { + app.close(); + } + + @Test + void testAddSpanOnHandlers() { + assertThat(app.getHttpClient().get("foo").getBody().getText()).isEqualTo("hi-foo"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /foo") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(URL_PATH, "/foo"), + equalTo(URL_SCHEME, "http"), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_QUERY, ""), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L)))); + } + + @Test + void testPropagateTraceToHttpCalls() { + assertThat(app.getHttpClient().get("bar").getBody().getText()).isEqualTo("hi-bar"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /bar") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/bar"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + equalTo(URL_PATH, "/bar"), + equalTo(URL_SCHEME, "http"), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_QUERY, "")), + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/other"), + equalTo(URL_FULL, "http://localhost:" + app.getAppPort() + "/other"), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L), + satisfies(SERVER_PORT, v -> v.isInstanceOf(Long.class)), + equalTo(SERVER_ADDRESS, "localhost")))); + } + + @Test + void testIgnoreHandlersBeforeOpenTelemetryServerHandler() { + testing.runWithSpan( + "parent", + () -> + assertThat(app.getHttpClient().get("ignore").getBody().getText()).isEqualTo("ignored")); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("parent") + .hasNoParent() + .hasKind(SpanKind.INTERNAL) + .hasAttributes(Attributes.empty()))); + } +} diff --git a/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.java new file mode 100644 index 000000000000..ac8253b09841 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.java @@ -0,0 +1,223 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_REQUEST_METHOD; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_RESPONSE_STATUS_CODE; +import static io.opentelemetry.semconv.HttpAttributes.HTTP_ROUTE; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PROTOCOL_VERSION; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.UrlAttributes.URL_PATH; +import static io.opentelemetry.semconv.UrlAttributes.URL_QUERY; +import static io.opentelemetry.semconv.UrlAttributes.URL_SCHEME; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.ratpack.v1_7.RatpackServerTelemetry; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import ratpack.exec.Blocking; +import ratpack.registry.Registry; +import ratpack.test.embed.EmbeddedApp; + +class RatpackServerTest { + @RegisterExtension + private static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @RegisterExtension + private static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + private static final RatpackServerTelemetry telemetry = + RatpackServerTelemetry.builder(testing.getOpenTelemetry()).build(); + + private static Registry registry; + + @BeforeAll + static void setUp() throws Exception { + registry = Registry.of(telemetry::configureRegistry); + } + + @Test + void testAddSpanOnHandlers() throws Exception { + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry(registry); + spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("hi-foo"))); + }); + cleanup.deferCleanup(app); + + assertThat(app.getHttpClient().get("foo").getBody().getText()).isEqualTo("hi-foo"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /foo") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(URL_PATH, "/foo"), + equalTo(URL_SCHEME, "http"), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_QUERY, ""), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L)))); + } + + @Test + void testPropagateTraceWithInstrumentedAsyncOperations() throws Exception { + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry(registry); + spec.handlers( + chain -> + chain.get( + "foo", + ctx -> { + ctx.render("hi-foo"); + Blocking.op( + () -> + testing.runWithSpan( + "a-span", + () -> { + Span span = Span.current(); + span.addEvent("an-event"); + })) + .then(); + })); + }); + cleanup.deferCleanup(app); + + assertThat(app.getHttpClient().get("foo").getBody().getText()).isEqualTo("hi-foo"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /foo") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(URL_PATH, "/foo"), + equalTo(URL_SCHEME, "http"), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_QUERY, ""), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L)), + span -> + span.hasName("a-span") + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()) + .hasEventsSatisfyingExactly( + event -> event.hasName("an-event").hasAttributes(Attributes.empty())))); + } + + @Test + void testPropagateTraceWithInstrumentedAsyncConcurrentOperations() throws Exception { + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry(registry); + spec.handlers( + chain -> { + chain.get( + "bar", + ctx -> { + ctx.render("hi-bar"); + Blocking.op( + () -> + testing.runWithSpan( + "another-span", + () -> { + Span span = Span.current(); + span.addEvent("an-event"); + })) + .then(); + }); + chain.get( + "foo", + ctx -> { + ctx.render("hi-foo"); + Blocking.op( + () -> + testing.runWithSpan( + "a-span", + () -> { + Span span = Span.current(); + span.addEvent("an-event"); + })) + .then(); + }); + }); + }); + cleanup.deferCleanup(app); + + assertThat(app.getHttpClient().get("foo").getBody().getText()).isEqualTo("hi-foo"); + assertThat(app.getHttpClient().get("bar").getBody().getText()).isEqualTo("hi-bar"); + + testing.waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /foo") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/foo"), + equalTo(URL_PATH, "/foo"), + equalTo(URL_SCHEME, "http"), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_QUERY, ""), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L)), + span -> + span.hasName("a-span") + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()) + .hasEventsSatisfyingExactly( + event -> event.hasName("an-event").hasAttributes(Attributes.empty()))), + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET /bar") + .hasNoParent() + .hasKind(SpanKind.SERVER) + .hasAttributesSatisfyingExactly( + equalTo(HTTP_ROUTE, "/bar"), + equalTo(URL_PATH, "/bar"), + equalTo(URL_SCHEME, "http"), + equalTo(SERVER_PORT, app.getServer().getBindPort()), + equalTo(SERVER_ADDRESS, "localhost"), + equalTo(NETWORK_PROTOCOL_VERSION, "1.1"), + equalTo(URL_QUERY, ""), + equalTo(HTTP_REQUEST_METHOD, "GET"), + equalTo(HTTP_RESPONSE_STATUS_CODE, 200L)), + span -> + span.hasName("another-span") + .hasParent(trace.getSpan(0)) + .hasAttributes(Attributes.empty()) + .hasEventsSatisfyingExactly( + event -> event.hasName("an-event").hasAttributes(Attributes.empty())))); + } +}