diff --git a/binary-compatibility-validator/build.gradle b/binary-compatibility-validator/build.gradle index d6508980f4..67d393ff7d 100644 --- a/binary-compatibility-validator/build.gradle +++ b/binary-compatibility-validator/build.gradle @@ -11,7 +11,7 @@ dependencies { compile 'com.google.code.gson:gson:2.6.2' testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" - + testArtifacts project(':kotlinx-coroutines-core') testArtifacts project(':kotlinx-coroutines-reactive') @@ -23,6 +23,7 @@ dependencies { testArtifacts project(':kotlinx-coroutines-jdk8') testArtifacts project(':kotlinx-coroutines-nio') testArtifacts project(':kotlinx-coroutines-quasar') + testArtifacts project(':kotlinx-coroutines-slf4j') testArtifacts project(':kotlinx-coroutines-android') testArtifacts project(':kotlinx-coroutines-javafx') diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt new file mode 100644 index 0000000000..d8587b36bd --- /dev/null +++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt @@ -0,0 +1,19 @@ +public final class kotlinx/coroutines/experimental/slf4j/MDCContext : kotlin/coroutines/experimental/AbstractCoroutineContextElement, kotlinx/coroutines/experimental/ThreadContextElement { + public static final field Key Lkotlinx/coroutines/experimental/slf4j/MDCContext$Key; + public fun ()V + public fun (Ljava/util/Map;)V + public synthetic fun (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public fun get (Lkotlin/coroutines/experimental/CoroutineContext$Key;)Lkotlin/coroutines/experimental/CoroutineContext$Element; + public final fun getContextMap ()Ljava/util/Map; + public fun minusKey (Lkotlin/coroutines/experimental/CoroutineContext$Key;)Lkotlin/coroutines/experimental/CoroutineContext; + public fun plus (Lkotlin/coroutines/experimental/CoroutineContext;)Lkotlin/coroutines/experimental/CoroutineContext; + public synthetic fun restoreThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Object;)V + public fun restoreThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/util/Map;)V + public synthetic fun updateThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;)Ljava/lang/Object; + public fun updateThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;)Ljava/util/Map; +} + +public final class kotlinx/coroutines/experimental/slf4j/MDCContext$Key : kotlin/coroutines/experimental/CoroutineContext$Key { +} + diff --git a/integration/README.md b/integration/README.md index 83b0a4bbcb..099a17ed1c 100644 --- a/integration/README.md +++ b/integration/README.md @@ -9,6 +9,7 @@ Module name below corresponds to the artifact name in Maven/Gradle. * [kotlinx-coroutines-nio](kotlinx-coroutines-nio/README.md) -- integration with asynchronous IO on JDK7+ (Android O Preview). * [kotlinx-coroutines-guava](kotlinx-coroutines-guava/README.md) -- integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained). * [kotlinx-coroutines-quasar](kotlinx-coroutines-quasar/README.md) -- integration with [Quasar](http://docs.paralleluniverse.co/quasar/). +* [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j/README.md) -- integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html). ## Contributing diff --git a/integration/kotlinx-coroutines-slf4j/README.md b/integration/kotlinx-coroutines-slf4j/README.md new file mode 100644 index 0000000000..265a9fc7e8 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/README.md @@ -0,0 +1,24 @@ +# Module kotlinx-coroutines-slf4j + +Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html). + +## Example + +Add [MDCContext] to the coroutine context so that the SLF4J MDC context is captured and passed into the coroutine. + +```kotlin +MDC.put("kotlin", "rocks") // put a value into the MDC context + +launch(MDCContext()) { + 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). + + + +[MDCContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.experimental.slf4j/-m-d-c-context/index.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/MDCContext.kt b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt new file mode 100644 index 0000000000..6976b36597 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.experimental.slf4j + +import kotlinx.coroutines.experimental.* +import org.slf4j.MDC +import kotlin.coroutines.experimental.AbstractCoroutineContextElement +import kotlin.coroutines.experimental.CoroutineContext + +/** + * The value of [MDC] context map. + * See [MDC.getCopyOfContextMap]. + */ +public typealias MDCContextMap = Map? + +/** + * [MDC] context element for [CoroutineContext]. + * + * Example: + * + * ``` + * MDC.put("kotlin", "rocks") // Put a value into the MDC context + * + * launch(MDCContext()) { + * logger.info { "..." } // The MDC context contains the mapping here + * } + * ``` + * + * Note, that you cannot update MDC context from inside of the coroutine simply + * using [MDC.put]. These updates are going to be lost on the next suspension and + * reinstalled to the MDC context that was captured or explicitly specified in + * [contextMap] when this object was created on the next resumption. + * Use `withContext(MDCContext()) { ... }` to capture updated map of MDC keys and values + * for the specified block of code. + * + * @param contextMap the value of [MDC] context map. + * Default value is the copy of the current thread's context map that is acquired via + * [MDC.getCopyOfContextMap]. + */ +public class MDCContext( + /** + * The value of [MDC] context map. + */ + public val contextMap: MDCContextMap = MDC.getCopyOfContextMap() +) : ThreadContextElement, AbstractCoroutineContextElement(Key) { + /** + * Key of [MDCContext] in [CoroutineContext]. + */ + companion object Key : CoroutineContext.Key + + override fun updateThreadContext(context: CoroutineContext): MDCContextMap { + val oldState = MDC.getCopyOfContextMap() + setCurrent(contextMap) + return oldState + } + + override fun restoreThreadContext(context: CoroutineContext, oldState: MDCContextMap) { + setCurrent(oldState) + } + + private fun setCurrent(contextMap: MDCContextMap) { + if (contextMap == null) { + MDC.clear() + } else { + MDC.setContextMap(contextMap) + } + } +} diff --git a/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml b/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml new file mode 100644 index 0000000000..8051011490 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + + + %X{first} %X{last} - %m%n + + + + + + + + + diff --git a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt new file mode 100644 index 0000000000..98388717ae --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt @@ -0,0 +1,91 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.experimental.slf4j + +import kotlinx.coroutines.experimental.* +import org.junit.* +import org.junit.Test +import org.slf4j.* +import kotlin.coroutines.experimental.* +import kotlin.test.* + +class MDCContextTest : TestBase() { + @Before + fun setUp() { + MDC.clear() + } + + @After + fun tearDown() { + MDC.clear() + } + + @Test + fun testContextIsNotPassedByDefaultBetweenCoroutines() = runTest { + expect(1) + MDC.put("myKey", "myValue") + launch { + assertEquals(null, MDC.get("myKey")) + expect(2) + }.join() + finish(3) + } + + @Test + fun testContextCanBePassedBetweenCoroutines() = runTest { + expect(1) + MDC.put("myKey", "myValue") + launch(MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + expect(2) + }.join() + + finish(3) + } + + @Test + fun testContextPassedWhileOnMainThread() { + MDC.put("myKey", "myValue") + // No MDCContext element + runBlocking { + assertEquals("myValue", MDC.get("myKey")) + } + } + + @Test + fun testContextCanBePassedWhileOnMainThread() { + MDC.put("myKey", "myValue") + runBlocking(MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + } + } + + @Test + fun testContextNeededWithOtherContext() { + MDC.put("myKey", "myValue") + runBlocking(MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + } + } + + @Test + fun testContextMayBeEmpty() { + runBlocking(MDCContext()) { + assertEquals(null, MDC.get("myKey")) + } + } + + @Test + fun testContextWithContext() = runTest { + MDC.put("myKey", "myValue") + val mainDispatcher = kotlin.coroutines.experimental.coroutineContext[ContinuationInterceptor]!! + withContext(DefaultDispatcher + MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + withContext(mainDispatcher) { + assertEquals("myValue", MDC.get("myKey")) + } + } + } +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index d27a455653..25d1cd7e75 100644 --- a/settings.gradle +++ b/settings.gradle @@ -28,6 +28,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') diff --git a/site/docs/index.md b/site/docs/index.md index e396687c03..194157337f 100644 --- a/site/docs/index.md +++ b/site/docs/index.md @@ -24,6 +24,7 @@ Library support for Kotlin coroutines. This reference is a companion to | [kotlinx-coroutines-nio](kotlinx-coroutines-nio) | Integration with asynchronous IO on JDK7+ (Android O Preview) | | [kotlinx-coroutines-guava](kotlinx-coroutines-guava) | Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained) | | [kotlinx-coroutines-quasar](kotlinx-coroutines-quasar) | Integration with [Quasar](http://docs.paralleluniverse.co/quasar/) | +| [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j) | Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html) | ## Examples