From 5ed553e892b1440150f7e376045e4348f5df3f20 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 30 Dec 2024 20:32:30 -0500 Subject: [PATCH] finish converting --- .../ratpack/v1_7/RatpackFunctionalTest.groovy | 45 --- .../client/InstrumentedHttpClientTest.groovy | 377 ------------------ .../RatpackServerApplicationTest.groovy | 169 -------- .../v1_7/server/RatpackServerTest.groovy | 163 -------- .../ratpack/v1_7/AbstractRatpackTest.java | 46 +++ .../ratpack/v1_7/client/BarForkService.java | 56 +++ .../ratpack/v1_7/client/BarService.java | 53 +++ .../client/InstrumentedHttpClientTest.java | 357 +++++++++++++++++ .../v1_7/server/OpenTelemetryModule.java | 76 ++++ .../ratpack/v1_7/server/RatpackApp.java | 35 ++ .../v1_7/server/RatpackFunctionalTest.java | 47 +++ .../server/RatpackServerApplicationTest.java | 109 +++++ .../v1_7/server/RatpackServerTest.java | 222 +++++++++-- 13 files changed, 961 insertions(+), 794 deletions(-) delete mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/RatpackFunctionalTest.groovy delete mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.groovy delete mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.groovy delete mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/groovy/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerTest.groovy create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/AbstractRatpackTest.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarForkService.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarService.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/OpenTelemetryModule.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackApp.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackFunctionalTest.java create mode 100644 instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.java 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 a997419d8730..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 RatpackServerTest2 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/AbstractRatpackTest.java b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/AbstractRatpackTest.java new file mode 100644 index 000000000000..5b1888164a8f --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/AbstractRatpackTest.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import org.junit.jupiter.api.AfterEach; + +public abstract class AbstractRatpackTest { + protected final InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); + private final SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + + protected OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CBaggagePropagator.getInstance())) + .setTracerProvider(tracerProvider) + .build(); + + protected RatpackClientTelemetry telemetry = RatpackClientTelemetry.create(openTelemetry); + protected RatpackServerTelemetry serverTelemetry = RatpackServerTelemetry.create(openTelemetry); + + @AfterEach + void cleanup() { + spanExporter.reset(); + } + + protected SpanData findSpanData(String spanName, SpanKind spanKind) { + return spanExporter.getFinishedSpanItems().stream() + .filter(span -> spanName.equals(span.getName()) && span.getKind().equals(spanKind)) + .findFirst() + .orElseThrow(() -> new AssertionError("Span not found")); + } +} 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..7dbb2d808a4d --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarForkService.java @@ -0,0 +1,56 @@ +/* + * 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.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.net.URI; +import java.util.concurrent.CountDownLatch; +import ratpack.exec.Execution; +import ratpack.exec.Operation; +import ratpack.http.client.HttpClient; +import ratpack.service.Service; +import ratpack.service.StartEvent; + +public class BarForkService implements Service { + private final String url; + private final CountDownLatch latch; + private final Tracer tracer; + + public BarForkService(CountDownLatch latch, String url, OpenTelemetry openTelemetry) { + this.latch = latch; + this.url = url; + this.tracer = openTelemetry.getTracerProvider().tracerBuilder("testing").build(); + } + + @Override + public void onStart(StartEvent event) { + Execution.fork() + .start( + Operation.of( + () -> { + Context parentContext = Context.current(); + Span span = tracer.spanBuilder("a-span").setParent(parentContext).startSpan(); + + 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(); + latch.countDown(); + }); + } + })); + } +} 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..2a3e374c6ea3 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/BarService.java @@ -0,0 +1,53 @@ +/* + * 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.Span; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.CountDownLatch; +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 CountDownLatch latch; + private final Tracer tracer; + + public BarService(CountDownLatch latch, String url, OpenTelemetry openTelemetry) { + this.latch = latch; + this.url = url; + this.tracer = openTelemetry.getTracerProvider().tracerBuilder("testing").build(); + } + + @Override + public void onStart(StartEvent event) { + Context parentContext = Context.current(); + Span span = tracer.spanBuilder("a-span").setParent(parentContext).startSpan(); + + 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(); + latch.countDown(); + }); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } +} 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..14c666365ea3 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/client/InstrumentedHttpClientTest.java @@ -0,0 +1,357 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.client; + +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.UrlAttributes.URL_PATH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.instrumentation.ratpack.v1_7.AbstractRatpackTest; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.net.URI; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import ratpack.exec.Promise; +import ratpack.func.Action; +import ratpack.guice.Guice; +import ratpack.http.client.HttpClient; +import ratpack.test.embed.EmbeddedApp; + +class InstrumentedHttpClientTest extends AbstractRatpackTest { + + @Test + void testPropagateTraceWithHttpCalls() throws Exception { + EmbeddedApp otherApp = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry(bindings -> serverTelemetry.configureRegistry(bindings))); + spec.handlers(chain -> chain.get("bar", ctx -> ctx.render("foo"))); + }); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, telemetry.instrument(HttpClient.of(Action.noop()))); + })); + + spec.handlers( + chain -> + chain.get( + "foo", + ctx -> { + HttpClient instrumentedHttpClient = ctx.get(HttpClient.class); + instrumentedHttpClient + .get(new URI(otherApp.getAddress().toString() + "bar")) + .then(response -> ctx.render("bar")); + })); + }); + + app.test( + httpClient -> { + assertThat(httpClient.get("foo").getBody().getText()).isEqualTo("bar"); + + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("GET /foo", SpanKind.SERVER); + SpanData spanClientData = findSpanData("GET", SpanKind.CLIENT); + SpanData spanDataApi = findSpanData("GET /bar", SpanKind.SERVER); + + assertThat(spanData.getTraceId()).isEqualTo(spanClientData.getTraceId()); + + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(spanClientData.getKind()).isEqualTo(SpanKind.CLIENT); + + Map, Object> clientAttributes = + spanClientData.getAttributes().asMap(); + assertThat(clientAttributes.get(HTTP_ROUTE)).isEqualTo("/bar"); + assertThat(clientAttributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(clientAttributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + + Map, Object> attributes = spanData.getAttributes().asMap(); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/foo"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + + Map, Object> apiAttributes = + spanDataApi.getAttributes().asMap(); + assertThat(apiAttributes.get(HTTP_ROUTE)).isEqualTo("/bar"); + assertThat(apiAttributes.get(URL_PATH)).isEqualTo("/bar"); + assertThat(apiAttributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(apiAttributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } + + @Test + void testAddSpansForMultipleConcurrentClientCalls() throws Exception { + CountDownLatch latch = new CountDownLatch(2); + + EmbeddedApp otherApp = + EmbeddedApp.of( + spec -> + spec.handlers( + chain -> { + chain.get("foo", ctx -> ctx.render("bar")); + chain.get("bar", ctx -> ctx.render("foo")); + })); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, telemetry.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().toString() + "foo")) + .then(response -> latch.countDown()); + instrumentedHttpClient + .get(new URI(otherApp.getAddress().toString() + "bar")) + .then(response -> latch.countDown()); + })); + }); + + app.test( + httpClient -> { + assertThat(httpClient.get("path-name").getBody().getText()).isEqualTo("hello"); + latch.await(1, TimeUnit.SECONDS); + + await() + .untilAsserted( + () -> { + assertThat(spanExporter.getFinishedSpanItems().size()).isEqualTo(3); + + SpanData spanData = findSpanData("GET /path-name", SpanKind.SERVER); + + SpanData spanClientData1 = + spanExporter.getFinishedSpanItems().stream() + .filter( + span -> + "GET".equals(span.getName()) + && span.getAttributes() + .asMap() + .get(HTTP_ROUTE) + .equals("/foo")) + .findFirst() + .orElseThrow(() -> new AssertionError("Span not found")); + + SpanData spanClientData2 = + spanExporter.getFinishedSpanItems().stream() + .filter( + span -> + "GET".equals(span.getName()) + && span.getAttributes() + .asMap() + .get(HTTP_ROUTE) + .equals("/bar")) + .findFirst() + .orElseThrow(() -> new AssertionError("Span not found")); + + assertThat(spanData.getTraceId()).isEqualTo(spanClientData1.getTraceId()); + assertThat(spanData.getTraceId()).isEqualTo(spanClientData2.getTraceId()); + + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + + assertThat(spanClientData1.getKind()).isEqualTo(SpanKind.CLIENT); + Map, Object> clientAttributes = + spanClientData1.getAttributes().asMap(); + assertThat(clientAttributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(clientAttributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(clientAttributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + + assertThat(spanClientData2.getKind()).isEqualTo(SpanKind.CLIENT); + Map, Object> client2Attributes = + spanClientData2.getAttributes().asMap(); + assertThat(client2Attributes.get(HTTP_ROUTE)).isEqualTo("/bar"); + assertThat(client2Attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(client2Attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + + Map, Object> attributes = spanData.getAttributes().asMap(); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/path-name"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/path-name"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } + + @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"))))); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, + telemetry.instrument( + HttpClient.of(s -> s.readTimeout(Duration.ofMillis(10))))); + })); + + spec.handlers( + chain -> + chain.get( + "path-name", + ctx -> { + HttpClient instrumentedHttpClient = ctx.get(HttpClient.class); + instrumentedHttpClient + .get(new URI(otherApp.getAddress().toString() + "foo")) + .onError(throwable -> ctx.render("error")) + .then(response -> ctx.render("hello")); + })); + }); + + app.test( + httpClient -> { + assertThat(httpClient.get("path-name").getBody().getText()).isEqualTo("error"); + + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("GET /path-name", SpanKind.SERVER); + SpanData spanClientData = findSpanData("GET", SpanKind.CLIENT); + + assertThat(spanData.getTraceId()).isEqualTo(spanClientData.getTraceId()); + + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(spanClientData.getKind()).isEqualTo(SpanKind.CLIENT); + Map, Object> clientAttributes = + spanClientData.getAttributes().asMap(); + assertThat(clientAttributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(clientAttributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(clientAttributes.get(HTTP_RESPONSE_STATUS_CODE)).isNull(); + assertThat(spanClientData.getStatus().getStatusCode()) + .isEqualTo(StatusCode.ERROR); + assertThat(spanClientData.getEvents().stream().findFirst().get().getName()) + .isEqualTo("exception"); + + Map, Object> attributes = spanData.getAttributes().asMap(); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/path-name"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/path-name"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } + + @Test + void testPropagateHttpTraceInRatpackServicesWithComputeThread() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + + EmbeddedApp otherApp = + EmbeddedApp.of(spec -> spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("bar")))); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, telemetry.instrument(HttpClient.of(Action.noop()))); + bindings.bindInstance( + new BarService( + latch, otherApp.getAddress().toString() + "foo", openTelemetry)); + })); + + spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("bar"))); + }); + + app.getAddress(); + latch.await(); + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("a-span", SpanKind.INTERNAL); + assertThat( + spanExporter.getFinishedSpanItems().stream() + .filter(span -> span.getTraceId().equals(spanData.getTraceId())) + .count()) + .isEqualTo(3); + }); + } + + @Test + void propagateHttpTraceInRatpackServicesWithForkExecutions() throws Exception { + CountDownLatch latch = new CountDownLatch(1); + + EmbeddedApp otherApp = + EmbeddedApp.of(spec -> spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("bar")))); + + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + Guice.registry( + bindings -> { + serverTelemetry.configureRegistry(bindings); + bindings.bindInstance( + HttpClient.class, telemetry.instrument(HttpClient.of(Action.noop()))); + bindings.bindInstance( + new BarForkService( + latch, otherApp.getAddress().toString() + "foo", openTelemetry)); + })); + + spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("bar"))); + }); + + app.getAddress(); + latch.await(); + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("a-span", SpanKind.INTERNAL); + assertThat( + spanExporter.getFinishedSpanItems().stream() + .filter(span -> span.getTraceId().equals(spanData.getTraceId())) + .count()) + .isEqualTo(3); + }); + } +} 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..0102653459ec --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/OpenTelemetryModule.java @@ -0,0 +1,76 @@ +/* + * 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.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 javax.inject.Singleton; +import ratpack.exec.ExecInitializer; +import ratpack.exec.ExecInterceptor; +import ratpack.func.Action; +import ratpack.handling.Handler; +import ratpack.http.client.HttpClient; + +public class OpenTelemetryModule extends AbstractModule { + @Override + protected void configure() { + bind(SpanExporter.class).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) { + SdkTracerProvider tracerProvider = + SdkTracerProvider.builder() + .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) + .build(); + return OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build(); + } + + @Singleton + @Provides + HttpClient instrumentedHttpClient(RatpackClientTelemetry ratpackTracing) throws Exception { + return ratpackTracing.instrument(HttpClient.of(Action.noop())); + } + + @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..ac73972fd063 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackApp.java @@ -0,0 +1,35 @@ +/* + * 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..246511c498d4 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackFunctionalTest.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.net.URI; +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; + +public class RatpackFunctionalTest extends MainClassApplicationUnderTest { + + private Registry registry; + protected InMemorySpanExporter spanExporter; + private final EmbeddedApp app; + + public RatpackFunctionalTest(Class mainClass) throws Exception { + super(mainClass); + this.app = + EmbeddedApp.of( + server -> server.handlers(chain -> chain.get("other", ctx -> ctx.render("hi-other")))); + getAddress(); + } + + @Override + protected void addImpositions(ImpositionsSpec impositions) { + impositions.add(ForceDevelopmentImposition.of(false)); + impositions.add( + UserRegistryImposition.of( + r -> { + registry = r; + spanExporter = (InMemorySpanExporter) registry.get(SpanExporter.class); + return registry; + })); + impositions.add( + BindingsImposition.of( + bindings -> bindings.bindInstance(URI.class, app.getAddress().resolve("other")))); + } +} 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..54106f7361e7 --- /dev/null +++ b/instrumentation/ratpack/ratpack-1.7/library/src/test/java/io/opentelemetry/instrumentation/ratpack/v1_7/server/RatpackServerApplicationTest.java @@ -0,0 +1,109 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.ratpack.v1_7.server; + +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.UrlAttributes.URL_PATH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class RatpackServerApplicationTest { + + private final RatpackFunctionalTest app = new RatpackFunctionalTest(RatpackApp.class); + + @Test + void testAddSpanOnHandlers() throws Exception { + app.test( + httpClient -> { + assertThat(httpClient.get("foo").getBody().getText()).isEqualTo("hi-foo"); + + await() + .untilAsserted( + () -> { + SpanData spanData = + app.spanExporter.getFinishedSpanItems().stream() + .filter(span -> "GET /foo".equals(span.getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("Span not found")); + + Map, Object> attributes = spanData.getAttributes().asMap(); + + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/foo"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } + + @Test + void testPropagateTraceToHttpCalls() throws Exception { + app.test( + httpClient -> { + assertThat(httpClient.get("bar").getBody().getText()).isEqualTo("hi-bar"); + + await() + .untilAsserted( + () -> { + SpanData spanData = + app.spanExporter.getFinishedSpanItems().stream() + .filter(span -> "GET /bar".equals(span.getName())) + .findFirst() + .orElseThrow(() -> new AssertionError("Span not found")); + + SpanData spanDataClient = + app.spanExporter.getFinishedSpanItems().stream() + .filter(span -> span.getName().equals("GET")) + .findFirst() + .orElseThrow(() -> new AssertionError("Span not found")); + + assertThat(spanData.getTraceId()).isEqualTo(spanDataClient.getTraceId()); + + Map, Object> attributes = spanData.getAttributes().asMap(); + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/bar"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/bar"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + + Map, Object> clientAttributes = + spanDataClient.getAttributes().asMap(); + + assertThat(spanDataClient.getKind()).isEqualTo(SpanKind.CLIENT); + assertThat(clientAttributes.get(HTTP_ROUTE)).isEqualTo("/other"); + assertThat(clientAttributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(clientAttributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } + + @Test + void testIgnoreHandlersBeforeOpenTelemetryServerHandler() throws Exception { + app.test( + httpClient -> { + assertThat(httpClient.get("ignore").getBody().getText()).isEqualTo("ignored"); + + await() + .untilAsserted( + () -> + assertThat( + app.spanExporter.getFinishedSpanItems().stream() + .noneMatch(span -> "GET /ignore".equals(span.getName()))) + .isTrue()); + }); + } + + RatpackServerApplicationTest() throws Exception {} +} 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 index 2e3fdad3d4ca..faedec80ee43 100644 --- 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 @@ -1,63 +1,205 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.ratpack.v1_7.server; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator; +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.UrlAttributes.URL_PATH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + import io.opentelemetry.api.common.AttributeKey; -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.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.ratpack.v1_7.AbstractRatpackTest; import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; -import org.junit.jupiter.api.AfterAll; +import java.util.Map; import org.junit.jupiter.api.Test; +import ratpack.exec.Blocking; import ratpack.registry.Registry; import ratpack.test.embed.EmbeddedApp; -import spock.util.concurrent.PollingConditions; -import java.util.Map; -import java.util.stream.Collectors; +class RatpackServerTest extends AbstractRatpackTest { -import static org.assertj.core.api.Assertions.assertThat; + @Test + void testAddSpanOnHandlers() throws Exception { + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + registry -> Registry.of(regSpec -> serverTelemetry.configureRegistry(regSpec))); + spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("hi-foo"))); + }); -class RatpackServerTest { + app.test( + httpClient -> { + assertThat(httpClient.get("foo").getBody().getText()).isEqualTo("hi-foo"); - private static InMemorySpanExporter spanExporter = InMemorySpanExporter.create(); - private SdkTracerProvider tracerProvider = SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(spanExporter)) - .build(); + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("GET /foo", SpanKind.SERVER); + Map, Object> attributes = spanData.getAttributes().asMap(); - OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() - .setPropagators(ContextPropagators.create(W3CBaggagePropagator.getInstance())) - .setTracerProvider(tracerProvider).build(); + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/foo"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } + + @Test + void testPropagateTraceWithInstrumentedAsyncOperations() throws Exception { + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + registry -> Registry.of(regSpec -> serverTelemetry.configureRegistry(regSpec))); + spec.handlers( + chain -> + chain.get( + "foo", + ctx -> { + ctx.render("hi-foo"); + Blocking.op( + () -> { + Span span = + openTelemetry + .getTracer("any-tracer") + .spanBuilder("a-span") + .startSpan(); + try (Scope scope = span.makeCurrent()) { + span.addEvent("an-event"); + } finally { + span.end(); + } + }) + .then(); + })); + }); + + app.test( + httpClient -> { + assertThat(httpClient.get("foo").getBody().getText()).isEqualTo("hi-foo"); - RatpackServerTelemetry telemetry = RatpackServerTelemetry.create(openTelemetry); + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("GET /foo", SpanKind.SERVER); + SpanData spanDataChild = findSpanData("a-span", SpanKind.INTERNAL); - @AfterAll - static void cleanup() { - spanExporter.reset(); + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(spanData.getTraceId()).isEqualTo(spanDataChild.getTraceId()); + assertThat(spanDataChild.getParentSpanId()).isEqualTo(spanData.getSpanId()); + assertThat( + spanDataChild.getEvents().stream() + .filter(event -> "an-event".equals(event.getName())) + .count()) + .isEqualTo(1); + + Map, Object> attributes = spanData.getAttributes().asMap(); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/foo"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); } @Test - void testAddSpanOnHandlers() throws Exception { - EmbeddedApp app = EmbeddedApp.of( - spec -> { - spec.registry(registry -> Registry.of(regSpec -> telemetry.configureRegistry(regSpec))); - spec.handlers(chain -> chain.get("foo", ctx -> ctx.render("hi-foo"))); - } - ); + void testPropagateTraceWithInstrumentedAsyncConcurrentOperations() throws Exception { + EmbeddedApp app = + EmbeddedApp.of( + spec -> { + spec.registry( + registry -> Registry.of(regSpec -> serverTelemetry.configureRegistry(regSpec))); + spec.handlers( + chain -> { + chain.get( + "bar", + ctx -> { + ctx.render("hi-bar"); + Blocking.op( + () -> { + Span span = + openTelemetry + .getTracer("any-tracer") + .spanBuilder("another-span") + .startSpan(); + try (Scope scope = span.makeCurrent()) { + span.addEvent("an-event"); + } finally { + span.end(); + } + }) + .then(); + }); + chain.get( + "foo", + ctx -> { + ctx.render("hi-foo"); + Blocking.op( + () -> { + Span span = + openTelemetry + .getTracer("any-tracer") + .spanBuilder("a-span") + .startSpan(); + try (Scope scope = span.makeCurrent()) { + span.addEvent("an-event"); + } finally { + span.end(); + } + }) + .then(); + }); + }); + }); - app.test( httpClient -> { - assertThat(httpClient.get("foo").getBody().getText()).isEqualTo("hi-foo"); - new PollingConditions().eventually(() -> { - Map spans = spanExporter.getFinishedSpanItems().stream() - .collect(Collectors.toMap(SpanData::getName, span -> span)); - assertThat(spans).containsKey("GET /foo"); + app.test( + httpClient -> { + assertThat(httpClient.get("foo").getBody().getText()).isEqualTo("hi-foo"); + assertThat(httpClient.get("bar").getBody().getText()).isEqualTo("hi-bar"); + await() + .untilAsserted( + () -> { + SpanData spanData = findSpanData("GET /foo", SpanKind.SERVER); + SpanData spanDataChild = findSpanData("a-span", SpanKind.INTERNAL); + SpanData spanData2 = findSpanData("GET /bar", SpanKind.SERVER); + SpanData spanDataChild2 = findSpanData("another-span", SpanKind.INTERNAL); - }); + assertThat(spanData.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(spanData.getTraceId()).isEqualTo(spanDataChild.getTraceId()); + assertThat(spanDataChild.getParentSpanId()).isEqualTo(spanData.getSpanId()); + assertThat( + spanDataChild.getEvents().stream() + .filter(event -> "an-event".equals(event.getName())) + .count()) + .isEqualTo(1); - } + assertThat(spanData2.getKind()).isEqualTo(SpanKind.SERVER); + assertThat(spanData2.getTraceId()).isEqualTo(spanDataChild2.getTraceId()); + assertThat(spanDataChild2.getParentSpanId()).isEqualTo(spanData2.getSpanId()); + assertThat( + spanDataChild2.getEvents().stream() + .filter(event -> "an-event".equals(event.getName())) + .count()) + .isEqualTo(1); + Map, Object> attributes = spanData.getAttributes().asMap(); + assertThat(attributes.get(HTTP_ROUTE)).isEqualTo("/foo"); + assertThat(attributes.get(URL_PATH)).isEqualTo("/foo"); + assertThat(attributes.get(HTTP_REQUEST_METHOD)).isEqualTo("GET"); + assertThat(attributes.get(HTTP_RESPONSE_STATUS_CODE)).isEqualTo(200L); + }); + }); + } }