Skip to content

implementation of a SLF4J MDC context #403

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

Closed
wants to merge 1 commit into from
Closed
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
19 changes: 19 additions & 0 deletions integration/kotlinx-coroutines-slf4j/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Module kotlinx-coroutines-slf4j

Integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html).

## Example

Extends a given Coroutine context so that the SLF4J MDC context is passed into the coroutine.

```kotlin
MDC.put("kotlin", "rocks") // put a value into the MDC context

launch(MDCContext(CommonPool)) {
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).
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,42 @@
package kotlinx.coroutines.experimental.slf4j

import org.slf4j.MDC
import kotlin.coroutines.experimental.AbstractCoroutineContextElement
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.ContinuationInterceptor
import kotlin.coroutines.experimental.CoroutineContext

class MDCContext(
private val context: CoroutineContext,
private val contextMap: Map<String, String>? = MDC.getCopyOfContextMap()
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {

override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
val next = context[ContinuationInterceptor]

val wrappedContinuation = Wrapper(continuation)

return when (next) {
null -> wrappedContinuation
else -> next.interceptContinuation(wrappedContinuation)
}
}

private inner class Wrapper<T>(private val continuation: Continuation<T>) : Continuation<T> {
private inline fun wrap(block: () -> Unit) {
try {
contextMap?.let {
MDC.setContextMap(contextMap)
}

block()
} finally {
MDC.clear()
}
}

override val context: CoroutineContext get() = continuation.context
override fun resume(value: T) = wrap { continuation.resume(value) }
override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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.test.assertEquals
import kotlin.test.assertNull

class MDCContextTest : TestBase() {

@Before
fun setUp() {
MDC.clear()
}

@After
fun tearDown() {
MDC.clear()
}

@Test
fun mdcContextIsNotPassedByDefaultBetweenCoRoutines() = runTest {
expect(1)
MDC.put("myKey", "myValue")

launch {
val mdcValue = MDC.get("myKey")
check(mdcValue == null)
expect(2)
}.join()

expect(3)

finish(4)
}

@Test
fun mdcContextCanBePassedBetweenCoRoutines() = runTest {
expect(1)
MDC.put("myKey", "myValue")

launch(MDCContext(DefaultDispatcher)) {
val mdcValue = MDC.get("myKey")
check(mdcValue == "myValue")
expect(2)
}.join()

expect(3)

finish(4)
}

@Test
fun mdcContextNotNeededWhileOnMainThread() {
MDC.put("myKey", "myValue")

runBlocking {
val mdcValue = MDC.get("myKey")

assertEquals(mdcValue, "myValue")
}
}

@Test
fun mdcContextNeededWithOtherContext() {
MDC.put("myKey", "myValue")

runBlocking(MDCContext(DefaultDispatcher)) {
val mdcValue = MDC.get("myKey")

assertEquals(mdcValue, "myValue")
}
}

@Test
fun mdcContextMayBeEmpty() {
runBlocking(MDCContext(DefaultDispatcher)) {
val mdcValue = MDC.get("myKey")

assertNull(mdcValue)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kotlinx.coroutines.experimental.slf4j

import mu.KotlinLogging
import org.slf4j.MDC

fun main(args: Array<String>) {
val logger = KotlinLogging.logger {}

// You can put values in the MDC at any time. Before anything else
// we put the first name
MDC.put("first", "Dorothy")

// We now put the last name
MDC.put("last", "Parker")

// The most beautiful two words in the English language according
// to Dorothy Parker:
logger.info("Check enclosed.")
logger.debug("The most beautiful two words in English.")

MDC.put("first", "Richard")
MDC.put("last", "Nixon")
logger.info("I am not a crook.")
logger.info("Attributed to the former US president. 17 Nov 1973.")
}
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>


1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,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