You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Cloud Gateway全局过滤器仅记录成功请求,错误请求无日志求助

问题原因及解决方案

核心问题分析

  1. 请求日志未触发:你的ServerHttpRequestDecorator仅在getBody()方法内初始化并输出请求日志,但该方法仅当下游服务(或网关自身)读取请求体时才会执行。如果网关直接返回错误响应(如500)且未消费请求体,getBody()不会被调用,请求日志自然无法输出。
  2. 响应日志失效:
    • 错误响应的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 23:40:32