Support ratpack functional tests (#4605)
* Support manual initialization of OpenTelemetryServerHandler for ratpack functional tests * Use getters to do not expose opentelemetry implementations of ExecInterceptor and Handlers * Update instrumentation/ratpack-1.4/library/build.gradle.kts Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com> * Make OpenTelemtryServerHandler public to be binded from Guice and use directly in Ratpack Chain * Add documentation to getters methods to support Ratpack Registry bindings * Fix checkstyle in javadoc Co-authored-by: Mateusz Rzeszutek <mrzeszutek@splunk.com>
This commit is contained in:
parent
bcae1b11d3
commit
f86312e277
|
@ -7,6 +7,7 @@ dependencies {
|
||||||
library("io.ratpack:ratpack-core:1.4.0")
|
library("io.ratpack:ratpack-core:1.4.0")
|
||||||
|
|
||||||
testImplementation(project(":instrumentation:ratpack-1.4:testing"))
|
testImplementation(project(":instrumentation:ratpack-1.4:testing"))
|
||||||
|
testLibrary("io.ratpack:ratpack-guice:1.4.0")
|
||||||
|
|
||||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) {
|
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) {
|
||||||
testImplementation("com.sun.activation:jakarta.activation:1.2.2")
|
testImplementation("com.sun.activation:jakarta.activation:1.2.2")
|
||||||
|
|
|
@ -15,7 +15,7 @@ import ratpack.handling.Handler;
|
||||||
import ratpack.http.Request;
|
import ratpack.http.Request;
|
||||||
import ratpack.http.Response;
|
import ratpack.http.Response;
|
||||||
|
|
||||||
final class OpenTelemetryServerHandler implements Handler {
|
public final class OpenTelemetryServerHandler implements Handler {
|
||||||
|
|
||||||
private final Instrumenter<Request, Response> instrumenter;
|
private final Instrumenter<Request, Response> instrumenter;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.ratpack;
|
||||||
|
|
||||||
import io.opentelemetry.api.OpenTelemetry;
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import ratpack.exec.ExecInterceptor;
|
||||||
import ratpack.handling.HandlerDecorator;
|
import ratpack.handling.HandlerDecorator;
|
||||||
import ratpack.http.Request;
|
import ratpack.http.Request;
|
||||||
import ratpack.http.Response;
|
import ratpack.http.Response;
|
||||||
|
@ -46,6 +47,16 @@ public final class RatpackTracing {
|
||||||
serverHandler = new OpenTelemetryServerHandler(serverInstrumenter);
|
serverHandler = new OpenTelemetryServerHandler(serverInstrumenter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns instance of {@link OpenTelemetryServerHandler} to support Ratpack Registry binding. */
|
||||||
|
public OpenTelemetryServerHandler getOpenTelemetryServerHandler() {
|
||||||
|
return serverHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns instance of {@link ExecInterceptor} to support Ratpack Registry binding. */
|
||||||
|
public ExecInterceptor getOpenTelemetryExecInterceptor() {
|
||||||
|
return OpenTelemetryExecInterceptor.INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
/** Configures the {@link RegistrySpec} with OpenTelemetry. */
|
/** Configures the {@link RegistrySpec} with OpenTelemetry. */
|
||||||
public void configureServerRegistry(RegistrySpec registry) {
|
public void configureServerRegistry(RegistrySpec registry) {
|
||||||
registry.add(HandlerDecorator.prepend(serverHandler));
|
registry.add(HandlerDecorator.prepend(serverHandler));
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ratpack
|
||||||
|
|
||||||
|
|
||||||
|
import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter
|
||||||
|
import io.opentelemetry.sdk.trace.export.SpanExporter
|
||||||
|
import ratpack.impose.ForceDevelopmentImposition
|
||||||
|
import ratpack.impose.ImpositionsSpec
|
||||||
|
import ratpack.impose.UserRegistryImposition
|
||||||
|
import ratpack.registry.Registry
|
||||||
|
import ratpack.test.MainClassApplicationUnderTest
|
||||||
|
|
||||||
|
class RatpackFunctionalTest extends MainClassApplicationUnderTest {
|
||||||
|
|
||||||
|
Registry registry
|
||||||
|
@Lazy InMemorySpanExporter spanExporter = registry.get(SpanExporter) as InMemorySpanExporter
|
||||||
|
|
||||||
|
RatpackFunctionalTest(Class<?> mainClass) {
|
||||||
|
super(mainClass)
|
||||||
|
getAddress()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void addImpositions(ImpositionsSpec impositions) {
|
||||||
|
impositions.add(ForceDevelopmentImposition.of(false))
|
||||||
|
impositions.add(UserRegistryImposition.of { r ->
|
||||||
|
registry = r
|
||||||
|
registry
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ratpack.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.OpenTelemetryServerHandler
|
||||||
|
import io.opentelemetry.instrumentation.ratpack.RatpackFunctionalTest
|
||||||
|
import io.opentelemetry.instrumentation.ratpack.RatpackTracing
|
||||||
|
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 io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
||||||
|
import ratpack.exec.ExecInterceptor
|
||||||
|
import ratpack.guice.Guice
|
||||||
|
import ratpack.server.RatpackServer
|
||||||
|
import spock.lang.Specification
|
||||||
|
import spock.util.concurrent.PollingConditions
|
||||||
|
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
class RatpackServerApplicationTest extends Specification {
|
||||||
|
|
||||||
|
def app = new RatpackFunctionalTest(RatpackApp)
|
||||||
|
|
||||||
|
def "add span on handlers"() {
|
||||||
|
expect:
|
||||||
|
app.test { httpClient -> "hi-foo" == httpClient.get("foo").body.text }
|
||||||
|
|
||||||
|
new PollingConditions().eventually {
|
||||||
|
def spanData = app.spanExporter.finishedSpanItems.find { it.name == "/foo" }
|
||||||
|
def attributes = spanData.attributes.asMap()
|
||||||
|
|
||||||
|
spanData.kind == SpanKind.SERVER
|
||||||
|
attributes[SemanticAttributes.HTTP_ROUTE] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_TARGET] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_METHOD] == "GET"
|
||||||
|
attributes[SemanticAttributes.HTTP_STATUS_CODE] == 200L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "ignore handlers before OpenTelemetryServerHandler"() {
|
||||||
|
expect:
|
||||||
|
app.test { httpClient -> "ignored" == httpClient.get("ignore").body.text }
|
||||||
|
|
||||||
|
new PollingConditions(initialDelay: 0.1, timeout: 0.3).eventually {
|
||||||
|
!app.spanExporter.finishedSpanItems.any { it.name == "/ignore" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@CompileStatic
|
||||||
|
class OpenTelemetryModule extends AbstractModule {
|
||||||
|
@Override
|
||||||
|
protected void configure() {
|
||||||
|
bind(SpanExporter).toInstance(InMemorySpanExporter.create())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
RatpackTracing ratpackTracing(OpenTelemetry openTelemetry) {
|
||||||
|
return RatpackTracing.create(openTelemetry)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
OpenTelemetryServerHandler ratpackServerHandler(RatpackTracing ratpackTracing) {
|
||||||
|
return ratpackTracing.getOpenTelemetryServerHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
ExecInterceptor ratpackExecInterceptor(RatpackTracing ratpackTracing) {
|
||||||
|
return ratpackTracing.getOpenTelemetryExecInterceptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
OpenTelemetry providesOpenTelemetry(SpanExporter spanExporter) {
|
||||||
|
def tracerProvider = SdkTracerProvider.builder()
|
||||||
|
.addSpanProcessor(SimpleSpanProcessor.create(spanExporter))
|
||||||
|
.build()
|
||||||
|
return OpenTelemetrySdk.builder().setTracerProvider(tracerProvider).build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CompileStatic
|
||||||
|
class RatpackApp {
|
||||||
|
|
||||||
|
static void main(String... args) {
|
||||||
|
RatpackServer.start { server ->
|
||||||
|
server
|
||||||
|
.registry(
|
||||||
|
Guice.registry { bindings ->
|
||||||
|
bindings
|
||||||
|
.module(OpenTelemetryModule)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.handlers { chain ->
|
||||||
|
chain
|
||||||
|
.get("ignore") { ctx -> ctx.render("ignored") }
|
||||||
|
.all(OpenTelemetryServerHandler)
|
||||||
|
.get("foo") { ctx -> ctx.render("hi-foo") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.ratpack.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.RatpackTracing
|
||||||
|
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.trace.attributes.SemanticAttributes
|
||||||
|
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 ratpackTracing = RatpackTracing.create(openTelemetry)
|
||||||
|
|
||||||
|
def cleanup() {
|
||||||
|
spanExporter.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
def "add span on handlers"() {
|
||||||
|
given:
|
||||||
|
def app = EmbeddedApp.of { spec ->
|
||||||
|
spec.registry { Registry.of { ratpackTracing.configureServerRegistry(it) } }
|
||||||
|
spec.handlers { chain ->
|
||||||
|
chain.get("foo") { ctx -> ctx.render("hi-foo") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when:
|
||||||
|
app.test { httpClient -> "hi-foo" == httpClient.get("foo").body.text }
|
||||||
|
|
||||||
|
then:
|
||||||
|
new PollingConditions().eventually {
|
||||||
|
def spanData = spanExporter.finishedSpanItems.find { it.name == "/foo" }
|
||||||
|
def attributes = spanData.attributes.asMap()
|
||||||
|
|
||||||
|
spanData.kind == SpanKind.SERVER
|
||||||
|
attributes[SemanticAttributes.HTTP_ROUTE] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_TARGET] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_METHOD] == "GET"
|
||||||
|
attributes[SemanticAttributes.HTTP_STATUS_CODE] == 200L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "propagate trace with instrumented async operations"() {
|
||||||
|
expect:
|
||||||
|
def app = EmbeddedApp.of { spec ->
|
||||||
|
spec.registry { Registry.of { ratpackTracing.configureServerRegistry(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 ->
|
||||||
|
"hi-foo" == httpClient.get("foo").body.text
|
||||||
|
|
||||||
|
new PollingConditions().eventually {
|
||||||
|
def spanData = spanExporter.finishedSpanItems.find { it.name == "/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[SemanticAttributes.HTTP_ROUTE] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_TARGET] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_METHOD] == "GET"
|
||||||
|
attributes[SemanticAttributes.HTTP_STATUS_CODE] == 200L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "propagate trace with instrumented async concurrent operations"() {
|
||||||
|
expect:
|
||||||
|
def app = EmbeddedApp.of { spec ->
|
||||||
|
spec.registry { Registry.of { ratpackTracing.configureServerRegistry(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 ->
|
||||||
|
"hi-foo" == httpClient.get("foo").body.text
|
||||||
|
"hi-bar" == httpClient.get("bar").body.text
|
||||||
|
new PollingConditions().eventually {
|
||||||
|
def spanData = spanExporter.finishedSpanItems.find { it.name == "/foo" }
|
||||||
|
def spanDataChild = spanExporter.finishedSpanItems.find { it.name == "a-span" }
|
||||||
|
|
||||||
|
def spanData2 = spanExporter.finishedSpanItems.find { it.name == "/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[SemanticAttributes.HTTP_ROUTE] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_TARGET] == "/foo"
|
||||||
|
attributes[SemanticAttributes.HTTP_METHOD] == "GET"
|
||||||
|
attributes[SemanticAttributes.HTTP_STATUS_CODE] == 200L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue