Skip to content

Commit a740ded

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

File tree

12 files changed

+233
-1
lines changed

12 files changed

+233
-1
lines changed

binary-compatibility-validator/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ dependencies {
1414
testArtifacts project(':kotlinx-coroutines-core')
1515
testArtifacts project(':kotlinx-coroutines-io')
1616

17-
1817
testArtifacts project(':kotlinx-coroutines-reactive')
1918
testArtifacts project(':kotlinx-coroutines-reactor')
2019
testArtifacts project(':kotlinx-coroutines-rx1')
@@ -24,6 +23,7 @@ dependencies {
2423
testArtifacts project(':kotlinx-coroutines-jdk8')
2524
testArtifacts project(':kotlinx-coroutines-nio')
2625
testArtifacts project(':kotlinx-coroutines-quasar')
26+
testArtifacts project(':kotlinx-coroutines-slf4j')
2727

2828
testArtifacts project(':kotlinx-coroutines-android')
2929
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
@@ -75,6 +75,11 @@ class PublicApiTest {
7575
snapshotAPIAndCompare("integration/kotlinx-coroutines-quasar")
7676
}
7777

78+
@Test
79+
fun kotlinxCoroutinesSlf4j() {
80+
snapshotAPIAndCompare("integration/kotlinx-coroutines-slf4j")
81+
}
82+
7883
@Test
7984
fun kotlinxCoroutinesAndroid() {
8085
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.Continuation
11+
import kotlin.coroutines.experimental.ContinuationInterceptor
12+
import kotlin.coroutines.experimental.CoroutineContext
13+
14+
/**
15+
* The value of [MDC] context map.
16+
* See [MDC.getCopyOfContextMap].
17+
*/
18+
public typealias MDCContextMap = Map<String, String>?
19+
20+
/**
21+
* [MDC] context element for [CoroutineContext].
22+
*
23+
* Example:
24+
*
25+
* ```
26+
* MDC.put("kotlin", "rocks") // put a value into the MDC context
27+
*
28+
* launch(MDCContext()) {
29+
* logger.info { "..." } // the MDC context contains the mapping here
30+
* }
31+
* ```
32+
*/
33+
public class MDCContext(
34+
/**
35+
* The value of [MDC] context map.
36+
*/
37+
public val contextMap: MDCContextMap = MDC.getCopyOfContextMap()
38+
) : AbstractCoroutineContextElement(Key) {
39+
/**
40+
* Key of [MDCContext] in [CoroutineContext].
41+
*/
42+
companion object Key : CoroutineContext.Key<MDCContext>
43+
}
44+
45+
internal class MDCContextThreadLocal : CoroutineContextThreadLocal<MDCContextMap> {
46+
override fun updateThreadContext(context: CoroutineContext): MDCContextMap {
47+
val oldValue = MDC.getCopyOfContextMap()
48+
val contextMap = context[MDCContext]?.contextMap
49+
if (contextMap == null) {
50+
MDC.clear()
51+
} else {
52+
MDC.setContextMap(contextMap)
53+
}
54+
return oldValue
55+
}
56+
57+
override fun restoreThreadContext(context: CoroutineContext, oldValue: MDCContextMap) {
58+
if (oldValue == null) {
59+
MDC.clear()
60+
} else {
61+
MDC.setContextMap(oldValue)
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,97 @@
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+
31+
launch {
32+
assertEquals(null, MDC.get("myKey"))
33+
expect(2)
34+
}.join()
35+
36+
finish(3)
37+
}
38+
39+
@Test
40+
fun mdcContextCanBePassedBetweenCoroutines() = runTest {
41+
expect(1)
42+
MDC.put("myKey", "myValue")
43+
44+
launch(MDCContext()) {
45+
assertEquals("myValue", MDC.get("myKey"))
46+
expect(2)
47+
}.join()
48+
49+
finish(3)
50+
}
51+
52+
@Test
53+
fun mdcContextNotPassedWhileOnMainThread() {
54+
MDC.put("myKey", "myValue")
55+
56+
runBlocking {
57+
assertEquals(null, MDC.get("myKey"))
58+
}
59+
}
60+
61+
@Test
62+
fun mdcContextCanBePassedWhileOnMainThread() {
63+
MDC.put("myKey", "myValue")
64+
65+
runBlocking(MDCContext()) {
66+
assertEquals("myValue", MDC.get("myKey"))
67+
}
68+
}
69+
70+
@Test
71+
fun mdcContextNeededWithOtherContext() {
72+
MDC.put("myKey", "myValue")
73+
74+
runBlocking(MDCContext()) {
75+
assertEquals("myValue", MDC.get("myKey"))
76+
}
77+
}
78+
79+
@Test
80+
fun mdcContextMayBeEmpty() {
81+
runBlocking(MDCContext()) {
82+
assertEquals(null, MDC.get("myKey"))
83+
}
84+
}
85+
86+
@Test
87+
fun mdcContextWithContext() = runTest {
88+
MDC.put("myKey", "myValue")
89+
val mainDispatcher = kotlin.coroutines.experimental.coroutineContext[ContinuationInterceptor]!!
90+
withContext(DefaultDispatcher + MDCContext()) {
91+
assertEquals("myValue", MDC.get("myKey"))
92+
withContext(mainDispatcher) {
93+
assertEquals("myValue", MDC.get("myKey"))
94+
}
95+
}
96+
}
97+
}

settings.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module('integration/kotlinx-coroutines-guava')
2929
module('integration/kotlinx-coroutines-jdk8')
3030
module('integration/kotlinx-coroutines-nio')
3131
module('integration/kotlinx-coroutines-quasar')
32+
module('integration/kotlinx-coroutines-slf4j')
3233

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

site/docs/index.md

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

2930
## Examples
3031

0 commit comments

Comments
 (0)