Skip to content

Commit 6fb5a31

Browse files
abendtelizarov
authored andcommitted
Implementation of a SLF4J MDC Context
See discussion in PR #403 and issue #119
1 parent ea55c5b commit 6fb5a31

File tree

12 files changed

+229
-1
lines changed

12 files changed

+229
-1
lines changed

binary-compatibility-validator/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ dependencies {
1111
compile 'com.google.code.gson:gson:2.6.2'
1212

1313
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
14-
14+
1515
testArtifacts project(':kotlinx-coroutines-core')
1616

1717
testArtifacts project(':kotlinx-coroutines-reactive')
@@ -23,6 +23,7 @@ dependencies {
2323
testArtifacts project(':kotlinx-coroutines-jdk8')
2424
testArtifacts project(':kotlinx-coroutines-nio')
2525
testArtifacts project(':kotlinx-coroutines-quasar')
26+
testArtifacts project(':kotlinx-coroutines-slf4j')
2627

2728
testArtifacts project(':kotlinx-coroutines-android')
2829
testArtifacts project(':kotlinx-coroutines-javafx')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
public final class kotlinx/coroutines/experimental/slf4j/MDCContext : kotlin/coroutines/experimental/AbstractCoroutineContextElement {
2+
public static final field Key Lkotlinx/coroutines/experimental/slf4j/MDCContext$Key;
3+
public fun <init> ()V
4+
public fun <init> (Ljava/util/Map;)V
5+
public synthetic fun <init> (Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
6+
public final fun getContextMap ()Ljava/util/Map;
7+
}
8+
9+
public final class kotlinx/coroutines/experimental/slf4j/MDCContext$Key : kotlin/coroutines/experimental/CoroutineContext$Key {
10+
}
11+

binary-compatibility-validator/test/PublicApiTest.kt

+5
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class PublicApiTest {
7777
snapshotAPIAndCompare("integration/kotlinx-coroutines-quasar")
7878
}
7979

80+
@Test
81+
fun kotlinxCoroutinesSlf4j() {
82+
snapshotAPIAndCompare("integration/kotlinx-coroutines-slf4j")
83+
}
84+
8085
@Test
8186
fun kotlinxCoroutinesAndroid() {
8287
snapshotAPIAndCompare("ui/kotlinx-coroutines-android")

integration/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Module name below corresponds to the artifact name in Maven/Gradle.
99
* [kotlinx-coroutines-nio](kotlinx-coroutines-nio/README.md) -- integration with asynchronous IO on JDK7+ (Android O Preview).
1010
* [kotlinx-coroutines-guava](kotlinx-coroutines-guava/README.md) -- integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained).
1111
* [kotlinx-coroutines-quasar](kotlinx-coroutines-quasar/README.md) -- integration with [Quasar](http://docs.paralleluniverse.co/quasar/).
12+
* [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j/README.md) -- integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).
1213

1314
## Contributing
1415

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Module kotlinx-coroutines-slf4j
2+
3+
Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).
4+
5+
## Example
6+
7+
Add [MDCContext] to the coroutine context so that the SLF4J MDC context is captured and passed into the coroutine.
8+
9+
```kotlin
10+
MDC.put("kotlin", "rocks") // put a value into the MDC context
11+
12+
launch(MDCContext()) {
13+
logger.info { "..." } // the MDC context will contain the mapping here
14+
}
15+
```
16+
17+
# Package kotlinx.coroutines.experimental.slf4j
18+
19+
Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).
20+
21+
<!--- MODULE kotlinx-coroutines-slf4j -->
22+
<!--- INDEX kotlinx.coroutines.experimental.slf4j -->
23+
[MDCContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-slf4j/kotlinx.coroutines.experimental.slf4j/-m-d-c-context/index.html
24+
<!--- END -->
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
dependencies {
2+
compile 'org.slf4j:slf4j-api:1.7.25'
3+
testCompile 'io.github.microutils:kotlin-logging:1.5.4'
4+
testRuntime 'ch.qos.logback:logback-classic:1.2.3'
5+
testRuntime 'ch.qos.logback:logback-core:1.2.3'
6+
}
7+
8+
tasks.withType(dokka.getClass()) {
9+
externalDocumentationLink {
10+
url = new URL("https://www.slf4j.org/apidocs/")
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
kotlinx.coroutines.experimental.slf4j.MDCContextThreadLocal
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.experimental.slf4j
6+
7+
import kotlinx.coroutines.experimental.*
8+
import org.slf4j.MDC
9+
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
10+
import kotlin.coroutines.experimental.CoroutineContext
11+
12+
/**
13+
* The value of [MDC] context map.
14+
* See [MDC.getCopyOfContextMap].
15+
*/
16+
public typealias MDCContextMap = Map<String, String>?
17+
18+
/**
19+
* [MDC] context element for [CoroutineContext].
20+
*
21+
* Example:
22+
*
23+
* ```
24+
* MDC.put("kotlin", "rocks") // put a value into the MDC context
25+
*
26+
* launch(MDCContext()) {
27+
* logger.info { "..." } // the MDC context contains the mapping here
28+
* }
29+
* ```
30+
*/
31+
public class MDCContext(
32+
/**
33+
* The value of [MDC] context map.
34+
*/
35+
public val contextMap: MDCContextMap = MDC.getCopyOfContextMap()
36+
) : AbstractCoroutineContextElement(Key) {
37+
/**
38+
* Key of [MDCContext] in [CoroutineContext].
39+
*/
40+
companion object Key : CoroutineContext.Key<MDCContext>
41+
}
42+
43+
internal class MDCContextThreadLocal : CoroutineContextThreadLocal<MDCContext, MDCContextMap> {
44+
override val key: CoroutineContext.Key<MDCContext>
45+
get() = MDCContext
46+
47+
override fun updateThreadContext(context: CoroutineContext, element: MDCContext): MDCContextMap {
48+
val oldState = MDC.getCopyOfContextMap()
49+
setCurrent(element.contextMap)
50+
return oldState
51+
}
52+
53+
override fun restoreThreadContext(context: CoroutineContext, oldState: MDCContextMap) {
54+
setCurrent(oldState)
55+
}
56+
57+
private fun setCurrent(contextMap: MDCContextMap) {
58+
if (contextMap == null) {
59+
MDC.clear()
60+
} else {
61+
MDC.setContextMap(contextMap)
62+
}
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration debug="false">
3+
4+
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
5+
<layout>
6+
<Pattern>%X{first} %X{last} - %m%n</Pattern>
7+
</layout>
8+
</appender>
9+
10+
<root level="DEBUG">
11+
<appender-ref ref="CONSOLE" />
12+
</root>
13+
</configuration>
14+
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.experimental.slf4j
6+
7+
import kotlinx.coroutines.experimental.*
8+
import org.junit.After
9+
import org.junit.Before
10+
import org.junit.Test
11+
import org.slf4j.MDC
12+
import kotlin.coroutines.experimental.*
13+
import kotlin.test.assertEquals
14+
15+
class MDCContextTest : TestBase() {
16+
@Before
17+
fun setUp() {
18+
MDC.clear()
19+
}
20+
21+
@After
22+
fun tearDown() {
23+
MDC.clear()
24+
}
25+
26+
@Test
27+
fun mdcContextIsNotPassedByDefaultBetweenCoroutines() = runTest {
28+
expect(1)
29+
MDC.put("myKey", "myValue")
30+
launch {
31+
assertEquals(null, MDC.get("myKey"))
32+
expect(2)
33+
}.join()
34+
finish(3)
35+
}
36+
37+
@Test
38+
fun mdcContextCanBePassedBetweenCoroutines() = runTest {
39+
expect(1)
40+
MDC.put("myKey", "myValue")
41+
launch(MDCContext()) {
42+
assertEquals("myValue", MDC.get("myKey"))
43+
expect(2)
44+
}.join()
45+
46+
finish(3)
47+
}
48+
49+
@Test
50+
fun mdcContextPassedWhileOnMainThread() {
51+
MDC.put("myKey", "myValue")
52+
// No MDCContext element
53+
runBlocking {
54+
assertEquals("myValue", MDC.get("myKey"))
55+
}
56+
}
57+
58+
@Test
59+
fun mdcContextCanBePassedWhileOnMainThread() {
60+
MDC.put("myKey", "myValue")
61+
runBlocking(MDCContext()) {
62+
assertEquals("myValue", MDC.get("myKey"))
63+
}
64+
}
65+
66+
@Test
67+
fun mdcContextNeededWithOtherContext() {
68+
MDC.put("myKey", "myValue")
69+
runBlocking(MDCContext()) {
70+
assertEquals("myValue", MDC.get("myKey"))
71+
}
72+
}
73+
74+
@Test
75+
fun mdcContextMayBeEmpty() {
76+
runBlocking(MDCContext()) {
77+
assertEquals(null, MDC.get("myKey"))
78+
}
79+
}
80+
81+
@Test
82+
fun mdcContextWithContext() = runTest {
83+
MDC.put("myKey", "myValue")
84+
val mainDispatcher = kotlin.coroutines.experimental.coroutineContext[ContinuationInterceptor]!!
85+
withContext(DefaultDispatcher + MDCContext()) {
86+
assertEquals("myValue", MDC.get("myKey"))
87+
withContext(mainDispatcher) {
88+
assertEquals("myValue", MDC.get("myKey"))
89+
}
90+
}
91+
}
92+
}

settings.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module('integration/kotlinx-coroutines-guava')
2828
module('integration/kotlinx-coroutines-jdk8')
2929
module('integration/kotlinx-coroutines-nio')
3030
module('integration/kotlinx-coroutines-quasar')
31+
module('integration/kotlinx-coroutines-slf4j')
3132

3233
module('reactive/kotlinx-coroutines-reactive')
3334
module('reactive/kotlinx-coroutines-reactor')

site/docs/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Library support for Kotlin coroutines. This reference is a companion to
2424
| [kotlinx-coroutines-nio](kotlinx-coroutines-nio) | Integration with asynchronous IO on JDK7+ (Android O Preview) |
2525
| [kotlinx-coroutines-guava](kotlinx-coroutines-guava) | Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained) |
2626
| [kotlinx-coroutines-quasar](kotlinx-coroutines-quasar) | Integration with [Quasar](http://docs.paralleluniverse.co/quasar/) |
27+
| [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j) | Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html) |
2728

2829
## Examples
2930

0 commit comments

Comments
 (0)