Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

feat!: migrate plugins to ktor 2.0.3 #39

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -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.3"
ext.opentracing_version = "0.33.0"
}

Expand All @@ -17,7 +17,7 @@ repositories {
}

dependencies {
implementation "io.github.microutils:kotlin-logging-jvm:2.0.8"
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"
Expand All @@ -26,14 +26,13 @@ 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.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"
testRuntime('org.junit.jupiter:junit-jupiter-engine:5.4.2')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.8.2')
}

test {
Expand All @@ -46,4 +45,12 @@ ext {
PUBLISH_ARTIFACT_ID = 'ktor-opentracing'
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
}

kotlin {
explicitApi()
}

apply from: "${rootProject.projectDir}/scripts/publish-module.gradle"
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
27 changes: 17 additions & 10 deletions src/main/kotlin/com/zopa/ktor/opentracing/Coroutines.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ 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


fun tracingContext(): CoroutineContext {
public fun tracingContext(): CoroutineContext {
val activeSpan: Span? = getGlobalTracer().scopeManager().activeSpan()

val spanStack = Stack<Span>()
Expand All @@ -24,13 +23,21 @@ fun tracingContext(): CoroutineContext {
return threadLocalSpanStack.asContextElement(spanStack)
}

fun CoroutineScope.launchTraced(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
public fun CoroutineScope.launchTraced(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job = launch(context + tracingContext(), start, block)

fun <T> CoroutineScope.asyncTraced(
@Suppress("DeferredIsResult")
@Deprecated("Use tracedAsync instead", ReplaceWith("tracedAsync(context, start, block)"))
public fun <T> CoroutineScope.asyncTraced(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> = tracedAsync(context, start, block)

public fun <T> CoroutineScope.tracedAsync(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
Expand Down
26 changes: 14 additions & 12 deletions src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingClient.kt
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
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
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 : HttpClientFeature<Config, OpenTracingClient> {
public companion object : HttpClientPlugin<Config, OpenTracingClient> {
override val key: AttributeKey<OpenTracingClient> = AttributeKey("OpenTracingClient")

override fun prepare(block: Config.() -> Unit): OpenTracingClient {
return OpenTracingClient()
}

override fun install(feature: OpenTracingClient, scope: HttpClient) {
override fun install(plugin: OpenTracingClient, scope: HttpClient) {

val tracer: Tracer = getGlobalTracer()

scope.sendPipeline.intercept(HttpSendPipeline.State) {
val spanStack = threadLocalSpanStack.get()
if (spanStack == null) {
log.warn("spanStack is null")
logger.warn("spanStack is null")
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)
Expand All @@ -43,7 +45,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))
Expand All @@ -52,17 +54,17 @@ 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()

val statusCode = context.response.status
val statusCode = subject.status
Tags.HTTP_STATUS.set(span, statusCode.value)
if (statusCode.value >= 400) span.setTag("error", true)

Expand Down
51 changes: 24 additions & 27 deletions src/main/kotlin/com/zopa/ktor/opentracing/OpenTracingServer.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
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.http.Headers
import io.ktor.request.httpMethod
import io.ktor.request.path
import io.ktor.routing.Routing
import io.ktor.server.application.*
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
Expand All @@ -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 Feature : ApplicationFeature<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 {
Expand All @@ -59,8 +57,9 @@ 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))
if (clientSpanContext == null) log.debug("Tracing context could not be found in request headers. Starting a new server trace.")
val clientSpanContext: SpanContext? =
tracer.extract(Format.Builtin.HTTP_HEADERS, TextMapAdapter(headers))
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()}"

Expand Down Expand Up @@ -90,9 +89,9 @@ 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")
}

Expand All @@ -101,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()
Expand All @@ -124,10 +123,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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}
4 changes: 2 additions & 2 deletions src/main/kotlin/com/zopa/ktor/opentracing/Span.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ 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()

span.addConfiguredLambdaTags()

try {
tracer.scopeManager().activate(span).use { scope ->
tracer.scopeManager().activate(span).use {
return block(span)
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
}

Expand Down
Loading