Spring Cloud Gateway中如何记录请求URI、请求体及关联路由?
Spring Cloud Gateway 全局日志过滤器实现(记录URI、请求体及关联路由)
你的现有实现存在两个核心问题:一是读取请求体后没有重新包装,导致后续过滤器无法再读取请求体;二是过滤器执行顺序过早,还没完成路由匹配,所以拿不到关联的路由信息。以下是修正后的完整实现方案:
核心实现思路
- 控制过滤器执行顺序:确保在路由匹配完成后执行,通过实现
Ordered接口指定优先级,顺序值需大于RouteToRequestUrlFilter的默认优先级(10000)。 - 缓存并复用请求体:由于Reactive场景下请求体数据流只能订阅一次,需读取后将内容缓存,并通过
ServerHttpRequestDecorator重新包装请求,保证后续流程能正常读取。 - 获取匹配路由信息:从
ServerWebExchange的属性中提取已匹配的路由对象,拿到路由ID和目标URI。
完整代码实现
import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.route.Route; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpRequestDecorator; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; import java.util.logging.Logger; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; @Component public class RequestLoggingFilter implements GlobalFilter, Ordered { private static final Logger logger = Logger.getLogger(RequestLoggingFilter.class.getName()); // 优先级高于路由转发过滤器,确保已完成路由匹配 private static final int FILTER_ORDER = 10001; @Override public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); // 获取已匹配的路由信息 Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); // 记录请求基本信息和路由信息 logger.info(String.format("Incoming request - URI: %s, Method: %s, Route ID: %s, Target URI: %s", request.getURI(), request.getMethod(), route.getId(), route.getUri())); // 仅对POST/PUT请求记录请求体 if ("POST".equalsIgnoreCase(request.getMethodValue()) || "PUT".equalsIgnoreCase(request.getMethodValue())) { return DataBufferUtils.join(request.getBody()) .flatMap(dataBuffer -> { // 缓存请求体字节数组 byte[] bodyBytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bodyBytes); DataBufferUtils.release(dataBuffer); // 记录请求体 String requestBody = new String(bodyBytes, StandardCharsets.UTF_8); logger.info("Request Body: " + requestBody); // 重新包装请求,让后续过滤器能读取请求体 ServerHttpRequest decoratedRequest = new ServerHttpRequestDecorator(request) { @Override public Flux<DataBuffer> getBody() { return Flux.defer(() -> { DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bodyBytes); DataBufferUtils.retain(buffer); return Mono.just(buffer); }); } }; // 继续执行过滤器链 return chain.filter(exchange.mutate().request(decoratedRequest).build()); }); } return chain.filter(exchange); } @Override public int getOrder() { return FILTER_ORDER; } }
关键细节说明
- 路由信息获取:通过
GATEWAY_ROUTE_ATTR常量从exchange属性中获取已匹配的Route对象,能拿到路由ID、目标URI等核心信息。 - 请求体重用:使用
DataBufferUtils.join合并所有请求体缓冲块,读取后通过ServerHttpRequestDecorator重新生成请求体数据流,避免后续流程因数据流已被消费而报错。 - 过滤器顺序:设置
FILTER_ORDER = 10001,确保在RouteToRequestUrlFilter(order=10000)之后执行,此时路由匹配已完成,能正确获取路由信息。
注意事项
- 大请求体处理:如果存在大文件上传等场景,直接缓存请求体到内存可能导致OOM,建议增加大小判断,超过阈值的请求体跳过记录。
- 敏感信息过滤:日志中避免记录密码、令牌等敏感字段,可在记录前对请求体进行脱敏处理。
- 日志级别调整:生产环境建议将日志级别从
INFO改为DEBUG,避免过多日志输出影响性能。
内容的提问来源于stack exchange,提问作者milad sorour
相关产品推荐
相关产品推荐

