Search code examples
javaspringperformancelogginglogback

Logging large string causes OutOfMemoryError


My Spring Boot web application connects to many external services and it has a requirement to write all requests and responses to and from the services to log files.

I use Logback for the log engine. And following code is for printing the response message to log files.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

....

private static final Logger LOG = LoggerFactory.getLogger(RequestLoggingClientRequestInterceptor.class);

...

ClientHttpResponse response = execution.execute(request, body);
InputStream s = response.getBody();
String loggingResponseBody = new String(ByteStreams.toByteArray(s), Charset.forName("UTF-8"));
LOG.info("response status code: {}, response headers: {}, response body: {}",
            response.getStatusCode(),
            response.getHeaders(),
            loggingResponseBody);

This code has performance problem. It consumes high CPU, causes high latency and, in some case where the response message is very large, it causes an OutOfMemoryError in execution of new String(ByteStreams.toByteArray(s), Charset.forName("UTF-8"));

Caused by: java.lang.OutOfMemoryError: Java heap space
    at java.lang.StringCoding.decode(StringCoding.java:215)
    at java.lang.String.<init>(String.java:463)
    at java.lang.String.<init>(String.java:515)
    at ...

Note that I have no specific requirement to decode the string to 'UTF-8', I just did so because the constructor of the String class suggests.

Please suggest how to improve the code to solve the performance problem. I have tried AsyncAppender, though it helps with the latency, I am stuck with the OutOfMemoryError.


Solution

  • A ClientHttpResponse must be closed (as specified), which ByteStreams.toByteArray(s) does not (as specified).

    try (InputStream s = response.getBody()) {
        LOG.info("response status code: {}, response headers: {}, response body:",
                response.getStatusCode(),
                response.getHeaders());
        String loggingResponseBody = new String(ByteStreams.toByteArray(s),
                StandardCharsets.UTF_8);
        LOG.info(loggingResponseBody); // One param means no format with {}
    }
    

    So it might just be a resource leak. The code seems brittle, as it must be certain that the response is UTF-8 text and not exceeding senseless megabytes.