从spring webflux WebFilter设置协同程序上下文

mcvgt66p  于 2022-11-21  发布在  Spring
关注(0)|答案(2)|浏览(281)

如何从spring webflux WebFilter设置协同程序上下文?这是可能的吗?我知道我可以使用React器上下文,但我不能设置协同程序上下文。

更多详细信息:

我想使用MDCContext将MDC传播到slf 4j。例如,我想从HTTP头中获取MDC,然后我想将这些值自动传播到我编写的任何日志。
目前,我可以:

  • 我在WebFilter中设置了reactor上下文
  • 在每个控制器中,我从reactor上下文中获取值,并将它们放入MDCContext(协程)中

正如你所看到的,这不是很方便,因为我必须在控制器中添加额外的代码。
有没有一种方法可以自动将Reactor上下文转换为协程上下文?我知道我可以使用ContextInjector和ServiceLoader进行相反的转换(请参见https://github.com/Kotlin/kotlinx.coroutines/issues/284#issuecomment-516270570),但似乎没有这样的反向转换机制。

sgtfey8w

sgtfey8w1#

@Component
class AuthorizationFilter : WebFilter {
    override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
        return chain.filter(exchange).contextWrite { ctx = ctx.put(KEY1, VALUE1) }
}

和:Using ReactiveSecurityContextHolder inside a Kotlin Flow

更新3(2022年1月25日)

我已经创建了一个库来解决React式环境中的MDC LocalThread问题。我已经创建了一个特殊的Map实现MDC类,它在React式环境中运行。
https://github.com/Numichi/reactive-logger

更新1

在Kotlin协程中使用和添加上下文。

val value1 = coroutineContext[ReactiveContext]?.context?.get(KEY1) // VALUE1

//--

withContext(Context.of()) {
     val x = coroutineContext[ReactiveContext]?.context?.get(KEY1) // NoSuchElementException
}

withContext(coroutineContext[ReactiveContext]?.context?.asCoroutineContext()) {
     val x = coroutineContext[ReactiveContext]?.context?.get(KEY1) // Work
}

// Add new key-pair context
val newContext = Context.of(coroutineContext[ReactiveContext]?.context ?: Context.of()).put(KEY2, VALUE2)
withContext(newContext.asCoroutineContext()) {
     val x = coroutineContext[ReactiveContext]?.context?.get(KEY2) // Work
}

更新2(2021年12月25日)

我将Log4j 2与slf 4j一起使用。但是,我认为它可以在另一种实现中工作(例如:回登录)。
build.gradle.kts

configurations {
    // ...
    all {
        exclude("org.springframework.boot", "spring-boot-starter-logging")
    }
    // ...
}

// ...

dependencies {
    // ...
    implementation("org.springframework.boot:spring-boot-starter-log4j2:VERSION")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:VERSION")
    // ...
}

(可选)如果您通过WebFlux使用WebFilter和writeContext。是否要将所有ReactorContext副本放入MDCContext中,请使用以下代码。您将在控制器的开始遇到包含所有ReactorContext元素的MDCContext。
如果你想使用@ExceptionHandler,MDCContext会丢弃你在控制器后面添加的所有值MDC.put("key", "value"),因为运行程序退出了挂起的作用域。它们的工作方式类似于代码变量和代码块。所以,我建议保存异常中的所有值,并从可抛出的示例中恢复到处理程序中。

package your.project.package

import org.slf4j.MDC
import reactor.core.CoreSubscriber
import reactor.core.publisher.Hooks
import reactor.core.publisher.Operators
import reactor.util.context.Context
import java.util.stream.Collectors
import javax.annotation.PostConstruct
import javax.annotation.PreDestroy
import org.reactivestreams.Subscription
import org.springframework.context.annotation.Configuration

@Configuration
class MdcContextLifterConfiguration {
    companion object {
        val MDC_CONTEXT_REACTOR_KEY: String = MdcContextLifterConfiguration::class.java.name
    }

    @PostConstruct
    fun contextOperatorHook() {
        Hooks.onEachOperator(MDC_CONTEXT_REACTOR_KEY, Operators.lift { _, subscriber -> MdcContextLifter(subscriber) })
    }

    @PreDestroy
    fun cleanupHook() {
        Hooks.resetOnEachOperator(MDC_CONTEXT_REACTOR_KEY)
    }
}

class MdcContextLifter<T>(private val coreSubscriber: CoreSubscriber<T>) : CoreSubscriber<T> {

    override fun onNext(t: T) {
        coreSubscriber.currentContext().copyToMdc()
        coreSubscriber.onNext(t)
    }

    override fun onSubscribe(subscription: Subscription) {
        coreSubscriber.onSubscribe(subscription)
    }

    override fun onComplete() {
        coreSubscriber.onComplete()
    }

    override fun onError(throwable: Throwable?) {
        coreSubscriber.onError(throwable)
    }

    override fun currentContext(): Context {
        return coreSubscriber.currentContext()
    }
}

private fun Context.copyToMdc() {
    if (!this.isEmpty) {
        val map: Map<String, String> = this.stream()
            .collect(Collectors.toMap({ e -> e.key.toString() }, { e -> e.value.toString() }))

        MDC.setContextMap(map)
    } else {
        MDC.clear()
    }
}

所以你可以使用MDCContext(或者在任何类中)。Ofc,不需要每次都调用LoggerFactory.getLogger(javaClass)。这也可以组织成属性。

import kotlinx.coroutines.slf4j.MDCContext
import kotlinx.coroutines.withContext
import org.slf4j.LoggerFactory

// ...

suspend fun info() {
    withContext(MDCContext()) {
        LoggerFactory.getLogger(javaClass).info("")
    }
}

在log4j2.xml中,您可以引用MDC键并将其加载到此处。例如:

  • <PatternLayout pattern="%mdc{context_map_key}">
  • 或者创建自我输出Plugin。
    Log4J插件

使用annotationProcessor添加多个依赖项

dependencies {
    // ...
    annotationProcessor("org.apache.logging.log4j:log4j-core:VERSION")
    // ...
}

写plugin. Ofc,它是一个极简主义者:

package your.project.package.log4j

import org.apache.logging.log4j.core.Core
import org.apache.logging.log4j.core.Layout
import org.apache.logging.log4j.core.LogEvent
import org.apache.logging.log4j.core.config.plugins.Plugin
import org.apache.logging.log4j.core.config.plugins.PluginFactory
import org.apache.logging.log4j.core.layout.AbstractStringLayout
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets

@Plugin(name = ExampleLog4JPlugin.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Layout.ELEMENT_TYPE)
class ExampleLog4JPlugin private constructor(charset: Charset) : AbstractStringLayout(charset) {
    companion object {
        const val PLUGIN_NAME = "ExampleLog4JPlugin"

        @JvmStatic
        @PluginFactory
        fun factory(): ExampleLog4JPlugin{
            return ExampleLog4JPlugin(StandardCharsets.UTF_8)
        }
    }

    override fun toSerializable(event: LogEvent): String {
        // event.contextData <-- this will contain MDCContext map
        return "String return. Itt this will appear in the log."
    }
}

还有log4j2.xml什么的。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Configuration packages="your.project.package.log4j">
    <Appenders>
        <Console name="stdout" target="SYSTEM_OUT">
            <ExampleLog4JPlugin/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="stdout"/>
        </Root>
    </Loggers>
</Configuration>
iqxoj9l9

iqxoj9l92#

很遗憾,现在还不可能。Spring框架中有一个开放的问题可以修复这个问题,您可以upvote -〉https://github.com/spring-projects/spring-framework/issues/26977

相关问题