Skip to content

Commit

Permalink
Ratpack services OpenTelemetry (#7477)
Browse files Browse the repository at this point in the history
While using Ratpack Handlers, the context is added by the
`OpenTelemetryServerHandler`

While using Ratpack Services, we might need to add manually the OT
Context into the Ratpack Execution and try to get it from the Execution
in the Ratpack `HttpClient`
  • Loading branch information
jsalinaspolo authored Jan 25, 2023
1 parent fc81be4 commit 99e600f
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public HttpClient instrument(HttpClient httpClient) throws Exception {
httpClientSpec -> {
httpClientSpec.requestIntercept(
requestSpec -> {
Context parentOtelCtx = Context.current();
Context parentOtelCtx =
Execution.current().maybeGet(Context.class).orElse(Context.current());
if (!instrumenter.shouldStart(parentOtelCtx, requestSpec)) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,25 @@

package io.opentelemetry.instrumentation.ratpack.client

import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.trace.StatusCode
import io.opentelemetry.api.trace.Tracer
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator
import io.opentelemetry.context.Context
import io.opentelemetry.context.propagation.ContextPropagators
import io.opentelemetry.instrumentation.ratpack.RatpackTelemetry
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.Execution
import ratpack.exec.Promise
import ratpack.func.Action
import ratpack.guice.Guice
import ratpack.http.client.HttpClient
import ratpack.service.Service
import ratpack.service.StartEvent
import ratpack.test.embed.EmbeddedApp
import spock.lang.Specification
import spock.util.concurrent.PollingConditions
Expand All @@ -27,6 +32,8 @@ import java.time.Duration
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit

import static io.opentelemetry.api.trace.SpanKind.CLIENT
import static io.opentelemetry.api.trace.SpanKind.SERVER
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_ROUTE
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE
Expand Down Expand Up @@ -84,14 +91,14 @@ class InstrumentedHttpClientTest extends Specification {

new PollingConditions().eventually {
def spanData = spanExporter.finishedSpanItems.find { it.name == "/foo" }
def spanClientData = spanExporter.finishedSpanItems.find { it.name == "HTTP GET" && it.kind == SpanKind.CLIENT }
def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "/bar" && it.kind == SpanKind.SERVER }
def spanClientData = spanExporter.finishedSpanItems.find { it.name == "HTTP GET" && it.kind == CLIENT }
def spanDataApi = spanExporter.finishedSpanItems.find { it.name == "/bar" && it.kind == SERVER }

spanData.traceId == spanClientData.traceId
spanData.traceId == spanDataApi.traceId

spanData.kind == SpanKind.SERVER
spanClientData.kind == SpanKind.CLIENT
spanData.kind == SERVER
spanClientData.kind == CLIENT
def atts = spanClientData.attributes.asMap()
atts[HTTP_ROUTE] == "/bar"
atts[HTTP_METHOD] == "GET"
Expand Down Expand Up @@ -154,15 +161,15 @@ class InstrumentedHttpClientTest extends Specification {
spanData.traceId == spanClientData1.traceId
spanData.traceId == spanClientData2.traceId

spanData.kind == SpanKind.SERVER
spanData.kind == SERVER

spanClientData1.kind == SpanKind.CLIENT
spanClientData1.kind == CLIENT
def atts = spanClientData1.attributes.asMap()
atts[HTTP_ROUTE] == "/foo"
atts[HTTP_METHOD] == "GET"
atts[HTTP_STATUS_CODE] == 200L

spanClientData2.kind == SpanKind.CLIENT
spanClientData2.kind == CLIENT
def atts2 = spanClientData2.attributes.asMap()
atts2[HTTP_ROUTE] == "/bar"
atts2[HTTP_METHOD] == "GET"
Expand Down Expand Up @@ -207,17 +214,16 @@ class InstrumentedHttpClientTest extends Specification {
}
}

app.test { httpClient -> "error" == httpClient.get("path-name").body.text
}
app.test { httpClient -> "error" == httpClient.get("path-name").body.text }

new PollingConditions().eventually {
def spanData = spanExporter.finishedSpanItems.find { it.name == "/path-name" }
def spanClientData = spanExporter.finishedSpanItems.find { it.name == "HTTP GET" }

spanData.traceId == spanClientData.traceId

spanData.kind == SpanKind.SERVER
spanClientData.kind == SpanKind.CLIENT
spanData.kind == SERVER
spanClientData.kind == CLIENT
def atts = spanClientData.attributes.asMap()
atts[HTTP_ROUTE] == "/foo"
atts[HTTP_METHOD] == "GET"
Expand All @@ -232,4 +238,139 @@ class InstrumentedHttpClientTest extends Specification {
attributes[HTTP_STATUS_CODE] == 200L
}
}

def "propagate http trace in ratpack services with compute thread"() {
expect:
def latch = new CountDownLatch(1)

def otherApp = EmbeddedApp.of { spec ->
spec.handlers {
it.get("foo") { ctx -> ctx.render("bar") }
}
}

def app = EmbeddedApp.of { spec ->
spec.registry(
Guice.registry { bindings ->
telemetry.configureServerRegistry(bindings)
bindings.bindInstance(HttpClient, telemetry.instrumentHttpClient(HttpClient.of(Action.noop())))
bindings.bindInstance(new BarService(latch, "${otherApp.address}foo", openTelemetry))
},
)
spec.handlers { chain ->
chain.get("foo") { ctx -> ctx.render("bar") }
}
}

app.address
latch.await()
new PollingConditions().eventually {
def spanData = spanExporter.finishedSpanItems.find { it.name == "a-span" }
def trace = spanExporter.finishedSpanItems.findAll { it.traceId == spanData.traceId }

trace.size() == 3
}
}

def "propagate http trace in ratpack services with fork executions"() {
expect:
def latch = new CountDownLatch(1)

def otherApp = EmbeddedApp.of { spec ->
spec.handlers {
it.get("foo") { ctx -> ctx.render("bar") }
}
}

def app = EmbeddedApp.of { spec ->
spec.registry(
Guice.registry { bindings ->
telemetry.configureServerRegistry(bindings)
bindings.bindInstance(HttpClient, telemetry.instrumentHttpClient(HttpClient.of(Action.noop())))
bindings.bindInstance(new BarForkService(latch, "${otherApp.address}foo", openTelemetry))
},
)
spec.handlers { chain ->
chain.get("foo") { ctx -> ctx.render("bar") }
}
}

app.address
latch.await()
new PollingConditions().eventually {
def spanData = spanExporter.finishedSpanItems.find { it.name == "a-span" }
def trace = spanExporter.finishedSpanItems.findAll { it.traceId == spanData.traceId }

trace.size() == 3
}
}
}

class BarService implements Service {
private final String url
private final CountDownLatch latch
private final OpenTelemetry opentelemetry

BarService(CountDownLatch latch, String url, OpenTelemetry opentelemetry) {
this.latch = latch
this.url = url
this.opentelemetry = opentelemetry
}

private Tracer tracer = opentelemetry.tracerProvider.tracerBuilder("testing").build()

void onStart(StartEvent event) {
def parentContext = Context.current()
def span = tracer.spanBuilder("a-span")
.setParent(parentContext)
.startSpan()

Context otelContext = parentContext.with(span)
otelContext.makeCurrent().withCloseable {
Execution.current().add(Context, otelContext)
def httpClient = event.registry.get(HttpClient)
httpClient.get(new URI(url))
.flatMap { httpClient.get(new URI(url)) }
.then {
span.end()
latch.countDown()
}
}
}
}


class BarForkService implements Service {
private final String url
private final CountDownLatch latch
private final OpenTelemetry opentelemetry

BarForkService(CountDownLatch latch, String url, OpenTelemetry opentelemetry) {
this.latch = latch
this.url = url
this.opentelemetry = opentelemetry
}

private Tracer tracer = opentelemetry.tracerProvider.tracerBuilder("testing").build()

void onStart(StartEvent event) {
Execution.fork().start {
def parentContext = Context.current()
def span = tracer.spanBuilder("a-span")
.setParent(parentContext)
.startSpan()

Context otelContext = parentContext.with(span)
otelContext.makeCurrent().withCloseable {
Execution.current().add(Context, otelContext)
def httpClient = event.registry.get(HttpClient)
httpClient.get(new URI(url))
.flatMap { httpClient.get(new URI(url)) }
.then {
span.end()
latch.countDown()
}
}
}
}
}

0 comments on commit 99e600f

Please sign in to comment.