From 57b51d15879da91df1b06db166fe0210905492df Mon Sep 17 00:00:00 2001 From: Michael Rittmeister <michael@rittmeister.in> Date: Fri, 10 Sep 2021 13:29:03 +0200 Subject: [PATCH 1/3] Migrate to Ktor 2.0.0 --- build.gradle | 15 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- .../ktor/opentracing/OpenTracingClient.kt | 12 ++--- .../ktor/opentracing/OpenTracingServer.kt | 20 ++++---- .../ktor/opentracing/RequestBuilderCarrier.kt | 2 +- .../kotlin/com/zopa/ktor/opentracing/utils.kt | 34 ++++++++----- .../ktor/opentracing/ConfigurationTest.kt | 16 +++--- .../zopa/ktor/opentracing/CoroutinesTest.kt | 12 ++--- .../opentracing/KtorOpenTracingServerTest.kt | 51 ++++++++++--------- .../ktor/opentracing/OpenTracingClientTest.kt | 14 +++-- .../com/zopa/ktor/opentracing/SpanTest.kt | 10 ++-- .../com/zopa/ktor/opentracing/UuidUtilTest.kt | 10 ++-- 12 files changed, 106 insertions(+), 92 deletions(-) diff --git a/build.gradle b/build.gradle index 13efe75..efa73dc 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { - ext.kotlin_version = "1.5.10" - ext.ktor_version = "1.6.0" + ext.kotlin_version = "1.5.30" + ext.ktor_version = "2.0.0-eap-205" ext.opentracing_version = "0.33.0" } @@ -14,10 +14,11 @@ apply from: "${rootDir}/scripts/publish-root.gradle" repositories { mavenCentral() + maven { url 'https://maven.pkg.jetbrains.space/public/p/ktor/eap' } } dependencies { - implementation "io.github.microutils:kotlin-logging-jvm:2.0.8" + implementation "io.github.microutils:kotlin-logging-jvm:2.0.11" implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-client-core:$ktor_version" @@ -26,14 +27,12 @@ dependencies { implementation "io.opentracing:opentracing-noop:$opentracing_version" implementation "io.opentracing:opentracing-util:$opentracing_version" - testImplementation 'org.jetbrains.kotlin:kotlin-test:1.4.31' - testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.4.31' - testImplementation "org.junit.jupiter:junit-jupiter-api:5.4.2" - testImplementation "com.willowtreeapps.assertk:assertk-jvm:0.23" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' + testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.24' testImplementation "io.opentracing:opentracing-mock:$opentracing_version" testImplementation "io.ktor:ktor-server-tests:$ktor_version" testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version" - testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2') + testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.7.2') } test { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 442d913..ffed3a2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt index 80cdcb5..20cfa00 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt @@ -1,7 +1,7 @@ package com.zopa.ktor.opentracing import io.ktor.client.HttpClient -import io.ktor.client.features.HttpClientFeature +import io.ktor.client.plugins.HttpClientPlugin import io.ktor.client.request.HttpSendPipeline import io.ktor.client.statement.HttpReceivePipeline import io.ktor.util.AttributeKey @@ -13,7 +13,7 @@ import io.opentracing.tag.Tags class OpenTracingClient { class Config - companion object : HttpClientFeature<Config, OpenTracingClient> { + companion object : HttpClientPlugin<Config, OpenTracingClient> { override val key: AttributeKey<OpenTracingClient> = AttributeKey("OpenTracingClient") override fun prepare(block: Config.() -> Unit): OpenTracingClient { @@ -31,8 +31,8 @@ class OpenTracingClient { return@intercept } - val pathUuid: PathUuid = context.url.encodedPath.UuidFromPath() - val name = "Call to ${context.method.value} ${context.url.host}${pathUuid.path}" + val pathUuid: PathUuid = context.url.pathSegments.toPathUuid() + val name = "Call to ${context.method.value} ${context.url.build().host}${pathUuid}" val spanBuilder = tracer.buildSpan(name) if (pathUuid.uuid != null) spanBuilder.withTag("UUID", pathUuid.uuid) @@ -43,7 +43,7 @@ class OpenTracingClient { Tags.SPAN_KIND.set(span, Tags.SPAN_KIND_CLIENT) Tags.HTTP_METHOD.set(span, context.method.value) - Tags.HTTP_URL.set(span, "${context.url.host}${context.url.encodedPath}") + Tags.HTTP_URL.set(span, "${context.url.host}${context.url.pathSegments.joinToString("/")}") spanStack.push(span) tracer.inject(span?.context(), Format.Builtin.HTTP_HEADERS, RequestBuilderCarrier(context.headers)) @@ -62,7 +62,7 @@ class OpenTracingClient { } val span = spanStack.pop() - val statusCode = context.response.status + val statusCode = subject.status Tags.HTTP_STATUS.set(span, statusCode.value) if (statusCode.value >= 400) span.setTag("error", true) diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt index 24ab8ed..f2a69e7 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt @@ -1,14 +1,14 @@ package com.zopa.ktor.opentracing -import io.ktor.application.Application -import io.ktor.application.ApplicationCall -import io.ktor.application.ApplicationCallPipeline -import io.ktor.application.ApplicationFeature -import io.ktor.application.call +import io.ktor.server.application.Application +import io.ktor.server.application.ApplicationCall +import io.ktor.server.application.ApplicationCallPipeline +import io.ktor.server.application.call import io.ktor.http.Headers -import io.ktor.request.httpMethod -import io.ktor.request.path -import io.ktor.routing.Routing +import io.ktor.server.application.ApplicationPlugin +import io.ktor.server.request.httpMethod +import io.ktor.server.request.path +import io.ktor.server.routing.Routing import io.ktor.util.AttributeKey import io.ktor.util.pipeline.PipelinePhase import io.opentracing.Span @@ -37,7 +37,7 @@ class OpenTracingServer { } } - companion object Feature : ApplicationFeature<Application, Configuration, OpenTracingServer> { + companion object Plugin : ApplicationPlugin<Application, Configuration, OpenTracingServer> { override val key = AttributeKey<OpenTracingServer>("OpenTracingServer") internal var config = Configuration() @@ -92,7 +92,7 @@ class OpenTracingServer { span.setTag(param.key, param.value.first()) pathWithParamsReplaced = pathWithParamsReplaced.replace(param.value.first(), "{${param.key}}") } - + span.setOperationName("${call.request.httpMethod.value} $pathWithParamsReplaced") } diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/RequestBuilderCarrier.kt b/src/main/kotlin/com/zopa/ktor/opentracing/RequestBuilderCarrier.kt index a2c8610..39bf1cc 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/RequestBuilderCarrier.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/RequestBuilderCarrier.kt @@ -14,4 +14,4 @@ internal class RequestBuilderCarrier(private val headerBuilder: HeadersBuilder): override fun iterator(): MutableIterator<MutableMap.MutableEntry<String, String>> { throw UnsupportedOperationException("carrier is write-only") } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt b/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt index 4febc64..ee3e283 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt @@ -13,23 +13,33 @@ import kotlin.coroutines.coroutineContext val log = KotlinLogging.logger { } -internal data class PathUuid(val path: String, val uuid: String?) -internal fun String.UuidFromPath(): PathUuid { - val match = """\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b""".toRegex().find(this) +private val uuidRegex = + """\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b""".toRegex() - if (match == null) - return PathUuid(this, null) - else { - val uuid = match.value - val pathWithReplacement = this.replace(uuid, "<UUID>") - return PathUuid(pathWithReplacement, uuid) +internal data class PathUuid(val path: List<String>, val uuid: String?) { + override fun toString(): String = path.joinToString("/") +} + +internal fun List<String>.toPathUuid(): PathUuid { + forEachIndexed { index, input -> + val match = uuidRegex.find(input) + if(match != null) { + val pathWithReplacement = toMutableList().apply { + removeAt(index) + add(index, "<UUID>") + }.toList() + + return PathUuid(pathWithReplacement, match.value) + } } + + return PathUuid(this, null) } fun getGlobalTracer(): Tracer { return GlobalTracer.get() ?: NoopTracerFactory.create() - .also { log.warn("Tracer not registered in GlobalTracer. Using Noop tracer instead.") } + .also { log.warn { "Tracer not registered in GlobalTracer. Using Noop tracer instead." } } } internal suspend fun Span.addCleanup() { @@ -60,8 +70,8 @@ fun Span.addConfiguredLambdaTags() { Note that this function will give unexpected results if used in regular functions, extension functions and init functions. For these spans, it is preferable to define span names explicitly. */ fun classAndMethodName( - currentInstance: Any, - anonymousObjectCreatedInMethod: Any + currentInstance: Any, + anonymousObjectCreatedInMethod: Any ): String { val className = currentInstance::class.simpleName diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/ConfigurationTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/ConfigurationTest.kt index de4c20f..7bcf0f0 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/ConfigurationTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/ConfigurationTest.kt @@ -4,21 +4,21 @@ import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isNull import com.zopa.ktor.opentracing.util.mockTracer -import io.ktor.application.call -import io.ktor.application.install import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond import io.ktor.client.request.get +import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.request.path -import io.ktor.response.respond -import io.ktor.routing.get -import io.ktor.routing.routing +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.request.path +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.routing import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication -import io.opentracing.mock.MockTracer import io.opentracing.util.GlobalTracer import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -130,7 +130,7 @@ class ConfigurationTest { application.routing { get(path) { - val clientResponse = client.get<String>("/member/74c144e6-ec05-49af-b3a2-217e1254897f") + val clientResponse = client.get("/member/74c144e6-ec05-49af-b3a2-217e1254897f").bodyAsText() call.respond(clientResponse) } } diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt index 1d2a4a6..2535d14 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt @@ -3,13 +3,13 @@ package com.zopa.ktor.opentracing import assertk.assertThat import assertk.assertions.isEqualTo import com.zopa.ktor.opentracing.util.mockTracer -import io.ktor.application.call -import io.ktor.application.install import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.get -import io.ktor.routing.routing +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.routing import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication import io.opentracing.util.GlobalTracer @@ -39,7 +39,7 @@ class CoroutinesTest { fun getDouble(parentSpanName: Int): Int = span { setTag("parentSpanName", parentSpanName) - return getDoubleSecondTime(parentSpanName)*2 + return getDoubleSecondTime(parentSpanName) * 2 } application.routing { diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt index 2e5d78b..44ae159 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt @@ -4,13 +4,13 @@ import assertk.assertThat import assertk.assertions.isEqualTo import assertk.assertions.isNotEqualTo import com.zopa.ktor.opentracing.util.mockTracer -import io.ktor.application.call -import io.ktor.application.install import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.get -import io.ktor.routing.routing +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.routing import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication import io.opentracing.propagation.Format @@ -58,32 +58,33 @@ class KtorOpenTracingServerTest { } @Test - fun `(Bug) Server span name incorrectly has all occurrence of request param replaced with value`() = withTestApplication { - val routePath = "/hello/there/{name}" - val path = "/hello/there/hello" + fun `(Bug) Server span name incorrectly has all occurrence of request param replaced with value`() = + withTestApplication { + val routePath = "/hello/there/{name}" + val path = "/hello/there/hello" - application.install(OpenTracingServer) + application.install(OpenTracingServer) - application.routing { - get(routePath) { - call.respond("OK") + application.routing { + get(routePath) { + call.respond("OK") + } } - } - - handleRequest(HttpMethod.Get, path) {}.let { call -> - assertThat(call.response.status()).isEqualTo(HttpStatusCode.OK) - with(mockTracer.finishedSpans()) { - assertThat(size).isEqualTo(1) - assertThat(first().parentId()).isEqualTo(0L) // no parent span - assertThat(first().operationName()).isEqualTo("GET /{name}/there/{name}") - assertThat(first().tags().get("name")).isEqualTo("hello") - assertThat(first().tags().get("span.kind")).isEqualTo("server") - assertThat(first().tags().get("http.status_code")).isEqualTo(200) + handleRequest(HttpMethod.Get, path) {}.let { call -> + assertThat(call.response.status()).isEqualTo(HttpStatusCode.OK) + + with(mockTracer.finishedSpans()) { + assertThat(size).isEqualTo(1) + assertThat(first().parentId()).isEqualTo(0L) // no parent span + assertThat(first().operationName()).isEqualTo("GET /{name}/there/{name}") + assertThat(first().tags().get("name")).isEqualTo("hello") + assertThat(first().tags().get("span.kind")).isEqualTo("server") + assertThat(first().tags().get("http.status_code")).isEqualTo(200) + } } - } - } + } @Test fun `Server span with error code has error tag`() = withTestApplication { diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt index 448d1a7..c35e4ca 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt @@ -10,13 +10,17 @@ import io.ktor.application.install import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond +import io.ktor.client.plugins.get import io.ktor.client.request.get import io.ktor.client.request.request +import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.get -import io.ktor.routing.routing +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.routing import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication import io.opentracing.Span @@ -56,7 +60,7 @@ class OpenTracingClientTest { application.routing { get(path) { - val clientResponse = client.get<String>("/member/74c144e6-ec05-49af-b3a2-217e1254897f") + val clientResponse = client.get("/member/74c144e6-ec05-49af-b3a2-217e1254897f").bodyAsText() call.respond(clientResponse) } } @@ -104,7 +108,7 @@ class OpenTracingClientTest { assertDoesNotThrow { runBlocking { withContext(threadLocalSpanStack.asContextElement(spanStack)) { - client.request<String>("/4DCA6409-D958-417E-B4DD-20738C721C48/view") + client.request("/4DCA6409-D958-417E-B4DD-20738C721C48/view").bodyAsText() } } } diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt index 7140406..76b7750 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt @@ -3,13 +3,13 @@ package com.zopa.ktor.opentracing import assertk.assertThat import assertk.assertions.isEqualTo import com.zopa.ktor.opentracing.util.mockTracer -import io.ktor.application.call -import io.ktor.application.install import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.response.respond -import io.ktor.routing.get -import io.ktor.routing.routing +import io.ktor.server.application.call +import io.ktor.server.application.install +import io.ktor.server.response.respond +import io.ktor.server.routing.get +import io.ktor.server.routing.routing import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication import io.opentracing.util.GlobalTracer diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/UuidUtilTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/UuidUtilTest.kt index 051cf9b..0aa8ead 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/UuidUtilTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/UuidUtilTest.kt @@ -9,9 +9,9 @@ import org.junit.jupiter.api.TestInstance class UuidUtilTest { @Test fun `UuidFromPath returns unchanged path and no uuid if no UUID in path`() { - val path = "/evidence" + val path = listOf("evidence") - val pathUuid = path.UuidFromPath() + val pathUuid = path.toPathUuid() assertThat(pathUuid.path).isEqualTo(path) assertThat(pathUuid.uuid).isEqualTo(null) @@ -19,11 +19,11 @@ class UuidUtilTest { @Test fun `UuidFromPath returns path with UUID and uuid if UUID in path`() { - val path = "/evidence/AB7AD59A-A0FF-4EB1-90CF-BC6D5C24095F" + val path = listOf("evidence", "AB7AD59A-A0FF-4EB1-90CF-BC6D5C24095F") - val pathUuid = path.UuidFromPath() + val pathUuid = path.toPathUuid() - assertThat(pathUuid.path).isEqualTo("/evidence/<UUID>") + assertThat(pathUuid.path).isEqualTo(listOf("evidence", "<UUID>")) assertThat(pathUuid.uuid).isEqualTo("AB7AD59A-A0FF-4EB1-90CF-BC6D5C24095F") } } From 4c303bff0100a286869034c69dd79a44ccaf2794 Mon Sep 17 00:00:00 2001 From: Michael Rittmeister <michael@rittmeister.in> Date: Fri, 10 Sep 2021 13:42:26 +0200 Subject: [PATCH 2/3] Fix intellij inspections --- build.gradle | 5 ++++ .../com/zopa/ktor/opentracing/Coroutines.kt | 20 +++++++++---- .../ktor/opentracing/OpenTracingClient.kt | 2 +- .../ktor/opentracing/OpenTracingServer.kt | 13 ++++---- .../kotlin/com/zopa/ktor/opentracing/Span.kt | 2 +- .../zopa/ktor/opentracing/CoroutinesTest.kt | 2 +- .../opentracing/KtorOpenTracingServerTest.kt | 22 +++++++------- .../zopa/ktor/opentracing/NamingUtilTest.kt | 8 ++--- .../ktor/opentracing/OpenTracingClientTest.kt | 30 ++++++++----------- .../com/zopa/ktor/opentracing/SpanTest.kt | 11 +++---- 10 files changed, 62 insertions(+), 53 deletions(-) diff --git a/build.gradle b/build.gradle index efa73dc..ca2b696 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.24' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2' testImplementation "io.opentracing:opentracing-mock:$opentracing_version" testImplementation "io.ktor:ktor-server-tests:$ktor_version" testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version" @@ -45,4 +46,8 @@ ext { PUBLISH_ARTIFACT_ID = 'ktor-opentracing' } +java { + sourceCompatibility = JavaVersion.VERSION_1_8 +} + apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt b/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt index 86b93f4..b2b13c6 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt @@ -3,11 +3,11 @@ package com.zopa.ktor.opentracing import io.opentracing.Span import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.launch -import kotlinx.coroutines.Job import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Job +import kotlinx.coroutines.asContextElement import kotlinx.coroutines.async +import kotlinx.coroutines.launch import java.util.Stack import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -25,15 +25,23 @@ fun tracingContext(): CoroutineContext { } fun CoroutineScope.launchTraced( - context: CoroutineContext = EmptyCoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - block: suspend CoroutineScope.() -> Unit + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit ): Job = launch(context + tracingContext(), start, block) +@Suppress("DeferredIsResult") +@Deprecated("Use tracedAsync instead", ReplaceWith("tracedAsync(context, start, block)")) fun <T> CoroutineScope.asyncTraced( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T +) = tracedAsync(context, start, block) + +fun <T> CoroutineScope.tracedAsync( + context: CoroutineContext = EmptyCoroutineContext, + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> T ): Deferred<T> = async(context + tracingContext(), start) { block() } diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt index 20cfa00..423be5a 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt @@ -20,7 +20,7 @@ class OpenTracingClient { return OpenTracingClient() } - override fun install(feature: OpenTracingClient, scope: HttpClient) { + override fun install(plugin: OpenTracingClient, scope: HttpClient) { val tracer: Tracer = getGlobalTracer() diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt index f2a69e7..acee355 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt @@ -59,7 +59,8 @@ class OpenTracingServer { val headers: MutableMap<String, String> = call.request.headers.toMap() headers.remove("Authorization") - val clientSpanContext: SpanContext? = tracer.extract(Format.Builtin.HTTP_HEADERS, TextMapAdapter(headers)) + val clientSpanContext: SpanContext? = + tracer.extract(Format.Builtin.HTTP_HEADERS, TextMapAdapter(headers)) if (clientSpanContext == null) log.debug("Tracing context could not be found in request headers. Starting a new server trace.") val spanName = "${context.request.httpMethod.value} ${context.request.path()}" @@ -90,7 +91,7 @@ class OpenTracingServer { var pathWithParamsReplaced = call.request.path() call.parameters.entries().forEach { param -> span.setTag(param.key, param.value.first()) - pathWithParamsReplaced = pathWithParamsReplaced.replace(param.value.first(), "{${param.key}}") + pathWithParamsReplaced = pathWithParamsReplaced.replace(param.value.first(), "{${param.key}}") } span.setOperationName("${call.request.httpMethod.value} $pathWithParamsReplaced") @@ -124,10 +125,8 @@ class OpenTracingServer { } private fun Headers.toMap(): MutableMap<String, String> = - this.entries() - .filter { (_, values) -> values.isNotEmpty() } - .map { (key, values) -> key to values.first() } - .toMap() - .toMutableMap() + this.entries() + .filter { (_, values) -> values.isNotEmpty() }.associate { (key, values) -> key to values.first() } + .toMutableMap() } } diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt b/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt index ba2c637..cbcac9e 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt @@ -9,7 +9,7 @@ inline fun <T> span(name: String = "defaultSpanName", block: Span.() -> T): T { span.addConfiguredLambdaTags() try { - tracer.scopeManager().activate(span).use { scope -> + tracer.scopeManager().activate(span).use { return block(span) } } finally { diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt index 2535d14..e62aa5c 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/CoroutinesTest.kt @@ -45,7 +45,7 @@ class CoroutinesTest { application.routing { get(path) { val result: List<Int> = listOf(1, 10).map { id -> - asyncTraced { + tracedAsync { span(id.toString()) { getDouble(id) } diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt index 44ae159..8c5683b 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt @@ -49,10 +49,10 @@ class KtorOpenTracingServerTest { assertThat(size).isEqualTo(1) assertThat(first().parentId()).isEqualTo(0L) // no parent span assertThat(first().operationName()).isEqualTo("GET /greeting/{id}/{name}") - assertThat(first().tags().get("id")).isEqualTo("ab7ad59a-a0ff-4eb1-90cf-bc6d5c24095f") - assertThat(first().tags().get("name")).isEqualTo("Ruth") - assertThat(first().tags().get("span.kind")).isEqualTo("server") - assertThat(first().tags().get("http.status_code")).isEqualTo(200) + assertThat(first().tags()["id"]).isEqualTo("ab7ad59a-a0ff-4eb1-90cf-bc6d5c24095f") + assertThat(first().tags()["name"]).isEqualTo("Ruth") + assertThat(first().tags()["span.kind"]).isEqualTo("server") + assertThat(first().tags()["http.status_code"]).isEqualTo(200) } } } @@ -78,9 +78,9 @@ class KtorOpenTracingServerTest { assertThat(size).isEqualTo(1) assertThat(first().parentId()).isEqualTo(0L) // no parent span assertThat(first().operationName()).isEqualTo("GET /{name}/there/{name}") - assertThat(first().tags().get("name")).isEqualTo("hello") - assertThat(first().tags().get("span.kind")).isEqualTo("server") - assertThat(first().tags().get("http.status_code")).isEqualTo(200) + assertThat(first().tags()["name"]).isEqualTo("hello") + assertThat(first().tags()["span.kind"]).isEqualTo("server") + assertThat(first().tags()["http.status_code"]).isEqualTo(200) } } @@ -106,9 +106,9 @@ class KtorOpenTracingServerTest { assertThat(size).isEqualTo(1) assertThat(first().parentId()).isEqualTo(0L) // no parent span assertThat(first().operationName()).isEqualTo("GET /greeting") - assertThat(first().tags().get("span.kind")).isEqualTo("server") - assertThat(first().tags().get("http.status_code")).isEqualTo(401) - assertThat(first().tags().get("error")).isEqualTo(true) + assertThat(first().tags()["span.kind"]).isEqualTo("server") + assertThat(first().tags()["http.status_code"]).isEqualTo(401) + assertThat(first().tags()["error"]).isEqualTo(true) } } } @@ -180,7 +180,7 @@ class KtorOpenTracingServerTest { assertThat(first().parentId()).isNotEqualTo(0L) // has parent span assertThat(first().references().first().referenceType).isEqualTo("child_of") assertThat(first().operationName()).isEqualTo("GET /greeting") - assertThat(first().tags().get("span.kind")).isEqualTo("server") + assertThat(first().tags()["span.kind"]).isEqualTo("server") } } } diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/NamingUtilTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/NamingUtilTest.kt index 26dec9f..1e7c96a 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/NamingUtilTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/NamingUtilTest.kt @@ -7,11 +7,11 @@ import org.junit.jupiter.api.Test class TracingUtilTest { @Test - fun `classAndMethodName returns class and method name for method of a class`() = runBlocking<Unit> { + fun `classAndMethodName returns class and method name for method of a class`() = runBlocking { var name: String? = null class Dog { - suspend fun bark() { + fun bark() { name = classAndMethodName(this, object {}) } } @@ -40,7 +40,7 @@ class TracingUtilTest { fun `classAndMethodName does not return function name`() = runBlocking<Unit> { var name: String? = null - suspend fun bark() { + fun bark() { name = classAndMethodName(this, object {}) } @@ -50,7 +50,7 @@ class TracingUtilTest { } @Test - fun `classAndMethodName in extension function does not return function name`() = runBlocking<Unit> { + fun `classAndMethodName in extension function does not return function name`() = runBlocking { var name: String? = null fun String.toSomethingElse() { diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt index c35e4ca..d5020df 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt @@ -5,12 +5,9 @@ import assertk.assertions.contains import assertk.assertions.isEqualTo import assertk.assertions.isNotEqualTo import com.zopa.ktor.opentracing.util.mockTracer -import io.ktor.application.call -import io.ktor.application.install import io.ktor.client.HttpClient import io.ktor.client.engine.mock.MockEngine import io.ktor.client.engine.mock.respond -import io.ktor.client.plugins.get import io.ktor.client.request.get import io.ktor.client.request.request import io.ktor.client.statement.bodyAsText @@ -26,14 +23,14 @@ import io.ktor.server.testing.withTestApplication import io.opentracing.Span import io.opentracing.util.GlobalTracer import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.withContext import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle -import java.util.* +import java.util.Stack @TestInstance(Lifecycle.PER_CLASS) class OpenTracingClientTest { @@ -73,12 +70,12 @@ class OpenTracingClientTest { assertThat(first().parentId()).isNotEqualTo(last().parentId()) assertThat(first().operationName()).isEqualTo("Call to GET localhostmember/<UUID>") - assertThat(first().tags().get("http.status_code")).isEqualTo(200) - assertThat(first().tags().get("UUID")).isEqualTo("74c144e6-ec05-49af-b3a2-217e1254897f") + assertThat(first().tags()["http.status_code"]).isEqualTo(200) + assertThat(first().tags()["UUID"]).isEqualTo("74c144e6-ec05-49af-b3a2-217e1254897f") assertThat(last().parentId()).isEqualTo(0L) assertThat(last().operationName()).isEqualTo("GET /sqrt") - assertThat(last().tags().get("span.kind")).isEqualTo("server") + assertThat(last().tags()["span.kind"]).isEqualTo("server") } } } @@ -91,8 +88,7 @@ class OpenTracingClientTest { addHandler { request -> val headers = request.headers.entries() .filter { (_, values) -> values.isNotEmpty() } - .map { (key, values) -> key to values.first() } - .toMap() + .associate { (key, values) -> key to values.first() } if (headers.containsKey("traceid")) respond("OK", HttpStatusCode.OK) @@ -106,19 +102,19 @@ class OpenTracingClientTest { val spanStack = Stack<Span>() spanStack.push(span) - assertDoesNotThrow { runBlocking { - withContext(threadLocalSpanStack.asContextElement(spanStack)) { - client.request("/4DCA6409-D958-417E-B4DD-20738C721C48/view").bodyAsText() + assertDoesNotThrow { + runBlockingTest { + withContext(threadLocalSpanStack.asContextElement(spanStack)) { + client.request("/4DCA6409-D958-417E-B4DD-20738C721C48/view").bodyAsText() + } } - } } + } with(mockTracer.finishedSpans()) { assertThat(size).isEqualTo(1) assertThat(first().tags()).contains(Pair("span.kind", "client")) assertThat(first().operationName()).isEqualTo("Call to GET localhost<UUID>/view") - assertThat(first().tags()).contains(Pair("UUID","4DCA6409-D958-417E-B4DD-20738C721C48")) + assertThat(first().tags()).contains(Pair("UUID", "4DCA6409-D958-417E-B4DD-20738C721C48")) } } - - } diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt index 76b7750..0e6b6df 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt @@ -14,7 +14,7 @@ import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication import io.opentracing.util.GlobalTracer import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -47,11 +47,12 @@ class SpanTest { } val sqrt: Double = sqrtOfInt(2) - val sqrtSuspend: Double = runBlocking { - sqrtOfIntSuspend(10) + runBlockingTest { + val sqrtSuspend: Double = sqrtOfIntSuspend(10) + + call.respond("Square root of 2: $sqrt, Square root of 10: $sqrtSuspend") } - call.respond("Square root of 2: $sqrt, Square root of 10: $sqrtSuspend") } } @@ -64,7 +65,7 @@ class SpanTest { // server span assertThat(last().parentId()).isEqualTo(0L) assertThat(last().operationName()).isEqualTo("GET /sqrt") - assertThat(last().tags().get("span.kind")).isEqualTo("server") + assertThat(last().tags()["span.kind"]).isEqualTo("server") // first child span assertThat(first().context().traceId()).isEqualTo(last().context().traceId()) From f4308c02cfc5d1359407f435a05afff3637941d4 Mon Sep 17 00:00:00 2001 From: mingchuno <mingchuno@gmail.com> Date: Thu, 28 Jul 2022 22:40:46 +0800 Subject: [PATCH 3/3] Migrate to ktor 2.0.3 --- build.gradle | 17 ++++++----- .../com/zopa/ktor/opentracing/Coroutines.kt | 11 ++++--- .../ktor/opentracing/OpenTracingClient.kt | 14 +++++---- .../ktor/opentracing/OpenTracingServer.kt | 30 +++++++++---------- .../kotlin/com/zopa/ktor/opentracing/Span.kt | 2 +- .../ThreadContextElementScopeManager.kt | 8 +++-- .../kotlin/com/zopa/ktor/opentracing/utils.kt | 23 +++++++------- .../ktor/opentracing/OpenTracingClientTest.kt | 23 ++++++-------- ...ServerTest.kt => OpenTracingServerTest.kt} | 2 +- .../com/zopa/ktor/opentracing/SpanTest.kt | 5 ++-- 10 files changed, 66 insertions(+), 69 deletions(-) rename src/test/kotlin/com/zopa/ktor/opentracing/{KtorOpenTracingServerTest.kt => OpenTracingServerTest.kt} (99%) diff --git a/build.gradle b/build.gradle index ca2b696..8230468 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { ext.kotlin_version = "1.5.30" - ext.ktor_version = "2.0.0-eap-205" + ext.ktor_version = "2.0.3" ext.opentracing_version = "0.33.0" } @@ -14,11 +14,10 @@ apply from: "${rootDir}/scripts/publish-root.gradle" repositories { mavenCentral() - maven { url 'https://maven.pkg.jetbrains.space/public/p/ktor/eap' } } dependencies { - implementation "io.github.microutils:kotlin-logging-jvm:2.0.11" + implementation 'io.github.microutils:kotlin-logging-jvm:2.1.23' implementation "io.ktor:ktor-server-core:$ktor_version" implementation "io.ktor:ktor-client-core:$ktor_version" @@ -27,13 +26,13 @@ dependencies { implementation "io.opentracing:opentracing-noop:$opentracing_version" implementation "io.opentracing:opentracing-util:$opentracing_version" - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2' - testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.24' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2' + testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.25' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.2' testImplementation "io.opentracing:opentracing-mock:$opentracing_version" testImplementation "io.ktor:ktor-server-tests:$ktor_version" testImplementation "io.ktor:ktor-client-mock-jvm:$ktor_version" - testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.7.2') + testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2') } test { @@ -50,4 +49,8 @@ java { sourceCompatibility = JavaVersion.VERSION_1_8 } +kotlin { + explicitApi() +} + apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt b/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt index b2b13c6..333c316 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt @@ -12,8 +12,7 @@ import java.util.Stack import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext - -fun tracingContext(): CoroutineContext { +public fun tracingContext(): CoroutineContext { val activeSpan: Span? = getGlobalTracer().scopeManager().activeSpan() val spanStack = Stack<Span>() @@ -24,7 +23,7 @@ fun tracingContext(): CoroutineContext { return threadLocalSpanStack.asContextElement(spanStack) } -fun CoroutineScope.launchTraced( +public fun CoroutineScope.launchTraced( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit @@ -32,13 +31,13 @@ fun CoroutineScope.launchTraced( @Suppress("DeferredIsResult") @Deprecated("Use tracedAsync instead", ReplaceWith("tracedAsync(context, start, block)")) -fun <T> CoroutineScope.asyncTraced( +public fun <T> CoroutineScope.asyncTraced( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T -) = tracedAsync(context, start, block) +): Deferred<T> = tracedAsync(context, start, block) -fun <T> CoroutineScope.tracedAsync( +public fun <T> CoroutineScope.tracedAsync( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt index 423be5a..0a5f6d1 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt @@ -8,12 +8,14 @@ import io.ktor.util.AttributeKey import io.opentracing.Tracer import io.opentracing.propagation.Format import io.opentracing.tag.Tags +import mu.KotlinLogging +private val logger = KotlinLogging.logger {} -class OpenTracingClient { - class Config +public class OpenTracingClient { + public class Config - companion object : HttpClientPlugin<Config, OpenTracingClient> { + public companion object : HttpClientPlugin<Config, OpenTracingClient> { override val key: AttributeKey<OpenTracingClient> = AttributeKey("OpenTracingClient") override fun prepare(block: Config.() -> Unit): OpenTracingClient { @@ -27,7 +29,7 @@ class OpenTracingClient { scope.sendPipeline.intercept(HttpSendPipeline.State) { val spanStack = threadLocalSpanStack.get() if (spanStack == null) { - log.warn("spanStack is null") + logger.warn("spanStack is null") return@intercept } @@ -52,12 +54,12 @@ class OpenTracingClient { scope.receivePipeline.intercept(HttpReceivePipeline.State) { val spanStack = threadLocalSpanStack.get() if (spanStack == null) { - log.warn("spanStack is null") + logger.warn("spanStack is null") return@intercept } if (spanStack.isEmpty()) { - log.error("span could not be found in thread local span context") + logger.error("span could not be found in thread local span context") return@intercept } val span = spanStack.pop() diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt index acee355..1b3cf98 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt @@ -1,11 +1,7 @@ package com.zopa.ktor.opentracing -import io.ktor.server.application.Application -import io.ktor.server.application.ApplicationCall -import io.ktor.server.application.ApplicationCallPipeline -import io.ktor.server.application.call import io.ktor.http.Headers -import io.ktor.server.application.ApplicationPlugin +import io.ktor.server.application.* import io.ktor.server.request.httpMethod import io.ktor.server.request.path import io.ktor.server.routing.Routing @@ -20,25 +16,27 @@ import io.opentracing.tag.Tags import io.opentracing.util.GlobalTracer import kotlinx.coroutines.asContextElement import kotlinx.coroutines.withContext +import mu.KotlinLogging import java.util.Stack +private val logger = KotlinLogging.logger {} -class OpenTracingServer { - class Configuration { - val filters = mutableListOf<(ApplicationCall) -> Boolean>() - val lambdaTags = mutableListOf<Pair<String, () -> String>>() +public class OpenTracingServer { + public class Configuration { + internal val filters = mutableListOf<(ApplicationCall) -> Boolean>() + internal val lambdaTags = mutableListOf<Pair<String, () -> String>>() - fun filter(predicate: (ApplicationCall) -> Boolean) { + public fun filter(predicate: (ApplicationCall) -> Boolean) { filters.add(predicate) } - fun addTag(name: String, lambda: () -> String) { + public fun addTag(name: String, lambda: () -> String) { lambdaTags.add(Pair(name, lambda)) } } - companion object Plugin : ApplicationPlugin<Application, Configuration, OpenTracingServer> { - override val key = AttributeKey<OpenTracingServer>("OpenTracingServer") + public companion object Plugin : BaseApplicationPlugin<Application, Configuration, OpenTracingServer> { + override val key: AttributeKey<OpenTracingServer> = AttributeKey("OpenTracingServer") internal var config = Configuration() override fun install(pipeline: Application, configure: Configuration.() -> Unit): OpenTracingServer { @@ -61,7 +59,7 @@ class OpenTracingServer { val clientSpanContext: SpanContext? = tracer.extract(Format.Builtin.HTTP_HEADERS, TextMapAdapter(headers)) - if (clientSpanContext == null) log.debug("Tracing context could not be found in request headers. Starting a new server trace.") + if (clientSpanContext == null) logger.debug("Tracing context could not be found in request headers. Starting a new server trace.") val spanName = "${context.request.httpMethod.value} ${context.request.path()}" @@ -102,12 +100,12 @@ class OpenTracingServer { val spanStack = threadLocalSpanStack.get() if (spanStack == null) { - log.warn("spanStack is null") + logger.warn("spanStack is null") return@intercept } if (spanStack.isEmpty()) { - log.error("Active span could not be found in thread local trace context") + logger.error("Active span could not be found in thread local trace context") return@intercept } val span = spanStack.pop() diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt b/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt index cbcac9e..9a1bb1c 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/Span.kt @@ -2,7 +2,7 @@ package com.zopa.ktor.opentracing import io.opentracing.Span -inline fun <T> span(name: String = "defaultSpanName", block: Span.() -> T): T { +public inline fun <T> span(name: String = "defaultSpanName", block: Span.() -> T): T { val tracer = getGlobalTracer() val span = tracer.buildSpan(name).start() diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/ThreadContextElementScopeManager.kt b/src/main/kotlin/com/zopa/ktor/opentracing/ThreadContextElementScopeManager.kt index fda1b1d..7766866 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/ThreadContextElementScopeManager.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/ThreadContextElementScopeManager.kt @@ -3,17 +3,19 @@ package com.zopa.ktor.opentracing import io.opentracing.Scope import io.opentracing.ScopeManager import io.opentracing.Span +import mu.KotlinLogging import java.util.Stack +private val logger = KotlinLogging.logger {} internal val threadLocalSpanStack = ThreadLocal<Stack<Span>>() -class ThreadContextElementScopeManager: ScopeManager { +public class ThreadContextElementScopeManager: ScopeManager { override fun activate(span: Span?): Scope { var spanStack = threadLocalSpanStack.get() if (spanStack == null) { - log.info { "Span stack is null, instantiating a new one." } + logger.info { "Span stack is null, instantiating a new one." } spanStack = Stack<Span>() threadLocalSpanStack.set(spanStack) } @@ -32,7 +34,7 @@ internal class CoroutineThreadLocalScope: Scope { override fun close() { val spanStack = threadLocalSpanStack.get() if (spanStack == null) { - log.error { "spanStack is null" } + logger.error { "spanStack is null" } return } diff --git a/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt b/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt index ee3e283..f6ca3f7 100644 --- a/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt +++ b/src/main/kotlin/com/zopa/ktor/opentracing/utils.kt @@ -10,8 +10,7 @@ import java.io.PrintWriter import java.io.StringWriter import kotlin.coroutines.coroutineContext - -val log = KotlinLogging.logger { } +private val logger = KotlinLogging.logger {} private val uuidRegex = """\b[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-\b[0-9a-fA-F]{12}\b""".toRegex() @@ -36,10 +35,10 @@ internal fun List<String>.toPathUuid(): PathUuid { return PathUuid(this, null) } -fun getGlobalTracer(): Tracer { +public fun getGlobalTracer(): Tracer { return GlobalTracer.get() ?: NoopTracerFactory.create() - .also { log.warn { "Tracer not registered in GlobalTracer. Using Noop tracer instead." } } + .also { logger.warn { "Tracer not registered in GlobalTracer. Using Noop tracer instead." } } } internal suspend fun Span.addCleanup() { @@ -54,22 +53,22 @@ internal suspend fun Span.addCleanup() { } } -fun Span.addConfiguredLambdaTags() { +public fun Span.addConfiguredLambdaTags() { OpenTracingServer.config.lambdaTags.forEach { try { this.setTag(it.first, it.second.invoke()) } catch (e: Exception) { - log.warn(e) { "Could not add tag: ${it.first}" } + logger.warn(e) { "Could not add tag: ${it.first}" } } } } -/* - Helper function to name spans. Should only be used in method of a class as such: - classAndMethodName(this, object {}) - Note that this function will give unexpected results if used in regular functions, extension functions and init functions. For these spans, it is preferable to define span names explicitly. -*/ -fun classAndMethodName( +/** + * Helper function to name spans. Should only be used in method of a class as such: `classAndMethodName(this, object {})` + * Note that this function will give unexpected results if used in regular functions, extension functions and init + * functions. For these spans, it is preferable to define span names explicitly. + */ +public fun classAndMethodName( currentInstance: Any, anonymousObjectCreatedInMethod: Any ): String { diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt index d5020df..c1a5f28 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingClientTest.kt @@ -13,18 +13,14 @@ import io.ktor.client.request.request import io.ktor.client.statement.bodyAsText import io.ktor.http.HttpMethod import io.ktor.http.HttpStatusCode -import io.ktor.server.application.call -import io.ktor.server.application.install +import io.ktor.server.application.* import io.ktor.server.response.respond -import io.ktor.server.routing.get -import io.ktor.server.routing.routing -import io.ktor.server.testing.handleRequest -import io.ktor.server.testing.withTestApplication +import io.ktor.server.routing.* +import io.ktor.server.testing.* import io.opentracing.Span import io.opentracing.util.GlobalTracer import kotlinx.coroutines.asContextElement -import kotlinx.coroutines.test.runBlockingTest -import kotlinx.coroutines.withContext +import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -66,10 +62,11 @@ class OpenTracingClientTest { assertThat(call.response.status()).isEqualTo(HttpStatusCode.OK) with(mockTracer.finishedSpans()) { + println(this) assertThat(size).isEqualTo(2) assertThat(first().parentId()).isNotEqualTo(last().parentId()) - assertThat(first().operationName()).isEqualTo("Call to GET localhostmember/<UUID>") + assertThat(first().operationName()).isEqualTo("Call to GET localhost/member/<UUID>") assertThat(first().tags()["http.status_code"]).isEqualTo(200) assertThat(first().tags()["UUID"]).isEqualTo("74c144e6-ec05-49af-b3a2-217e1254897f") @@ -103,17 +100,15 @@ class OpenTracingClientTest { spanStack.push(span) assertDoesNotThrow { - runBlockingTest { - withContext(threadLocalSpanStack.asContextElement(spanStack)) { - client.request("/4DCA6409-D958-417E-B4DD-20738C721C48/view").bodyAsText() - } + runTest(threadLocalSpanStack.asContextElement(spanStack)) { + client.request("/4DCA6409-D958-417E-B4DD-20738C721C48/view").bodyAsText() } } with(mockTracer.finishedSpans()) { assertThat(size).isEqualTo(1) assertThat(first().tags()).contains(Pair("span.kind", "client")) - assertThat(first().operationName()).isEqualTo("Call to GET localhost<UUID>/view") + assertThat(first().operationName()).isEqualTo("Call to GET localhost/<UUID>/view") assertThat(first().tags()).contains(Pair("UUID", "4DCA6409-D958-417E-B4DD-20738C721C48")) } } diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingServerTest.kt similarity index 99% rename from src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt rename to src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingServerTest.kt index 8c5683b..d6cdd1a 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/KtorOpenTracingServerTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/OpenTracingServerTest.kt @@ -22,7 +22,7 @@ import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance.Lifecycle @TestInstance(Lifecycle.PER_CLASS) -class KtorOpenTracingServerTest { +class OpenTracingServerTest { @BeforeEach fun setup() { mockTracer.reset() diff --git a/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt b/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt index 0e6b6df..b789ae1 100644 --- a/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt +++ b/src/test/kotlin/com/zopa/ktor/opentracing/SpanTest.kt @@ -14,7 +14,7 @@ import io.ktor.server.testing.handleRequest import io.ktor.server.testing.withTestApplication import io.opentracing.util.GlobalTracer import kotlinx.coroutines.delay -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance @@ -47,9 +47,8 @@ class SpanTest { } val sqrt: Double = sqrtOfInt(2) - runBlockingTest { + runTest { val sqrtSuspend: Double = sqrtOfIntSuspend(10) - call.respond("Square root of 2: $sqrt, Square root of 10: $sqrtSuspend") }