How to share MDC context between threads in multimodule project in kotlin - kotlinx.coroutines.slf4j

1.5k Views Asked by At

I have a multi-module project where I would like to have a context to be shared through the different modules.

In my core module (from which all of them depends on), I have created a class to manage the context using org.slf4j.MDC with two functions, one to store properties in the context (logContext) and another one to get the context (getContext)

import jakarta.inject.Singleton
import kotlinx.coroutines.slf4j.MDCContextMap
import mu.KotlinLogging
import org.slf4j.MDC

private val logger = KotlinLogging.logger {}

@Singleton
class LoggingContext {
    companion object {
        fun logContext(name: String, id: String) {
            MDC.put("name", name)
            MDC.put("id", id)
            logger.info { "Companion object - logContext: ${MDC.getCopyOfContextMap()}" }
        }

        fun getContext(): MDCContextMap {
            logger.info { "Companion object - getContext: ${MDC.getCopyOfContextMap()}" }
            return MDC.getCopyOfContextMap()
        }
    }
}

The problem is as follow:

I have a full module where I have a controller and I want to store the name and id that is coming from the request in the context. For that, I just add the following line in my controller code:

logContext("request_name", "request_id")

Within the controller code (in the controller module), if I invoke the getContext() function I'm getting the properties that I just saved from the request. Until here everything's fine ✅

❌ But, if I tried to do the same, get the context from another module, for example, invoke the getContext() function from the repository module (it is a fully new module within the project) I'm getting a null. There is no context at all.

I'm assuming is because, when I tried to get the context from another module, it is other thread and the context is not shared within the whole project.

UPDATE: to give more context, the flow is as follow:

  1. This is the endpoint who receive the request where I want to store the name and id properties in the context.
@Get(PATH_GLOBAL)
    suspend fun getGlobal(
        @PathVariable name: String,
        @PathVariable id: String,
        httpRequest: HttpRequest<*>,
    ): HttpResponse<Resource> {
        LoggingContext.logContext(name, id)
        ...
    // invoke repository
        return readOperation.execute(trigger).fold(...)
    }
  1. Previous endpoints invoke the following function that is stored in another module.
override suspend fun readConfiguration(...): {
        logger.trace { "Reading configuration from repository" }
        val entity = configurationDataRepository.findBy(...)
        
        logger.info { "ConfigurationDataRepositoryImpl - readConfiguration4: ${MDC.getCopyOfContextMap()}" }

        withContext(MDCContext()) {
            logger.info { "ConfigurationDataRepositoryImpl - readConfiguration1: ${MDC.getCopyOfContextMap()}" }
        }

        return ...
    }

I tried adding withContext(MDCContext()){...} but I'm getting a null when calling getContext() function with and without withContext...

So, how can I set the context to be shared within all the modules?

Thank you very much in advance for all your help :)

1

There are 1 best solutions below

2
On

Apparently the solution is as easy as adding withContext(MDCContext()) right after setting the MDC Context to preserve the MDC Context.

So, in my case, I have to wrap my controller with the previous function like that:

@Get(PATH_GLOBAL)
    suspend fun getGlobal(
        @PathVariable name: String,
        @PathVariable id: String,
        httpRequest: HttpRequest<*>,
    ): HttpResponse<Resource> {

        LoggingContext.logContext(name, id)
    
        // Add withContext(MDCContext()) right after setting the Context
        return withContext(MDCContext()) {
            ...
            // invoke repository
            readOperation.execute(trigger).fold(...)
        }
    }

That works for me!