Logback access events not capturing response content

209 views Asked by At

We are developing a new service using Spring Boot and Spring for GraphQL. We want to enable logback access logs to capture both request and response headers and content. I have configured logback access logs but it's only working for the request headers and content but response content is always empty

application.yaml

logback:
  access:
    enabled: true
    config: classpath:logback/logback-access.xml
    useServerPortInsteadOfLocalPort: true
    tee-filter:
      enabled: true
  tomcat:
    enableRequestAttributes: true

logback-access.xml

<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%fullRequest%n%n%fullResponse</pattern>
    </encoder>
</appender>
<appender-ref ref="CONSOLE"/>

TeeFilter config: I have tried with order range [-1,0,1,9999,-9999]

@Bean
public FilterRegistrationBean<TeeFilter> someFilterRegistration() {
    FilterRegistrationBean<TeeFilter> registrationBean = new FilterRegistrationBean<>(new TeeFilter());
    registrationBean.addUrlPatterns("/*");
    registrationBean.setName("TeeFilter");
    registrationBean.setOrder(-1);

    return registrationBean;
}

and sample console output is

POST /graphql HTTP/1.1
accept: application/json, multipart/mixed
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9,de;q=0.8
authorization: xxxx
connection: keep-alive
content-length: 253
content-type: application/json
cookie: Idea-89db1df9=f548ac74-ebc1-4a6a-b686-1517cf9a6c8b; JSESSIONID=B6F9566795FF0216864B7D37F70FF61D

{"query":"xxxx"}

HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Connection: keep-alive
Content-Type: application/json
Date: Thu, 12 Oct 2023 08:52:42 GMT
Expires: 0
Keep-Alive: timeout=60
Pragma: no-cache
Transfer-Encoding: chunked
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0

This request has graphql response content but log cannot capture it. Why log configuration only capturing request content but not response ? It's strange because exact the same configuration working for our our another Spring Boot Rest service. Is it somehow related to the Spring for Graphql or Graphql for Java ?

1

There are 1 answers

0
Nodirbek Shamsiev On

After many trials and errors, I have found a solution that may be not the optimal one. I disabled TeeFilter and registered two filters: one for DispatcherType.ASYNC to capture response content and another for DispatcherType.REQUEST to capture request content.

Configuration for filter registration

@Configuration
public class LoggingConfiguration {

    @Bean
    public FilterRegistrationBean<ResponseContentCaptureFilter> responseContentCaptureFilter() {
        FilterRegistrationBean<ResponseContentCaptureFilter> registrationBean = new FilterRegistrationBean<>(new ResponseContentCaptureFilter());
        registrationBean.addUrlPatterns("/graphql");
        registrationBean.setName("ResponseContentCaptureFilter");
        registrationBean.setOrder(1);
        registrationBean.setAsyncSupported(true);
        registrationBean.setDispatcherTypes(DispatcherType.ASYNC);

        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<RequestContentCaptureFilter> requestContentCaptureFilter() {
        FilterRegistrationBean<RequestContentCaptureFilter> registrationBean = new FilterRegistrationBean<>(new RequestContentCaptureFilter());
        registrationBean.addUrlPatterns("/graphql");
        registrationBean.setName("RequestContentCaptureFilter");
        registrationBean.setOrder(1);
        registrationBean.setDispatcherTypes(DispatcherType.REQUEST);

        return registrationBean;
    }
} 

and filters: ResponseContentCaptureFilter

import static ch.qos.logback.access.AccessConstants.LB_OUTPUT_BUFFER;

@Slf4j
public class ResponseContentCaptureFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingResponseWrapper cashedResponse = new ContentCachingResponseWrapper((HttpServletResponse) response);

        chain.doFilter(request, cashedResponse);

        request.setAttribute(LB_OUTPUT_BUFFER, cashedResponse.getContentAsByteArray());

        cashedResponse.copyBodyToResponse();
    }
}

RequestContentCaptureFilter

import static ch.qos.logback.access.AccessConstants.LB_INPUT_BUFFER;

@Slf4j
public class RequestContentCaptureFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        ContentCachingRequestWrapper cachedRequest = new ContentCachingRequestWrapper((HttpServletRequest) request);

        chain.doFilter(cachedRequest, response);

        cachedRequest.setAttribute(LB_INPUT_BUFFER, cachedRequest.getContentAsByteArray());
    }
}

This approach has a drawback as it breaks the testing integration of Spring Boot for GraphQL, causing the request execution to fail in tests.