diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f08e1ea --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM openjdk:11-slim as builder + +ENV OTEL_RESOURCE_ATTRIBUTES=service.name=ratpack-kotlin-otel +ENV OTEL_INSTRUMENTATION_KAFKA_ENABLED=false +ENV OTEL_EXPORTER_ZIPKIN_ENDPOINT=http://jaegerallinone:9411/api/v2/spans +ENV OTEL_PROPAGATORS=b3,tracecontext +ENV OTEL_METRICS_EXPORTER=none +ENV OTEL_INSTRUMENTATION_CASSANDRA_ENABLED=false +ENV OTEL_TRACES_EXPORTER=zipkin +ENV OTEL_INSTRUMENTATION_NETTY_ALWAYS_CREATE_SPAN=false +ENV RATPACK_SERVER__ADDRESS=0.0.0.0 + +COPY run.sh / +COPY build/distributions/*.tar / +COPY otel /otel +RUN cd /tmp/ \ + && tar xvf /*.tar \ + && mv ratpack-kotlin-otel* /src +WORKDIR /src + +ENTRYPOINT ["/run.sh"] diff --git a/README.md b/README.md index f075c31..238f71e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # ratpack-kotlin-otel Simple project to debug ratpack+kotlin+otel libraries + +# Dependent services +``` +docker-compose up +``` + +View traces in [Jaeger UI](http://localhost:16686) diff --git a/docker-compose.yml b/docker-compose.yml index 9b2139a..a9755fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,3 +20,8 @@ services: build: downstream-app ports: - "9999:9999" + + otel-ratpack-kotlin: + build: . + ports: + - "5050:5050" diff --git a/downstream-app/README.md b/downstream-app/README.md new file mode 100644 index 0000000..5a374ca --- /dev/null +++ b/downstream-app/README.md @@ -0,0 +1,9 @@ +# Simple api to do timeouts easily + +``` +docker build -t testing . && docker run --rm -it -p 9999:9999 testing +``` + +``` +curl http://localhost:9999/api/v1/timeout?duration=10s +``` diff --git a/downstream-app/main.go b/downstream-app/main.go index c5db0d4..b12b208 100644 --- a/downstream-app/main.go +++ b/downstream-app/main.go @@ -25,8 +25,14 @@ func timeoutHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) } +func healthHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("{\"status\": \"up\"}")) + w.WriteHeader(200) +} + func addRoutes() { http.HandleFunc("/api/v1/timeout", timeoutHandler) + http.HandleFunc("/health", healthHandler) } func main() { diff --git a/otel/otel.1.10.1.jar b/otel/otel.1.10.1.jar new file mode 100644 index 0000000..e1d1f0b Binary files /dev/null and b/otel/otel.1.10.1.jar differ diff --git a/otel/otel.jar b/otel/otel.jar index 0311e08..e613dcd 120000 --- a/otel/otel.jar +++ b/otel/otel.jar @@ -1 +1 @@ -otel.1.5.0.jar \ No newline at end of file +otel.1.10.1.jar \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..2ce51fb --- /dev/null +++ b/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +# Why does java have to be so hard? + +java -javaagent:/otel/otel.jar -classpath /src/app:$(find /src/lib | xargs | tr ' ' ':') sample.MainKt diff --git a/src/main/kotlin/exampleapp/Main.kt b/src/main/kotlin/exampleapp/Main.kt index 742f994..6738179 100644 --- a/src/main/kotlin/exampleapp/Main.kt +++ b/src/main/kotlin/exampleapp/Main.kt @@ -18,6 +18,7 @@ import ratpack.service.StartEvent import ratpackkotlinotel.promisesession.DefaultPromiseSession import ratpackkotlinotel.promisesession.PromiseSession import ratpackkotlinotel.promisesession.PromiseSessionProvider +import java.net.InetAddress import java.net.InetSocketAddress import javax.inject.Inject import javax.inject.Singleton @@ -30,6 +31,10 @@ fun main() { fun app(bindings: List = listOf()) = ratpack { System.setProperty("log4j2.contextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector") + serverConfig { + env() + } + bindings { for (b in bindings) { bindInstance(b) diff --git a/src/main/kotlin/exampleapp/data/RefreshItem.kt b/src/main/kotlin/exampleapp/data/RefreshItem.kt index 1937c5d..5396504 100644 --- a/src/main/kotlin/exampleapp/data/RefreshItem.kt +++ b/src/main/kotlin/exampleapp/data/RefreshItem.kt @@ -7,5 +7,6 @@ data class RefreshItem( var userLast: User, var timeout: Int, var hostUnknown: Int, - var connectError: Int + var connectError: Int, + var hostUnreachable: Int ) \ No newline at end of file diff --git a/src/main/kotlin/exampleapp/service/JsonPlaceholder.kt b/src/main/kotlin/exampleapp/service/JsonPlaceholder.kt index 597428e..0188f61 100644 --- a/src/main/kotlin/exampleapp/service/JsonPlaceholder.kt +++ b/src/main/kotlin/exampleapp/service/JsonPlaceholder.kt @@ -100,11 +100,31 @@ class JsonPlaceholder @Inject constructor( .map { 1 }.mapError { - println("an error was seen") + println("Caught error trying to do an http request to ${host}:${port}") 1 } } + fun getURLBody(host: String, path: String, port: Int, params: Map): Promise { + val url = HttpUrlBuilder + .http() + .host(host) + .port(port) + .path(path) + .params(params) + .build() + println(url) + + return httpClient.copyWith { + it.readTimeout(Duration.ofSeconds(1)) + it.connectTimeout( Duration.ofSeconds(1)) + } + .get(url) + .map { + it + } + } + fun getURLThatHasTimeout(): Promise { return getErrorURL("localhost", "api/v1/timeout", 9999, mapOf("duration" to "60s")) } @@ -116,4 +136,14 @@ class JsonPlaceholder @Inject constructor( fun getURLThatHasConnectError(): Promise { return getErrorURL("localhost", "", 9998, emptyMap()) } + + fun getHostUnreachableError(): Promise { + return getErrorURL("192.168.5.1", "", 9999, emptyMap()) + } + + fun getLocalURL(hostname: String): Promise { + return getURLBody(hostname, "/health", 9999, mapOf("duration" to "1ms")).map { + it.body.text + } + } } \ No newline at end of file diff --git a/src/main/kotlin/exampleapp/service/Refresh.kt b/src/main/kotlin/exampleapp/service/Refresh.kt index a1014fc..3f329ed 100644 --- a/src/main/kotlin/exampleapp/service/Refresh.kt +++ b/src/main/kotlin/exampleapp/service/Refresh.kt @@ -22,7 +22,8 @@ class Refresh ( userLast = userLast.await(), timeout = doHttpTimeout.await(), hostUnknown = doHttpHostNotFound.await(), - connectError = doHttpConnectError.await() + connectError = doHttpConnectError.await(), + hostUnreachable = doHttpHostUnreachable.await() ) } @@ -73,6 +74,10 @@ class Refresh ( refreshService.jsonPlaceholder.getURLThatHasConnectError().await() } + private val doHttpHostUnreachable: Deferred by lazyAsync { + refreshService.jsonPlaceholder.getHostUnreachableError().await() + } + private fun startCoroutines() { val coroutines = listOf( createPost, @@ -82,7 +87,8 @@ class Refresh ( userLast, doHttpTimeout, doHttpHostNotFound, - doHttpConnectError + doHttpConnectError, + doHttpHostUnreachable ) coroutines.forEach { deferredFunc -> deferredFunc.start() diff --git a/src/main/kotlin/http/V1Chain.kt b/src/main/kotlin/http/V1Chain.kt index 30a7f3f..8c64030 100644 --- a/src/main/kotlin/http/V1Chain.kt +++ b/src/main/kotlin/http/V1Chain.kt @@ -16,12 +16,32 @@ class V1Chain @Inject constructor( get("hi") { render("hey there") } + path("connectiontimeout") { + refreshService.jsonPlaceholder.getURLThatHasConnectError().then { + render("fetched conn error host") + } + } + path("unknownhost") { + refreshService.jsonPlaceholder.getURLThatHasHostUnknown().then { + render("fetched unknown host") + } + } path("onlyhttp/:id") { val id = allPathTokens.getOrDefault("id", "1") refreshService.refresh(id.toInt()).then { render(json(it)) } } + path("localthing") { + val qParams = this.request.queryParams + var hostname = "localhost" + if ("hostname" in qParams) { + hostname = qParams["hostname"].toString() + } + refreshService.jsonPlaceholder.getLocalURL(hostname).then { + render("did local url for $hostname
$it") + } + } // path("cassandrahttp/:id") { // val id = allPathTokens.getOrDefault("id", "1") // val promises = listOf(