Accessing throw-site MDC context in "global" exception handler
See original GitHub issueI am trying to enrich an application with some MDC context in order to make debugging issues easier. My goal is to be able to log the MDC context from the point where an exception was thrown alongside the error.
In a non-coroutine world, that is easily possible by only popping the MDC entries when your block of code finishes successfully, but not on error. Your catch
on the outermost level or a global exception handler would then still be able to “see” the MDC context from the throw site because it is left intact.
In a coroutine world, we have to wrap our suspend functions with withContext(MDCContext()) {}
in order to bridge the ThreadLocal
gap. But that means that whenever the coroutine resumes, the MDCContext will be restored to what it was when the MDCContext()
was instantiated.
I tried using CoroutineExceptionHandler
and supervisorScope
to achieve the desired behaviour because I thought the coroutineContext
passed to the CoroutineExceptionHandler
would refer to the coroutine that failed (so I could get the MDCContext
element in there), but it seems to be the supervisor job’s coroutineContext
.
I could wrap every body inside withContext(MDCContext())
with a try/catch, log the error there and then rethrow, but that would mean I log the exception N times for N levels of nested MDCContext
s, which is unsatisfying.
Any help on how I could achieve this will be greatly appreciated, I am out of ideas.
Please take a look at the following sample code to get an idea of what I am trying to achieve:
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.slf4j.MDCContext
import kotlinx.coroutines.withContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.MDC
private val logger: Logger = LoggerFactory.getLogger("test")
suspend fun deepInSomeOtherCode() {
MDC.put("nesting", "deeper")
withContext(MDCContext()) {
logger.info("deep in the code")
throw Exception("Oh noes, a bug!")
}
}
fun main() {
runBlocking {
try {
MDC.put("additional", "info")
withContext(MDCContext()) {
// do something
logger.info("I swear I am useful")
deepInSomeOtherCode()
}
} catch (e: Exception) {
// I want to be able to log the MDC Context from
// where the exception occurred (the innermost coroutine context)
// alongside with this error message. So MDC context should be
// "additional": "info"
// "nesting": "deeper"
// but with this code, MDC will only contain "additional": "info"
// because MDCContext() will always install the MDC the coroutine was created with
// upon resuming, so you will always end up with the MDC from the outermost MDCContext()
logger.error("An error occurred", e)
}
}
}
Issue Analytics
- State:
- Created 3 years ago
- Comments:10 (5 by maintainers)
Top GitHub Comments
I’d suggest to enrich the exception with the context. This can be done by implementating a function like this and using it every time you need to adjust the context. Something like this:
There are different approaches to implement
enrichExceptionWithContext
function that you can choose based on your project’s needs and preferences:WeakHashMap
to associate exceptions with the additional context and use this map in your logging code.e.addSuppressed(...)
to attach this context to the exception before rethrowing it.Does it help?
There’s one idea that came to my mind. Saving it for the record here for further discussion: https://github.com/Kotlin/kotlinx.coroutines/issues/2426