feat(trace): Refactoring the tracking module - Remove the ignorePatten property in TraceProperties

- Delete FeignTraceAutoConfiguration, WebClientTraceAutoConfiguration, and RequestIdTaskDecorator
- Refactor TraceAutoConfiguration and use ContextFieldContributor to replace the original filters and interceptors
- Remove the RequestIdUtil class and use StringContextHolder to replace thread local storage
- Update TraceRequestIdProvider and use ContextFieldContributor to provide the request ID
- Remove useless extension functions and filter classes
This commit is contained in:
gewuyou 2025-06-05 16:07:41 +08:00
parent b90048a57d
commit 0e74483168
15 changed files with 33 additions and 476 deletions

View File

@ -21,10 +21,4 @@ class TraceProperties {
* MDCMapped Diagnostic Context中用于存储请求ID的键名默认为"requestId"
*/
var requestIdMdcKey: String = "requestId"
/**
* 配置忽略日志记录的路径模式通常用于静态资源文件
* 默认忽略以.css.js.png等结尾的静态资源请求
*/
var ignorePatten = arrayOf(".*\\.(css|js|png|jpg|jpeg|gif|svg)")
}

View File

@ -1,7 +1,9 @@
plugins {
alias(libs.plugins.kotlin.plugin.spring)
}
dependencies {
implementation(project(Modules.Core.EXTENSION))
compileOnly(platform(libs.springBootDependencies.bom))
implementation(platform(libs.springBootDependencies.bom))
implementation(libs.springBoot.autoconfigure)
compileOnly(platform(libs.springCloudDependencies.bom))
compileOnly(libs.springBootStarter.web)
compileOnly(libs.springBootStarter.webflux)

View File

@ -1,34 +0,0 @@
package com.gewuyou.forgeboot.trace.autoconfig
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
*Feign跟踪自动配置
*
* @since 2025-05-31 22:02:56
* @author gewuyou
*/
@Configuration
@ConditionalOnClass(name = ["feign.RequestInterceptor"])
open class FeignTraceAutoConfiguration {
/**
* Feign 拦截器仅当 Feign 存在时生效
*
* 该拦截器用于在Feign客户端中传递请求ID
* @param traceProperties 跟踪配置属性
* @return FeignRequestIdInterceptor实例
*/
@Bean
@ConditionalOnMissingBean(name = ["feignRequestIdInterceptor"])
open fun feignRequestIdInterceptor(traceProperties: TraceProperties): Any {
val clazz = Class.forName("com.gewuyou.forgeboot.trace.impl.interceptor.FeignRequestIdInterceptor")
val constructor = clazz.getConstructor(TraceProperties::class.java)
log.info( "创建FeignRequestIdInterceptor实例")
return constructor.newInstance(traceProperties)
}
}

View File

@ -1,18 +1,18 @@
package com.gewuyou.forgeboot.trace.autoconfig
import com.gewuyou.forgeboot.context.api.ContextFieldContributor
import com.gewuyou.forgeboot.context.api.entities.FieldDef
import com.gewuyou.forgeboot.context.api.enums.Scope
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import com.gewuyou.forgeboot.trace.impl.decorator.RequestIdTaskDecorator
import com.gewuyou.forgeboot.trace.impl.filter.ReactiveRequestIdFilter
import com.gewuyou.forgeboot.trace.impl.filter.RequestIdFilter
import com.gewuyou.forgeboot.trace.impl.provider.TraceRequestIdProvider
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.util.UUID
/**
* 跟踪自动配置
@ -23,37 +23,9 @@ import org.springframework.context.annotation.Configuration
*/
@Configuration
@EnableConfigurationProperties(TraceProperties::class)
open class TraceAutoConfiguration {
/**
* Spring MVC 过滤器仅当 Spring MVC 存在时生效
*
* 该过滤器用于在Spring MVC应用中生成和传递请求ID
* @param traceProperties 跟踪配置属性
* @return RequestIdFilter实例
*/
@Bean
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "servlet", matchIfMissing = true)
@ConditionalOnMissingBean
open fun requestIdFilter(traceProperties: TraceProperties): RequestIdFilter {
log.info("RequestIdFilter 已创建!")
return RequestIdFilter(traceProperties)
}
/**
* Spring WebFlux 过滤器仅当 Spring WebFlux 存在时生效
*
* 该过滤器用于在Spring WebFlux应用中生成和传递请求ID
* @param traceProperties 跟踪配置属性
* @return ReactiveRequestIdFilter实例
*/
@Bean
@ConditionalOnProperty(name = ["spring.main.web-application-type"], havingValue = "reactive")
@ConditionalOnMissingBean
open fun reactiveRequestIdFilter(traceProperties: TraceProperties): ReactiveRequestIdFilter {
log.info("ReactiveRequestIdFilter 已创建!")
return ReactiveRequestIdFilter(traceProperties)
}
class TraceAutoConfiguration(
private val traceProperties: TraceProperties
) {
/**
* 请求ID提供者用于生成请求ID
*
@ -62,23 +34,19 @@ open class TraceAutoConfiguration {
*/
@Bean
@ConditionalOnMissingBean(RequestIdProvider::class)
open fun traceRequestIdProvider(): TraceRequestIdProvider {
fun traceRequestIdProvider(): TraceRequestIdProvider {
log.info("TraceRequestIdProvider 已创建!")
return TraceRequestIdProvider()
return TraceRequestIdProvider(traceProperties)
}
/**
* 线程池装饰器用于 @Async
*
* 该装饰器用于在异步线程池中传递请求ID确保异步执行的任务能够携带正确地请求信息
* @param traceProperties 跟踪配置属性
* @return RequestIdTaskDecorator实例
*/
@Bean
@ConditionalOnMissingBean
open fun requestIdTaskDecorator(traceProperties: TraceProperties): RequestIdTaskDecorator {
log.info("RequestIdTaskDecorator 已创建!")
return RequestIdTaskDecorator(traceProperties)
fun requestContributor() = ContextFieldContributor {
setOf(
FieldDef(
header = traceProperties.requestIdHeaderName, // 请求-响应头名
key = traceProperties.requestIdMdcKey, // ctx/MDC 键
generator = { UUID.randomUUID().toString() }, // 如果前端没带,用这个生成
scopes = setOf(Scope.HEADER, Scope.MDC, Scope.REACTOR)
)
)
}
}

View File

@ -1,43 +0,0 @@
package com.gewuyou.forgeboot.trace.autoconfig
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import com.gewuyou.forgeboot.trace.impl.filter.WebClientRequestIdFilter
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Web客户端跟踪自动配置
*
* 该配置类用于自动配置Web客户端的请求ID过滤器以实现请求跟踪功能
* 它依赖于特定条件如类的存在和Bean的定义以确保在适当的时候进行配置
*
* @since 2025-05-31 21:59:02
* @author gewuyou
*/
@Configuration
@ConditionalOnClass(name = ["org.springframework.web.reactive.function.client.WebClient\$Builder"])
open class WebClientTraceAutoConfiguration {
/**
* 配置Web客户端的请求ID过滤器
*
* 该方法在满足条件时被调用它获取WebClient.Builder实例并应用请求ID过滤器
* 以便在发出请求时添加请求ID信息这对于跟踪请求跨多个服务非常有用
*
* @param webClientBuilder Web客户端构建器用于配置过滤器
* @param traceProperties 跟踪属性配置用于定制跟踪行为
* @return 配置后的WebClient.Builder实例
*/
@Bean
@ConditionalOnBean(name = ["webClientBuilder"])
open fun webClientRequestIdFilter(
webClientBuilder: org.springframework.web.reactive.function.client.WebClient.Builder,
traceProperties: TraceProperties
): org.springframework.web.reactive.function.client.WebClient.Builder {
log .info("配置Web客户端的请求ID过滤器")
return webClientBuilder.filter(WebClientRequestIdFilter(traceProperties))
}
}

View File

@ -1,3 +1 @@
com.gewuyou.forgeboot.trace.autoconfig.TraceAutoConfiguration
com.gewuyou.forgeboot.trace.autoconfig.WebClientTraceAutoConfiguration
com.gewuyou.forgeboot.trace.autoconfig.FeignTraceAutoConfiguration

View File

@ -1,7 +1,8 @@
dependencies {
implementation(project(Modules.Core.EXTENSION))
implementation(platform(libs.springBootDependencies.bom))
api(project(Modules.Core.EXTENSION))
api(project(Modules.Context.STARTER))
compileOnly(project(Modules.TRACE.API))
compileOnly(platform(libs.springBootDependencies.bom))
compileOnly(platform(libs.springCloudDependencies.bom))
compileOnly(libs.springBootStarter.webflux)
compileOnly(libs.springBootStarter.web)

View File

@ -1,29 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.decorator
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import org.slf4j.MDC
import org.springframework.core.task.TaskDecorator
/**
*请求ID任务装饰器
*
* @since 2025-03-17 16:57:54
* @author gewuyou
*/
class RequestIdTaskDecorator(
private val traceProperties: TraceProperties
) : TaskDecorator {
override fun decorate(task: Runnable): Runnable {
val requestIdMdcKey = traceProperties.requestIdMdcKey
// 获取主线程 requestId
val requestId = MDC.get(requestIdMdcKey)
return Runnable {
try {
MDC.put(requestIdMdcKey, requestId)
task.run()
} finally {
MDC.remove(requestIdMdcKey)
}
}
}
}

View File

@ -1,60 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.extension
import jakarta.servlet.http.HttpServletRequest
import org.springframework.http.HttpMethod
import org.springframework.http.server.reactive.ServerHttpRequest
import org.springframework.web.reactive.function.client.ClientRequest
/**
* 请求扩展
*
* @since 2025-05-02 21:59:26
* @author gewuyou
*/
/**
* 判断是否跳过请求
* @apiNote 这个方法是给反应式请求id过滤器使用的
* @return true: 跳过请求; false: 不跳过请求
*/
fun ServerHttpRequest.isSkipRequest(ignorePaths: Array<String>): Boolean {
return isSkipRequest(this.method.name(), this.uri.path, ignorePaths)
}
/**
* 判断是否跳过请求
* @apiNote 这个方法是给请求id过滤器使用的
* @return true: 跳过请求; false: 不跳过请求
*/
fun HttpServletRequest.isSkipRequest(ignorePaths: Array<String>): Boolean {
return isSkipRequest(this.method, this.requestURI, ignorePaths)
}
/**
* 判断是否跳过请求
* @apiNote 这个方法是给请求id过滤器使用的
* @return true: 跳过请求; false: 不跳过请求
*/
fun ClientRequest.isSkipRequest(ignorePaths: Array<String>): Boolean {
return isSkipRequest(this.method().name(), this.url().path, ignorePaths)
}
/**
* 判断是否跳过请求
* @param method 请求方法
* @param uri 请求路径
* @return true: 跳过请求; false: 不跳过请求
*/
fun isSkipRequest(method: String, uri: String, ignorePaths: Array<String>): Boolean {
return when {
// 跳过 OPTIONS 请求
HttpMethod.OPTIONS.name() == method -> true
// 跳过 HEAD 请求
HttpMethod.HEAD.name() == method -> true
// 跳过 TRACE 请求
HttpMethod.TRACE.name() == method -> true
// 跳过模式匹配
ignorePaths.any { uri.matches(Regex(it)) } -> true
else -> false
}
}

View File

@ -1,65 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.filter
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
import org.slf4j.MDC
import org.springframework.web.server.ServerWebExchange
import org.springframework.web.server.WebFilter
import org.springframework.web.server.WebFilterChain
import reactor.core.publisher.Mono
/**
* 反应式请求 ID 过滤器
*
* 该类的主要作用是在每个请求开始时生成或获取一个唯一的请求 ID并将其设置到日志上下文中
* 以便在后续的日志记录中能够追踪到该请求它还支持基于特定模式跳过某些请求的处理
*
* @param traceProperties 配置属性包含请求 ID 的头名称和 MDK 关键等信息
* @since 2025-02-09 02:14:49
* @author gewuyou
*/
class ReactiveRequestIdFilter(
private val traceProperties: TraceProperties
) : WebFilter {
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
val request = exchange.request
// 检测请求是否需要跳过
if (request.isSkipRequest(traceProperties.ignorePatten)) {
return chain.filter(exchange)
}
// 获取请求头中请求 ID 的名称和 MDK 中的键
val requestIdHeader = traceProperties.requestIdHeaderName
val requestIdMdcKey = traceProperties.requestIdMdcKey
// 尝试从请求头中获取 requestId如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
request.headers[requestIdHeader]?.let {
it.firstOrNull()?.let(RequestIdUtil::requestId::set) ?: RequestIdUtil.generateRequestId()
} ?: RequestIdUtil.generateRequestId()
// 获取当前的 requestId
val currentRequestId = RequestIdUtil.requestId
// 将 requestId 设置到日志中
MDC.put(requestIdMdcKey, currentRequestId)
log.info("设置 Request id: $currentRequestId")
// ✅ **创建新的 request 并更新 exchange**
// 更新请求头,确保后续的请求处理中包含 requestId
val mutatedRequest = request.mutate()
.header(requestIdHeader, currentRequestId)
.build()
val mutatedExchange = exchange.mutate().request(mutatedRequest).build()
// 放行请求
return chain.filter(mutatedExchange)
// ✅ 让 Reactor 线程也能获取 requestId
// 将 requestId 写入 Reactor 的上下文中,以便在异步处理中也能访问
.contextWrite { ctx -> ctx.put(requestIdMdcKey, currentRequestId!!) }
.doFinally {
// 清理 MDC 中的 requestId避免内存泄漏
MDC.remove(requestIdMdcKey)
// 将 requestId 清除
RequestIdUtil.removeRequestId()
}
}
}

View File

@ -1,64 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.filter
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.slf4j.MDC
import org.springframework.web.filter.OncePerRequestFilter
/**
* 请求过滤器
*
* 该过滤器用于在请求处理过程中添加和管理请求ID(RequestId)以便于日志追踪和调试
* 它基于Spring的OncePerRequestFilter确保每个请求只被过滤一次
*
* @param traceProperties Trace属性配置包含请求ID的相关配置信息
* @since 2025-01-02 14:31:07
* @author gewuyou
*/
class RequestIdFilter(
private val traceProperties: TraceProperties
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain
) {
// 检查请求是否需要跳过
if (request.isSkipRequest(traceProperties.ignorePatten)) {
return chain.doFilter(request, response)
}
// 获取请求头中 requestId 的名称和 MDC 中的键
val requestIdHeader = traceProperties.requestIdHeaderName
val requestIdMdcKey = traceProperties.requestIdMdcKey
try {
// 尝试从请求头中获取 requestId
request.getHeader(requestIdHeader)?.also(
RequestIdUtil::requestId::set
) ?: run {
// 如果没有,则生成新的 requestId
RequestIdUtil.generateRequestId()
}
// 获取 requestId
val requestId = RequestIdUtil.requestId
// 将requestId 设置到日志中
MDC.put(requestIdMdcKey, requestId)
log.info("设置 Request id: $requestId")
// 将 requestId 设置到响应头中
response.setHeader(requestIdHeader, requestId)
// 继续处理请求
chain.doFilter(request, response)
} finally {
// 移除 MDC 中的 requestId
MDC.remove(requestIdMdcKey)
// 清理当前线程的 RequestId防止内存泄漏
RequestIdUtil.removeRequestId()
}
}
}

View File

@ -1,58 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.filter
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import com.gewuyou.forgeboot.trace.impl.extension.isSkipRequest
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
import org.slf4j.MDC
import org.springframework.web.reactive.function.client.ClientRequest
import org.springframework.web.reactive.function.client.ClientResponse
import org.springframework.web.reactive.function.client.ExchangeFilterFunction
import org.springframework.web.reactive.function.client.ExchangeFunction
import reactor.core.publisher.Mono
/**
* Web客户端请求ID过滤器
*
* 该类的主要作用是在发出HTTP请求时确保请求ID的正确传递和记录
* 如果请求被忽略则直接传递不做处理否则会尝试从请求头中获取请求ID
* 如果获取不到则生成新的请求ID并将其设置到请求头中以及日志中以便于追踪请求
*
* @param traceProperties 追踪属性配置包括忽略模式请求ID头名称和MDK中的键
* @since 2025-05-02 22:18:06
* @author gewuyou
*/
class WebClientRequestIdFilter(
private val traceProperties: TraceProperties
) : ExchangeFilterFunction {
override fun filter(request: ClientRequest, next: ExchangeFunction): Mono<ClientResponse> {
// 检查请求是否被忽略,如果被忽略,则直接执行请求
if (request.isSkipRequest(traceProperties.ignorePatten)) {
return next.exchange(request)
}
// 获取请求头中请求 ID 的名称和 MDK 中的键
val requestIdHeader = traceProperties.requestIdHeaderName
val requestIdMdcKey = traceProperties.requestIdMdcKey
// 尝试从请求头中获取 requestId如果存在则设置到 RequestIdUtil 中,否则生成一个新的 requestId
request.headers()[requestIdHeader]?.let {
it.firstOrNull()?.let(RequestIdUtil::requestId::set) ?: RequestIdUtil.generateRequestId()
} ?: RequestIdUtil.generateRequestId()
// 获取当前的 requestId
val currentRequestId = RequestIdUtil.requestId
// 将 requestId 设置到日志中
MDC.put(requestIdMdcKey, currentRequestId)
log.info("设置 Request id: $currentRequestId")
// 创建一个新的请求,包含 requestId 头
val mutatedRequest = ClientRequest.from(request)
.header(requestIdHeader, currentRequestId)
.build()
// 执行请求,并在请求完成后清除 MDC 和 RequestIdUtil 中的 requestId
return next.exchange(mutatedRequest)
.doFinally {
MDC.remove(requestIdMdcKey)
RequestIdUtil.removeRequestId()
}
}
}

View File

@ -1,30 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.interceptor
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
import feign.RequestInterceptor
import feign.RequestTemplate
/**
* Feign请求ID 拦截器
*
* @since 2025-03-17 16:42:50
* @author gewuyou
*/
class FeignRequestIdInterceptor(
private val traceProperties: TraceProperties
) : RequestInterceptor {
override fun apply(template: RequestTemplate) {
// 尝试获取当前请求的请求id
val requestId = RequestIdUtil.requestId
requestId?.let {
// 如果请求id存在则添加到请求头中
template.header(traceProperties.requestIdHeaderName, requestId)
} ?: run {
log.warn("请求ID为null请检查您是否已在过滤链中添加了请求filter。")
}
}
}

View File

@ -1,7 +1,8 @@
package com.gewuyou.forgeboot.trace.impl.provider
import com.gewuyou.forgeboot.context.impl.StringContextHolder
import com.gewuyou.forgeboot.trace.api.RequestIdProvider
import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
import com.gewuyou.forgeboot.trace.api.config.TraceProperties
/**
@ -10,7 +11,9 @@ import com.gewuyou.forgeboot.trace.impl.util.RequestIdUtil
* @since 2025-05-03 17:26:46
* @author gewuyou
*/
class TraceRequestIdProvider: RequestIdProvider {
class TraceRequestIdProvider(
private val traceProperties: TraceProperties
): RequestIdProvider {
/**
* 获取请求ID
*
@ -19,6 +22,6 @@ class TraceRequestIdProvider: RequestIdProvider {
* @return 请求ID的字符串表示
*/
override fun getRequestId(): String {
return RequestIdUtil.requestId?:throw RuntimeException("requestId is null")
return StringContextHolder.get(traceProperties.requestIdMdcKey) ?:throw RuntimeException("requestId is null")
}
}

View File

@ -1,26 +0,0 @@
package com.gewuyou.forgeboot.trace.impl.util
import java.util.*
/**
* 请求 ID Util
* 这个类需配合 RequestIdFilter 使用用于生成请求 ID并将其绑定到线程变量中供后续可能需要的地方使用
* @author gewuyou
* @since 2025-01-02 14:27:45
*/
object RequestIdUtil {
private val REQUEST_ID_HOLDER = ThreadLocal<String>()
fun generateRequestId() {
REQUEST_ID_HOLDER.set(UUID.randomUUID().toString())
}
var requestId: String?
get() = REQUEST_ID_HOLDER.get()
set(uuid) {
REQUEST_ID_HOLDER.set(uuid)
}
fun removeRequestId() {
REQUEST_ID_HOLDER.remove()
}
}