Implementation of a SLF4J MDC Context

See discussion in PR #403 and issue #119
diff --git a/binary-compatibility-validator/build.gradle b/binary-compatibility-validator/build.gradle
index d650898..67d393f 100644
--- a/binary-compatibility-validator/build.gradle
+++ b/binary-compatibility-validator/build.gradle
@@ -11,7 +11,7 @@
     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 @@
     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 0000000..d8587b3
--- /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 <init> ()V
+	public fun <init> (Ljava/util/Map;)V
+	public synthetic fun <init> (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 83b0a4b..099a17e 100644
--- a/integration/README.md
+++ b/integration/README.md
@@ -9,6 +9,7 @@
 * [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 0000000..265a9fc
--- /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).
+
+<!--- MODULE kotlinx-coroutines-slf4j -->
+<!--- INDEX kotlinx.coroutines.experimental.slf4j -->
+[MDCContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.experimental.slf4j/-m-d-c-context/index.html
+<!--- END -->
diff --git a/integration/kotlinx-coroutines-slf4j/build.gradle b/integration/kotlinx-coroutines-slf4j/build.gradle
new file mode 100644
index 0000000..e2d3a34
--- /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 0000000..6976b36
--- /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<String, String>?
+
+/**
+ * [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<MDCContextMap>, AbstractCoroutineContextElement(Key) {
+    /**
+     * Key of [MDCContext] in [CoroutineContext].
+     */
+    companion object Key : CoroutineContext.Key<MDCContext>
+
+    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 0000000..8051011
--- /dev/null
+++ b/integration/kotlinx-coroutines-slf4j/test-resources/logback-test.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration debug="false">
+
+    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
+        <layout>
+            <Pattern>%X{first} %X{last} - %m%n</Pattern>
+        </layout>
+    </appender>
+
+    <root level="DEBUG">
+        <appender-ref ref="CONSOLE" />
+    </root>
+</configuration>
+
+
diff --git a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
new file mode 100644
index 0000000..9838871
--- /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 d27a455..25d1cd7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -28,6 +28,7 @@
 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 e396687..1941573 100644
--- a/site/docs/index.md
+++ b/site/docs/index.md
@@ -24,6 +24,7 @@
 | [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