From bc43364c48b51b9e66094c3c18c3c89bc9aaf93e Mon Sep 17 00:00:00 2001 From: Alphonse Bendt Date: Wed, 20 Jun 2018 00:27:53 +0200 Subject: [PATCH] https://github.com/Kotlin/kotlinx.coroutines/issues/119: implementation of a SLF4J MDC Context --- .../kotlinx-coroutines-slf4j/README.md | 19 +++++ .../kotlinx-coroutines-slf4j/build.gradle | 12 +++ .../experimental/slf4j/MDCContext.kt | 42 +++++++++ .../experimental/slf4j/MDCContextTest.kt | 85 +++++++++++++++++++ .../experimental/slf4j/SimpleMDC.kt | 25 ++++++ .../src/test/resources/logback-test.xml | 15 ++++ settings.gradle | 1 + 7 files changed, 199 insertions(+) create mode 100644 integration/kotlinx-coroutines-slf4j/README.md create mode 100644 integration/kotlinx-coroutines-slf4j/build.gradle create mode 100644 integration/kotlinx-coroutines-slf4j/src/main/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContext.kt create mode 100644 integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContextTest.kt create mode 100644 integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/SimpleMDC.kt create mode 100644 integration/kotlinx-coroutines-slf4j/src/test/resources/logback-test.xml diff --git a/integration/kotlinx-coroutines-slf4j/README.md b/integration/kotlinx-coroutines-slf4j/README.md new file mode 100644 index 0000000000..2e2d4c1ea1 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/README.md @@ -0,0 +1,19 @@ +# Module kotlinx-coroutines-slf4j + +Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html). + +## Example + +Extends a given Coroutine context so that the SLF4J MDC context is passed into the coroutine. + +```kotlin +MDC.put("kotlin", "rocks") // put a value into the MDC context + +launch(MDCContext(CommonPool)) { + logger.info { "..." } // the MDC context will contain the mapping here +} +``` + +# Package kotlinx.coroutines.experimental.slf4j + +Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html). diff --git a/integration/kotlinx-coroutines-slf4j/build.gradle b/integration/kotlinx-coroutines-slf4j/build.gradle new file mode 100644 index 0000000000..e2d3a34070 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/build.gradle @@ -0,0 +1,12 @@ +dependencies { + compile 'org.slf4j:slf4j-api:1.7.25' + testCompile 'io.github.microutils:kotlin-logging:1.5.4' + testRuntime 'ch.qos.logback:logback-classic:1.2.3' + testRuntime 'ch.qos.logback:logback-core:1.2.3' +} + +tasks.withType(dokka.getClass()) { + externalDocumentationLink { + url = new URL("https://www.slf4j.org/apidocs/") + } +} \ No newline at end of file diff --git a/integration/kotlinx-coroutines-slf4j/src/main/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContext.kt b/integration/kotlinx-coroutines-slf4j/src/main/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContext.kt new file mode 100644 index 0000000000..5050518b5e --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/main/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContext.kt @@ -0,0 +1,42 @@ +package kotlinx.coroutines.experimental.slf4j + +import org.slf4j.MDC +import kotlin.coroutines.experimental.AbstractCoroutineContextElement +import kotlin.coroutines.experimental.Continuation +import kotlin.coroutines.experimental.ContinuationInterceptor +import kotlin.coroutines.experimental.CoroutineContext + +class MDCContext( + private val context: CoroutineContext, + private val contextMap: Map? = MDC.getCopyOfContextMap() +) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor { + + override fun interceptContinuation(continuation: Continuation): Continuation { + val next = context[ContinuationInterceptor] + + val wrappedContinuation = Wrapper(continuation) + + return when (next) { + null -> wrappedContinuation + else -> next.interceptContinuation(wrappedContinuation) + } + } + + private inner class Wrapper(private val continuation: Continuation) : Continuation { + private inline fun wrap(block: () -> Unit) { + try { + contextMap?.let { + MDC.setContextMap(contextMap) + } + + block() + } finally { + MDC.clear() + } + } + + override val context: CoroutineContext get() = continuation.context + override fun resume(value: T) = wrap { continuation.resume(value) } + override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) } + } +} diff --git a/integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContextTest.kt b/integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContextTest.kt new file mode 100644 index 0000000000..0bee128665 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/MDCContextTest.kt @@ -0,0 +1,85 @@ +package kotlinx.coroutines.experimental.slf4j + +import kotlinx.coroutines.experimental.* +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.slf4j.MDC +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class MDCContextTest : TestBase() { + + @Before + fun setUp() { + MDC.clear() + } + + @After + fun tearDown() { + MDC.clear() + } + + @Test + fun mdcContextIsNotPassedByDefaultBetweenCoRoutines() = runTest { + expect(1) + MDC.put("myKey", "myValue") + + launch { + val mdcValue = MDC.get("myKey") + check(mdcValue == null) + expect(2) + }.join() + + expect(3) + + finish(4) + } + + @Test + fun mdcContextCanBePassedBetweenCoRoutines() = runTest { + expect(1) + MDC.put("myKey", "myValue") + + launch(MDCContext(DefaultDispatcher)) { + val mdcValue = MDC.get("myKey") + check(mdcValue == "myValue") + expect(2) + }.join() + + expect(3) + + finish(4) + } + + @Test + fun mdcContextNotNeededWhileOnMainThread() { + MDC.put("myKey", "myValue") + + runBlocking { + val mdcValue = MDC.get("myKey") + + assertEquals(mdcValue, "myValue") + } + } + + @Test + fun mdcContextNeededWithOtherContext() { + MDC.put("myKey", "myValue") + + runBlocking(MDCContext(DefaultDispatcher)) { + val mdcValue = MDC.get("myKey") + + assertEquals(mdcValue, "myValue") + } + } + + @Test + fun mdcContextMayBeEmpty() { + runBlocking(MDCContext(DefaultDispatcher)) { + val mdcValue = MDC.get("myKey") + + assertNull(mdcValue) + } + } +} \ No newline at end of file diff --git a/integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/SimpleMDC.kt b/integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/SimpleMDC.kt new file mode 100644 index 0000000000..6c7019a8f2 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/test/kotlin/kotlinx/coroutines/experimental/slf4j/SimpleMDC.kt @@ -0,0 +1,25 @@ +package kotlinx.coroutines.experimental.slf4j + +import mu.KotlinLogging +import org.slf4j.MDC + +fun main(args: Array) { + val logger = KotlinLogging.logger {} + + // You can put values in the MDC at any time. Before anything else + // we put the first name + MDC.put("first", "Dorothy") + + // We now put the last name + MDC.put("last", "Parker") + + // The most beautiful two words in the English language according + // to Dorothy Parker: + logger.info("Check enclosed.") + logger.debug("The most beautiful two words in English.") + + MDC.put("first", "Richard") + MDC.put("last", "Nixon") + logger.info("I am not a crook.") + logger.info("Attributed to the former US president. 17 Nov 1973.") +} diff --git a/integration/kotlinx-coroutines-slf4j/src/test/resources/logback-test.xml b/integration/kotlinx-coroutines-slf4j/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..8051011490 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + + %X{first} %X{last} - %m%n + + + + + + + + + diff --git a/settings.gradle b/settings.gradle index b564b96fe8..4c41894730 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,6 +24,7 @@ module('integration/kotlinx-coroutines-guava') module('integration/kotlinx-coroutines-jdk8') module('integration/kotlinx-coroutines-nio') module('integration/kotlinx-coroutines-quasar') +module('integration/kotlinx-coroutines-slf4j') module('reactive/kotlinx-coroutines-reactive') module('reactive/kotlinx-coroutines-reactor')