Spring Cloud Gateway全局过滤器仅记录成功请求,错误请求无日志求助
问题原因及解决方案
核心问题分析
- 请求日志未触发:你的
ServerHttpRequestDecorator仅在getBody()方法内初始化并输出请求日志,但该方法仅当下游服务(或网关自身)读取请求体时才会执行。如果网关直接返回错误响应(如500)且未消费请求体,getBody()不会被调用,请求日志自然无法输出。 - 响应日志失效:
- 错误响应的
ContentType可能为空,导致String.valueOf(getHeaders().getContentType())生成"null"字符串,无法匹配LOGGABLE_CONTENT_TYPES,跳过日志逻辑。 - 若错误响应是网关内部直接生成的(未经过
GatewayFilterChain后续处理),可能未正确使用装饰后的ServerHttpResponseDecorator。
- 错误响应的
代码修改方案
1. 重构请求日志逻辑
将请求日志的初始化、输出与getBody()解耦,确保无论请求体是否被消费,都能输出日志:
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 提前初始化请求日志实例 Logger requestLogger = new Logger(exchange.getRequest()); // 主动订阅请求体流,确保日志触发逻辑执行 exchange.getRequest().getBody() .doOnNext(ds -> { if (isLoggableContentType(exchange.getRequest().getHeaders().getContentType())) { requestLogger.appendBody(ds.asByteBuffer()); } }) .doFinally(s -> requestLogger.log()) .subscribe(); // 装饰请求体,仅用于捕获body内容(日志输出已通过上方订阅处理) ServerHttpRequestDecorator requestMutated = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return super.getBody().doOnNext(ds -> { if (isLoggableContentType(getHeaders().getContentType())) { requestLogger.appendBody(ds.asByteBuffer()); } }); } }; // 重构响应日志逻辑,处理ContentType为空的边界情况 ServerHttpResponseDecorator responseMutated = new ServerHttpResponseDecorator(exchange.getResponse()) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { Logger responseLogger = new Logger(getDelegate()); if (isLoggableContentType(getHeaders().getContentType())) { return join(body).flatMap(db -> { responseLogger.appendBody(db.asByteBuffer()); responseLogger.log(); return getDelegate().writeWith(Mono.just(db)); }); } else { responseLogger.log(); return getDelegate().writeWith(body); } } }; return chain.filter(exchange.mutate().request(requestMutated).response(responseMutated).build()); } // 抽取工具方法,统一处理ContentType判断逻辑 private boolean isLoggableContentType(MediaType contentType) { if (contentType == null) { return false; // 可根据需求调整,比如允许默认类型 } return LOGGABLE_CONTENT_TYPES.contains(contentType.toString().toLowerCase()); }
2. 确保错误响应经过装饰后的响应对象
如果是在网关内部直接生成错误响应,需确保使用当前ServerWebExchange中的响应对象(即已装饰后的responseMutated),示例:
// 错误响应生成示例 byte[] errorJson = "{\"code\":500,\"msg\":\"server error\"}".getBytes(); return exchange.getResponse().writeWith(Mono.just( exchange.getResponse().bufferFactory().wrap(errorJson) ));
关键说明
- 请求日志通过主动订阅请求体流的方式,打破了依赖下游消费请求体的限制,确保日志必触发。
- 工具方法
isLoggableContentType统一处理了ContentType为空的边界情况,避免空指针或匹配失败。 - 响应装饰器覆盖了所有网关输出的响应场景,包括内部生成的错误响应。
内容的提问来源于stack exchange,提问作者CrowFlo
相关产品推荐
相关产品推荐

