当前位置: 首页 > 知识库问答 >
问题:

Spring cloud gateway-响应体日志记录导致直接内存错误

令狐灿
2023-03-14

下面是我使用Spring Cloud gateway构建API网关的堆栈-

SCG - Hoxton.RELEASE
Java - 1.8
Spring Boot - 2.2.1.RELEASE

我从互联网上得到了下面的“GlobalFilter”代码来记录请求/响应正文。这很好,但我会遇到这样的错误

"failed to allocate 16777216 byte(s) of direct memory (used: 1023410183, max: 1038876672)"
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;

import com.xx.GatewayJsonLogger;

import lombok.extern.slf4j.Slf4j;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Component
@Slf4j
public class RequestResponseLogFilter implements GlobalFilter, Ordered {

    private static final String MAGIC_HEADER = "x-debug";
    private static final String START_TIME = "startTime";

    private static final String HTTP_SCHEME = "http";

    private static final String HTTPS_SCHEME = "https";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        List<String> debugHeader = exchange.getRequest().getHeaders().get(MAGIC_HEADER);
        if (!log.isDebugEnabled() && debugHeader == null) {
            // DO NOTHING
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        URI requestURI = request.getURI();
        String scheme = requestURI.getScheme();
        if (debugHeader != null) {
            String debugHeaderContent = debugHeader.get(0);
            if (!debugHeaderContent.equalsIgnoreCase("true") && !requestURI.getPath().toLowerCase().contains(debugHeaderContent.toLowerCase())) {
                return chain.filter(exchange);
            }
        }
        if ((!HTTP_SCHEME.equalsIgnoreCase(scheme) && !HTTPS_SCHEME.equals(scheme))) {
            return chain.filter(exchange);
        }
        long startTime = System.currentTimeMillis();
        exchange.getAttributes().put(START_TIME, startTime);
        logRequest(request, exchange.getAttribute("cachedRequestBodyObject"));
        return chain.filter(exchange.mutate().response(logResponse(exchange)).build());
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }

    private void logRequest(ServerHttpRequest request, String body) {
        URI requestURI = request.getURI();
        String scheme = requestURI.getScheme();
        HttpHeaders headers = request.getHeaders();
        log.info("Request Scheme:{},Path:{}", scheme, requestURI.getPath());
        log.info("Request Method:{},IP:{},Host:{}", request.getMethod(), request.getRemoteAddress(), requestURI.getHost());
        headers.forEach((key, value) -> log.debug("Request Headers:Key->{},Value->{}", key, value));
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        if (!queryParams.isEmpty()) {
            queryParams.forEach((key, value) -> log.info("Request Query Param :Key->({}),Value->({})", key, value));
        }
        MediaType contentType = headers.getContentType();
        long length = headers.getContentLength();
        log.info("Request ContentType:{},Content Length:{}", contentType, length);
        if (body != null) {
            GatewayJsonLogger.log(null, requestURI.getPath(), body.replaceAll("\"password\"\\s*:\\s*\".*\"", "\"password\":\"*******\""), "REQUEST");
        }
    }

    private ServerHttpResponseDecorator logResponse(ServerWebExchange exchange) {
        ServerHttpResponse origResponse = exchange.getResponse();
        Long startTime = exchange.getAttribute(START_TIME);
        log.info("Response HttpStatus:{}", origResponse.getStatusCode());
        HttpHeaders headers = origResponse.getHeaders();
        headers.forEach((key, value) -> log.debug("[RequestLogFilter]Headers:Key->{},Value->{}", key, value));
        MediaType contentType = headers.getContentType();
        long length = headers.getContentLength();
        log.info("Response ContentType:{},Content Length:{}", contentType, length);
        Long executeTime = (System.currentTimeMillis() - startTime);
        log.info("Response Original Path:{},Cost:{} ms", exchange.getRequest().getURI().getPath(), executeTime);
        DataBufferFactory bufferFactory = origResponse.bufferFactory();
        return new ServerHttpResponseDecorator(origResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.map(dataBuffer -> {
                        byte[] content = new byte[dataBuffer.readableByteCount()];
                        dataBuffer.read(content);
                        String bodyContent = new String(content, StandardCharsets.UTF_8);
                        GatewayJsonLogger.log(null, exchange.getRequest().getURI().getPath(), bodyContent, "RESPONSE");
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body);
            }
        };

    }
}
1. Enable DEBUG logging for reactor.netty
2. Add below property to application.yml
   spring.cloud.gateway.httpserver.wiretap = true

它的工作原理是,我可以在日志文件中看到请求和响应头+正文,但它不是一种可读的格式(我认为是某种十六进制格式)。

你好,雅各布

共有1个答案

公英哲
2023-03-14

似乎应该使用dataBuffer.release()发布您使用的dataBuffer,因为您使用bufferFactory.wrap(content)提供了一个新的dataBuffer,所以原始dataBuffer永远不会发布,并导致内存泄漏。

 类似资料:
  • 标题应该是不言自明的。 出于调试目的,我希望express打印每个服务请求的响应代码和正文。打印响应代码很容易,但打印响应体更为复杂,因为响应体似乎不容易作为属性使用。 以下情况不起作用: 当然,我可以很容易地在发出请求的客户端打印响应,但我也更喜欢在服务器端打印。 PS:如果有帮助的话,我所有的响应都是json,但希望有一个解决方案可以处理一般的响应。

  • 需要帮助...我在这里做错了什么???我相信它确实从应用程序属性文件中读取路径和文件名。但我不认为它读取了logback.xml或logback-spring.xml 我做了一些研究,发现了许多问题/答案。但是我想每个人都说要把logback xml放在资源中,把路径和文件名放在application.properties.我知道这很简单,但是在某个地方遗漏了一些东西… 提前谢谢!! 应用程序属性

  • 我想在我的应用程序中使用SLF4J+logback用于两个目的--日志和审计。 14:41:57.978[main]信息AUDIT_LOGGER-110欢迎使用main 如何确保审核消息在审核记录器下只出现一次?

  • 问题内容: 我正在考虑将Redis用于Web应用程序日志记录目的。我用谷歌搜索,有人将日志转储到Redis队列/列表中,然后将计划的工作人员转储到磁盘中。 http://nosql.mypopescu.com/post/8652869828/another-redis-use-case- centralized-logging 我希望寻求理解,为什么不直接使用Redis持久化到磁盘?如果我分配了一

  • logging 模块自 2.3 版以来一直是 Python 标准库的一部分。在 PEP 282 中有对它的简洁描述。除了 基础日志教程 之外,这些文档是非常难以阅读的。 日志记录一般有两个目的: 诊断日志 记录与应用程序操作相关的日志。例如,当用户遇到程序报错时, 可通过搜索诊断日志以获得上下文信息。 审计日志 为商业分析而记录的日志。从审计日志中,可提取用户的交易信息, 并结合其他用户资料构成用

  • 我试图实现log4j日志记录类。代码的包结构如下所述。目前我的log4j.properties放在包的资源下。 我有时确实会得到日志,但有时调试器会显示“log4j: WARN无法为记录器找到附加程序”所以我最后得到了: 我应该在哪里放置log4j.properties来获得MyTest中一致的日志? 同一个项目可以在不同的包中有多个log4j.properties吗? 如何将调试器/编译器的lo