diff --git a/binary-compatibility-validator/build.gradle b/binary-compatibility-validator/build.gradle index 799aefca5d..0a39dde422 100644 --- a/binary-compatibility-validator/build.gradle +++ b/binary-compatibility-validator/build.gradle @@ -14,7 +14,6 @@ dependencies { testArtifacts project(':kotlinx-coroutines-core') testArtifacts project(':kotlinx-coroutines-io') - testArtifacts project(':kotlinx-coroutines-reactive') testArtifacts project(':kotlinx-coroutines-reactor') testArtifacts project(':kotlinx-coroutines-rx1') @@ -24,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..52631cb393 --- /dev/null +++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-slf4j.txt @@ -0,0 +1,11 @@ +public final class kotlinx/coroutines/experimental/slf4j/MDCContext : kotlin/coroutines/experimental/AbstractCoroutineContextElement { + 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 final fun getContextMap ()Ljava/util/Map; +} + +public final class kotlinx/coroutines/experimental/slf4j/MDCContext$Key : kotlin/coroutines/experimental/CoroutineContext$Key { +} + diff --git a/binary-compatibility-validator/test/PublicApiTest.kt b/binary-compatibility-validator/test/PublicApiTest.kt index 7455fa6540..33165f5c40 100644 --- a/binary-compatibility-validator/test/PublicApiTest.kt +++ b/binary-compatibility-validator/test/PublicApiTest.kt @@ -75,6 +75,11 @@ class PublicApiTest { snapshotAPIAndCompare("integration/kotlinx-coroutines-quasar") } + @Test + fun kotlinxCoroutinesSlf4j() { + snapshotAPIAndCompare("integration/kotlinx-coroutines-slf4j") + } + @Test fun kotlinxCoroutinesAndroid() { snapshotAPIAndCompare("ui/kotlinx-coroutines-android") 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/resources/META-INF/services/kotlinx.coroutines.experimental.CoroutineContextThreadLocal b/integration/kotlinx-coroutines-slf4j/resources/META-INF/services/kotlinx.coroutines.experimental.CoroutineContextThreadLocal new file mode 100644 index 0000000000..082ed84b9a --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/resources/META-INF/services/kotlinx.coroutines.experimental.CoroutineContextThreadLocal @@ -0,0 +1 @@ +kotlinx.coroutines.experimental.slf4j.MDCContextThreadLocal \ 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..c3af126059 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt @@ -0,0 +1,64 @@ +/* + * 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.Continuation +import kotlin.coroutines.experimental.ContinuationInterceptor +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 + * } + * ``` + */ +public class MDCContext( + /** + * The value of [MDC] context map. + */ + public val contextMap: MDCContextMap = MDC.getCopyOfContextMap() +) : AbstractCoroutineContextElement(Key) { + /** + * Key of [MDCContext] in [CoroutineContext]. + */ + companion object Key : CoroutineContext.Key +} + +internal class MDCContextThreadLocal : CoroutineContextThreadLocal { + override fun updateThreadContext(context: CoroutineContext): MDCContextMap { + val oldValue = MDC.getCopyOfContextMap() + val contextMap = context[MDCContext]?.contextMap + if (contextMap == null) { + MDC.clear() + } else { + MDC.setContextMap(contextMap) + } + return oldValue + } + + override fun restoreThreadContext(context: CoroutineContext, oldValue: MDCContextMap) { + if (oldValue == null) { + MDC.clear() + } else { + MDC.setContextMap(oldValue) + } + } +} 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..07992fd1a1 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt @@ -0,0 +1,97 @@ +/* + * 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.After +import org.junit.Before +import org.junit.Test +import org.slf4j.MDC +import kotlin.coroutines.experimental.* +import kotlin.test.assertEquals + +class MDCContextTest : TestBase() { + @Before + fun setUp() { + MDC.clear() + } + + @After + fun tearDown() { + MDC.clear() + } + + @Test + fun mdcContextIsNotPassedByDefaultBetweenCoroutines() = runTest { + expect(1) + MDC.put("myKey", "myValue") + + launch { + assertEquals(null, MDC.get("myKey")) + expect(2) + }.join() + + finish(3) + } + + @Test + fun mdcContextCanBePassedBetweenCoroutines() = runTest { + expect(1) + MDC.put("myKey", "myValue") + + launch(MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + expect(2) + }.join() + + finish(3) + } + + @Test + fun mdcContextNotPassedWhileOnMainThread() { + MDC.put("myKey", "myValue") + + runBlocking { + assertEquals(null, MDC.get("myKey")) + } + } + + @Test + fun mdcContextCanBePassedWhileOnMainThread() { + MDC.put("myKey", "myValue") + + runBlocking(MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + } + } + + @Test + fun mdcContextNeededWithOtherContext() { + MDC.put("myKey", "myValue") + + runBlocking(MDCContext()) { + assertEquals("myValue", MDC.get("myKey")) + } + } + + @Test + fun mdcContextMayBeEmpty() { + runBlocking(MDCContext()) { + assertEquals(null, MDC.get("myKey")) + } + } + + @Test + fun mdcContextWithContext() = 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 39eed4e06a..ef4a8ad3c9 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 e9b3d37981..d32bac48df 100644 --- a/site/docs/index.md +++ b/site/docs/index.md @@ -25,6 +25,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