Skip to content

Commit

Permalink
Implementation of a SLF4J MDC Context
Browse files Browse the repository at this point in the history
See discussion in PR #403 and issue #119
  • Loading branch information
abendt authored and elizarov committed Jul 25, 2018
1 parent d70ac8c commit b0b5646
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 1 deletion.
2 changes: 1 addition & 1 deletion binary-compatibility-validator/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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')
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <init> ()V
public fun <init> (Ljava/util/Map;)V
public synthetic fun <init> (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 {
}

5 changes: 5 additions & 0 deletions binary-compatibility-validator/test/PublicApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions integration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 24 additions & 0 deletions integration/kotlinx-coroutines-slf4j/README.md
Original file line number Diff line number Diff line change
@@ -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 -->
12 changes: 12 additions & 0 deletions integration/kotlinx-coroutines-slf4j/build.gradle
Original file line number Diff line number Diff line change
@@ -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/")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kotlinx.coroutines.experimental.slf4j.MDCContextThreadLocal
64 changes: 64 additions & 0 deletions integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
Original file line number Diff line number Diff line change
@@ -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<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
* }
* ```
*/
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<MDCContext>
}

internal class MDCContextThreadLocal : CoroutineContextThreadLocal<MDCContextMap> {
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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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>


97 changes: 97 additions & 0 deletions integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
Original file line number Diff line number Diff line change
@@ -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"))
}
}
}
}
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
1 change: 1 addition & 0 deletions site/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit b0b5646

Please sign in to comment.