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