Search code examples
javaspring-webfluxproject-reactorspring-webclient

Custom log format for HttpRequest and HttpResponse using webClient


I need log HttpRequest body and HttpResponse body in custom format. For example "Send request. Headers: {}, Body: {}, uri: {}"

How can I get request body?

I'm try this:

log.info("Send request. Headers: {}, Body: {}, uri: {}", request, headers, path);
        return webClient.post()
                .uri(path)
                .headers(httpHeaders -> {....})
                .bodyValue(request)...

But in this case, the log does not correspond to the fact of sending the request, but is only part of the reactor pipeline being formed

Can I log HttpRequest body and HttpResponse body in custom format in the moment of send/received this?


Solution

  • You can use RequestBodyAdviceAdapter & ResponseBodyAdvice for logging request-body, headers etc.
    Please find the code snippets.

    CustomResponseBodyAdviceAdapter.class.

    import com.pmi.hyperface.constants.Constants;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.http.server.ServletServerHttpResponse;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    
    @ControllerAdvice
    public class CustomResponseBodyAdviceAdapter implements ResponseBodyAdvice<Object> {
    
        @Autowired
        private LoggingService loggingService;
    
        @Override
        public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
            return true;
        }
    
        @Override
        public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
                Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest,
                ServerHttpResponse serverHttpResponse) {
    
            if (serverHttpRequest instanceof ServletServerHttpRequest
                    && serverHttpResponse instanceof ServletServerHttpResponse
                    && (!serverHttpRequest.getURI().toString().contains(Constants.ACTUATOR_HEALTH_PATH))) {
                loggingService.logResponse(((ServletServerHttpRequest) serverHttpRequest).getServletRequest(),
                        ((ServletServerHttpResponse) serverHttpResponse).getServletResponse(), o);
            }
    
            return o;
        }
    

    CustomRequestBodyAdviceAdapter.class.

    import com.pmi.hyperface.constants.Constants;
    import jakarta.servlet.http.HttpServletRequest;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
    
    import java.lang.reflect.Type;
    
    @ControllerAdvice
    public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
    
        @Autowired
        LoggingService loggingService;
    
        @Autowired
        HttpServletRequest httpServletRequest;
    
        @Override
        public boolean supports(MethodParameter methodParameter, Type type,
                Class<? extends HttpMessageConverter<?>> aClass) {
            return true;
        }
    
        @Override
        public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType,
                Class<? extends HttpMessageConverter<?>> converterType) {
            if (!httpServletRequest.getRequestURI().contains(Constants.ACTUATOR_HEALTH_PATH))
                loggingService.logRequest(httpServletRequest, body);
    
            return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
        }
    }
    

    LoggingServiceImpl.class.

    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import lombok.extern.slf4j.Slf4j;
    
    import org.springframework.stereotype.Service;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.util.Collection;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    
    @Service
    @Slf4j
    public class LoggingServiceImpl implements LoggingService {
    
        private static final ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        public void logRequest(HttpServletRequest httpServletRequest, Object body) {
            StringBuilder stringBuilder = new StringBuilder();
            Map<String, String> parameters = buildParametersMap(httpServletRequest);
    
            stringBuilder.append("\nREQUEST");
            stringBuilder.append("\nmethod=[").append(httpServletRequest.getMethod()).append("]");
            stringBuilder.append("\npath=[").append(httpServletRequest.getRequestURI()).append("]");
            stringBuilder.append("\nheaders=[").append(buildHeadersMap(httpServletRequest)).append("]");
    
            if (!parameters.isEmpty()) {
                stringBuilder.append("\nparameters=[").append(parameters).append("] ");
            }
    
            if (body != null) {
                try {
                    stringBuilder.append("\nbody=[" + objectMapper.writeValueAsString(body) + "]");
                } catch (JsonProcessingException e) {
                    log.error("Json processing exception while logging request details");
                }
            }
    
            log.info(stringBuilder.toString());
        }
    
        @Override
        public void logResponse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                Object body) {
            StringBuilder stringBuilder = new StringBuilder();
    
            stringBuilder.append("\nRESPONSE");
            stringBuilder.append("\nmethod=[").append(httpServletRequest.getMethod()).append("]");
            stringBuilder.append("\npath=[").append(httpServletRequest.getRequestURI()).append("]");
            stringBuilder.append("\nresponseHeaders=[").append(buildHeadersMap(httpServletResponse)).append("]");
            try {
                stringBuilder.append("\nresponseBody=[").append(objectMapper.writeValueAsString(body)).append("] ");
            } catch (JsonProcessingException e) {
                log.error("Json processing exception while logging request details");
            }
    
            log.info(stringBuilder.toString());
        }
    
        private Map<String, String> buildParametersMap(HttpServletRequest httpServletRequest) {
            Map<String, String> resultMap = new HashMap<>();
            Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
    
            while (parameterNames.hasMoreElements()) {
                String key = parameterNames.nextElement();
                String value = httpServletRequest.getParameter(key);
                resultMap.put(key, value);
            }
    
            return resultMap;
        }
    
        private Map<String, String> buildHeadersMap(HttpServletRequest request) {
            Map<String, String> map = new HashMap<>();
    
            Enumeration<String> headerNames = request.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String key = headerNames.nextElement();
                String value = request.getHeader(key);
                map.put(key, value);
            }
    
            return map;
        }
    
        private Map<String, String> buildHeadersMap(HttpServletResponse response) {
            Map<String, String> map = new HashMap<>();
    
            Collection<String> headerNames = response.getHeaderNames();
            for (String header : headerNames) {
                map.put(header, response.getHeader(header));
            }
    
            return map;
        }
    
    }