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")
|
||||
|
||||
testImplementation(project(":instrumentation:ratpack-1.4:testing"))
|
||||
testLibrary("io.ratpack:ratpack-guice:1.4.0")
|
||||
|
||||
if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11)) {
|
||||
testImplementation("com.sun.activation:jakarta.activation:1.2.2")
|
||||
|
|
|
@ -15,7 +15,7 @@ import ratpack.handling.Handler;
|
|||
import ratpack.http.Request;
|
||||
import ratpack.http.Response;
|
||||
|
||||
final class OpenTelemetryServerHandler implements Handler {
|
||||
public final class OpenTelemetryServerHandler implements Handler {
|
||||
|
||||
private final Instrumenter<Request, Response> instrumenter;
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ package io.opentelemetry.instrumentation.ratpack;
|
|||
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||
import ratpack.exec.ExecInterceptor;
|
||||
import ratpack.handling.HandlerDecorator;
|
||||
import ratpack.http.Request;
|
||||
import ratpack.http.Response;
|
||||
|
@ -46,6 +47,16 @@ public final class RatpackTracing {
|
|||
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. */
|
||||
public void configureServerRegistry(RegistrySpec registry) {
|
||||
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