@Component
class AuthorizationFilter : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
return chain.filter(exchange).contextWrite { ctx = ctx.put(KEY1, VALUE1) }
}
并且:在 Kotlin 流程中使用 ReactiveSecurityContextHolder
更新 3 (25.01.2022)
我创建了一个库来解决反应式环境中的 MDC LocalThread 问题。我创建了一个特殊的 Map 实现 MDC 类,该类在反应式上下文中进行。
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 (25.12.2021)
我将 Log4j2 与 slf4j 一起使用。但是,我认为它将适用于另一种实现(例如:logback)。
构建.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}">
- 或创建自输出插件。
Log4J 插件
添加多个依赖项annotationProcessor
dependencies {
// ...
annotationProcessor("org.apache.logging.log4j:log4j-core:VERSION")
// ...
}
写插件。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 中的内容project/src/main/resources/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>