You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多模块Kotlin项目中基于kotlinx.coroutines.slf4j跨模块共享SLF4J MDC上下文的问题求助

多模块Kotlin项目中基于kotlinx.coroutines.slf4j跨模块共享SLF4J MDC上下文的问题求助

嗨,我来帮你梳理下这个跨模块MDC上下文丢失的问题~

问题根源

你遇到的核心问题是MDC是线程本地存储(Thread-Local),而Kotlin协程会在不同线程之间切换执行。你之前在控制器里调用伴生对象的logContext方法,只是把MDC数据存在了当前线程的本地存储里,但当协程切换到其他线程执行仓库模块的代码时,新线程的MDC是空的,自然拿不到之前设置的上下文。

另外,你尝试加的withContext(MDCContext())之所以没生效,是因为这个方法默认会取当前线程的MDC生成上下文,但如果当前协程已经切换了线程,这里拿到的本来就是空的MDC,自然传递不了。

解决方案

我们需要把MDC上下文和协程上下文绑定,而不是依赖线程本地存储,同时调整LoggingContext的实现方式,避免用静态的伴生对象操作MDC。

1. 重构LoggingContext类

把伴生对象的静态方法改成实例方法,并添加协程上下文包装的工具方法,确保MDC能跟着协程走:

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

@Singleton
class LoggingContext {
    private val logger = KotlinLogging.logger {}

    // 设置MDC上下文(在当前线程/协程中生效)
    fun setContext(name: String, id: String) {
        MDC.put("name", name)
        MDC.put("id", id)
        logger.info { "已设置MDC上下文: ${MDC.getCopyOfContextMap()}" }
    }

    // 获取当前MDC上下文的副本
    fun getCurrentContext() = MDC.getCopyOfContextMap() ?: emptyMap()

    // 包装协程执行逻辑,传递MDC上下文
    suspend fun <T> withMdcContext(block: suspend () -> T): T {
        return withContext(MDCContext(getCurrentContext())) {
            block()
        }
    }
}

2. 修改控制器代码,确保后续协程继承MDC上下文

在控制器的suspend函数里,设置完MDC后,把所有需要共享上下文的后续调用(比如仓库操作)都包裹在withMdcContext方法里:

@Get(PATH_GLOBAL)
suspend fun getGlobal(
    @PathVariable name: String,
    @PathVariable id: String,
    httpRequest: HttpRequest<*>,
    loggingContext: LoggingContext // 通过依赖注入获取实例
): HttpResponse<Resource> {
    // 先在当前协程的线程中设置MDC
    loggingContext.setContext(name, id)
    
    // 把后续的业务逻辑(包括跨模块调用)包裹在MDC上下文里
    return loggingContext.withMdcContext {
        // 这里调用仓库模块的代码,协程会带着MDC上下文切换线程
        readOperation.execute(trigger).fold(...)
    }
}

3. 仓库模块代码无需额外处理

经过上面的改造,仓库模块的suspend函数会自动继承上层协程的MDC上下文,不需要再额外添加withContext(MDCContext()):

override suspend fun readConfiguration(...): {
    logger.trace { "Reading configuration from repository" }
    val entity = configurationDataRepository.findBy(...)
    // 这里就能正常拿到MDC上下文了
    logger.info { "ConfigurationDataRepositoryImpl - 当前MDC上下文: ${MDC.getCopyOfContextMap()}" }
    return ...
}

关键注意点

  • 不要用静态方法(伴生对象)直接操作MDC:静态方法绑定的是当前线程,协程切换线程后就会丢失上下文;
  • 确保所有需要MDC的协程逻辑都运行在MDCContext包裹的上下文中;
  • 依赖注入的LoggingContext是单例,确保所有模块都使用同一个实例(你的项目已经标注了@Singleton,这部分没问题)。

备注:内容来源于stack exchange,提问作者Lorena

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 13:12:42