Skip to content

Commit 4b0379f

Browse files
abendtelizarov
authored andcommitted
Implementation of a SLF4J MDC Context
See discussion in PR #403 and issue #119
1 parent e342597 commit 4b0379f

File tree

10 files changed

+236
-1
lines changed

10 files changed

+236
-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,19 @@
1+
public final class kotlinx/coroutines/experimental/slf4j/MDCContext : kotlin/coroutines/experimental/AbstractCoroutineContextElement, kotlinx/coroutines/experimental/ThreadContextElement {
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 fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
7+
public fun get (Lkotlin/coroutines/experimental/CoroutineContext$Key;)Lkotlin/coroutines/experimental/CoroutineContext$Element;
8+
public final fun getContextMap ()Ljava/util/Map;
9+
public fun minusKey (Lkotlin/coroutines/experimental/CoroutineContext$Key;)Lkotlin/coroutines/experimental/CoroutineContext;
10+
public fun plus (Lkotlin/coroutines/experimental/CoroutineContext;)Lkotlin/coroutines/experimental/CoroutineContext;
11+
public synthetic fun restoreThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Object;)V
12+
public fun restoreThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/util/Map;)V
13+
public synthetic fun updateThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;)Ljava/lang/Object;
14+
public fun updateThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;)Ljava/util/Map;
15+
}
16+
17+
public final class kotlinx/coroutines/experimental/slf4j/MDCContext$Key : kotlin/coroutines/experimental/CoroutineContext$Key {
18+
}
19+

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,70 @@
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+
* Note, that you cannot update MDC context from inside of the coroutine simply
32+
* using [MDC.put]. These updates are going to be lost on the next suspension and
33+
* reinstalled to the MDC context that was captured or explicitly specified in
34+
* [contextMap] when this object was created on the next resumption.
35+
* Use `withContext(MDCContext()) { ... }` to capture updated map of MDC keys and values
36+
* for the specified block of code.
37+
*
38+
* @param contextMap the value of [MDC] context map.
39+
* Default value is the copy of the current thread's context map that is acquired via
40+
* [MDC.getCopyOfContextMap].
41+
*/
42+
public class MDCContext(
43+
/**
44+
* The value of [MDC] context map.
45+
*/
46+
public val contextMap: MDCContextMap = MDC.getCopyOfContextMap()
47+
) : ThreadContextElement<MDCContextMap>, AbstractCoroutineContextElement(Key) {
48+
/**
49+
* Key of [MDCContext] in [CoroutineContext].
50+
*/
51+
companion object Key : CoroutineContext.Key<MDCContext>
52+
53+
override fun updateThreadContext(context: CoroutineContext): MDCContextMap {
54+
val oldState = MDC.getCopyOfContextMap()
55+
setCurrent(contextMap)
56+
return oldState
57+
}
58+
59+
override fun restoreThreadContext(context: CoroutineContext, oldState: MDCContextMap) {
60+
setCurrent(oldState)
61+
}
62+
63+
private fun setCurrent(contextMap: MDCContextMap) {
64+
if (contextMap == null) {
65+
MDC.clear()
66+
} else {
67+
MDC.setContextMap(contextMap)
68+
}
69+
}
70+
}
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,91 @@
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.*
9+
import org.junit.Test
10+
import org.slf4j.*
11+
import kotlin.coroutines.experimental.*
12+
import kotlin.test.*
13+
14+
class MDCContextTest : TestBase() {
15+
@Before
16+
fun setUp() {
17+
MDC.clear()
18+
}
19+
20+
@After
21+
fun tearDown() {
22+
MDC.clear()
23+
}
24+
25+
@Test
26+
fun testContextIsNotPassedByDefaultBetweenCoroutines() = runTest {
27+
expect(1)
28+
MDC.put("myKey", "myValue")
29+
launch {
30+
assertEquals(null, MDC.get("myKey"))
31+
expect(2)
32+
}.join()
33+
finish(3)
34+
}
35+
36+
@Test
37+
fun testContextCanBePassedBetweenCoroutines() = runTest {
38+
expect(1)
39+
MDC.put("myKey", "myValue")
40+
launch(MDCContext()) {
41+
assertEquals("myValue", MDC.get("myKey"))
42+
expect(2)
43+
}.join()
44+
45+
finish(3)
46+
}
47+
48+
@Test
49+
fun testContextPassedWhileOnMainThread() {
50+
MDC.put("myKey", "myValue")
51+
// No MDCContext element
52+
runBlocking {
53+
assertEquals("myValue", MDC.get("myKey"))
54+
}
55+
}
56+
57+
@Test
58+
fun testContextCanBePassedWhileOnMainThread() {
59+
MDC.put("myKey", "myValue")
60+
runBlocking(MDCContext()) {
61+
assertEquals("myValue", MDC.get("myKey"))
62+
}
63+
}
64+
65+
@Test
66+
fun testContextNeededWithOtherContext() {
67+
MDC.put("myKey", "myValue")
68+
runBlocking(MDCContext()) {
69+
assertEquals("myValue", MDC.get("myKey"))
70+
}
71+
}
72+
73+
@Test
74+
fun testContextMayBeEmpty() {
75+
runBlocking(MDCContext()) {
76+
assertEquals(null, MDC.get("myKey"))
77+
}
78+
}
79+
80+
@Test
81+
fun testContextWithContext() = runTest {
82+
MDC.put("myKey", "myValue")
83+
val mainDispatcher = kotlin.coroutines.experimental.coroutineContext[ContinuationInterceptor]!!
84+
withContext(DefaultDispatcher + MDCContext()) {
85+
assertEquals("myValue", MDC.get("myKey"))
86+
withContext(mainDispatcher) {
87+
assertEquals("myValue", MDC.get("myKey"))
88+
}
89+
}
90+
}
91+
}

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)