Skip to content

Implementation of a SLF4J MDC Context #455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion binary-compatibility-validator/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ dependencies {
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')
Expand All @@ -23,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,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 {
}

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/")
}
}
70 changes: 70 additions & 0 deletions integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
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>


91 changes: 91 additions & 0 deletions integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
Original file line number Diff line number Diff line change
@@ -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"))
}
}
}
}
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 @@ -24,6 +24,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