Show log of request and response in Spring Boot
I. Introduction
For you as a backend, writing API is no longer strange. But when it comes to problems, log checking is mandatory and indispensable. With the API, viewing log requests and responses is also a powerful way to help develop investigate the cause of bugs. So in this article, I will guide you to the config project so that the show is the most visible and easy to do log.
II. Installation and demo example
You initialize the spring boot + project and implement some simple API heads as a visual example
1. Configure Spring Boot Application
This is the most basic way to display log requests and responses
You add the following classes to conduct a demo:
Project structure:
RequestLoggingFilterConfig Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.example.demologging.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.CommonsRequestLoggingFilter; @Configuration public class RequestLoggingFilterConfig { @Bean public CommonsRequestLoggingFilter logFilter() { CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); filter.setIncludeQueryString(true); filter.setIncludePayload(true); filter.setMaxPayloadLength(10000); filter.setIncludeHeaders(false); filter.setAfterMessagePrefix("REQUEST DATA : "); return filter; } } |
Class UserController:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.example.demologging.controller; import com.example.demologging.model.UserDto; import com.example.demologging.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @GetMapping public List<UserDto> getListUser(){ return userService.getListUser(); } } |
UserDto Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 | package com.example.demologging.model; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor public class UserDto { private String name; private String address; private int age; } |
Class UserService:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.example.demologging.service; import com.example.demologging.model.UserDto; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class UserService { public List<UserDto> getListUser(){ List<UserDto> userDtoList = new ArrayList<>(); UserDto userDto1 = new UserDto("Nguyen Van A","Ha Noi",18); UserDto userDto2 = new UserDto("Nguyen Van B","Ha Noi",19); UserDto userDto3 = new UserDto("Nguyen Van C","Ha Noi",20); UserDto userDto4 = new UserDto("Nguyen Van D","Ha Noi",18); UserDto userDto5 = new UserDto("Nguyen Van E","Ha Noi",19); userDtoList.add(userDto1); userDtoList.add(userDto2); userDtoList.add(userDto3); userDtoList.add(userDto4); userDtoList.add(userDto5); return userDtoList; } } |
DemoLoggingApplication Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.example.demologging; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoLoggingApplication { public static void main(String[] args) { SpringApplication.run(DemoLoggingApplication.class, args); } } |
File application.properties:
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
Logback.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="LOGS" value="./logs"/> <property name="SERVICE" value="ssk-service"/> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <layout class="ch.qos.logback.classic.PatternLayout"> <Pattern> %black(%d{ISO8601}) %highlight(%-5level) [%blue(%t)] %yellow(%C{2}:%L): %msg%n%throwable </Pattern> </layout> </appender> <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOGS}/${SERVICE}-logger.log</file> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <Pattern>%d %p %C{2}:%L [%t] %m%n</Pattern> </encoder> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- rollover daily and when the file reaches 1 GigaBytes --> <fileNamePattern>${LOGS}/archived/${SERVICE}-logger-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>1GB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> </appender> <root level="DEBUG"> <appender-ref ref="rollingFile"/> <appender-ref ref="console"/> </root> <logger name="org.springframework.web.filter.CommonsRequestLoggingFilter"> <level value="DEBUG" /> </logger> </configuration> |
You use postman to check the API and show log:
Observing the console log in the IDE will see that the log is displayed with all requests and responses:
But you notice the requests and responses have not been “beautiful” and very detailed. So I will go to part 2 to create a full log and more favorable offline.
2. Customize the Log show with Controller Advice
You add the following class:
*** Class CustomURLFilter: ***
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package com.example.demologging.config; import lombok.extern.slf4j.Slf4j; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Enumeration; import java.util.UUID; @Slf4j public class CustomURLFilter implements Filter { private static final String REQUEST_ID = "request_id"; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String requestId = UUID.randomUUID().toString(); servletRequest.setAttribute(REQUEST_ID, requestId); logRequest((HttpServletRequest) servletRequest, requestId); filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } private void logRequest(HttpServletRequest request, String requestId) { if (request != null) { StringBuilder data = new StringBuilder(); data.append("nLOGGING REQUEST-----------------------------------n") .append("[REQUEST-ID]: ").append(requestId).append("n") .append("[PATH]: ").append(request.getRequestURI()).append("n") .append("[QUERIES]: ").append(request.getQueryString()).append("n") .append("[HEADERS]: ").append("n"); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); data.append("---").append(key).append(" : ").append(value).append("n"); } data.append("LOGGING REQUEST-----------------------------------n"); log.info(data.toString()); } } } |
RequestLoggingFilterConfig Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.example.demologging.config; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.filter.CommonsRequestLoggingFilter; @Configuration public class RequestLoggingFilterConfig { @Bean public CommonsRequestLoggingFilter logFilter() { CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); filter.setIncludeQueryString(true); filter.setIncludePayload(true); filter.setMaxPayloadLength(10000); filter.setIncludeHeaders(false); filter.setAfterMessagePrefix("REQUEST DATA : "); return filter; } @Bean public FilterRegistrationBean < CustomURLFilter > filterRegistrationBean() { FilterRegistrationBean < CustomURLFilter > registrationBean = new FilterRegistrationBean(); CustomURLFilter customURLFilter = new CustomURLFilter(); registrationBean.setFilter(customURLFilter); registrationBean.setOrder(2); //set precedence return registrationBean; } } |
Class CustomRequestBodyAdviceAdapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package com.example.demologging.controller; import com.example.demologging.service.LoggingService; 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 javax.servlet.http.HttpServletRequest; 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) { loggingService.logRequest(httpServletRequest, body); return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType); } } |
Class CustomResponseBodyAdviceAdapter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com.example.demologging.controller; import com.example.demologging.service.LoggingService; 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 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) { loggingService.logResponse( ((ServletServerHttpRequest) serverHttpRequest).getServletRequest(), ((ServletServerHttpResponse) serverHttpResponse).getServletResponse(), o); } return o; } } |
LoggingService Class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | package com.example.demologging.service; import com.example.demologging.utils.GsonParserUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Service @Slf4j public class LoggingService { private static final String REQUEST_ID = "request_id"; public void logRequest(HttpServletRequest httpServletRequest, Object body) { if (httpServletRequest.getRequestURI().contains("medias")) { return; } Object requestId = httpServletRequest.getAttribute(REQUEST_ID); StringBuilder data = new StringBuilder(); data.append("nLOGGING REQUEST BODY-----------------------------------n") .append("[REQUEST-ID]: ").append(requestId).append("n") .append("[BODY REQUEST]: ").append("nn") .append(GsonParserUtils.parseObjectToString(body)) .append("nn") .append("LOGGING REQUEST BODY-----------------------------------n"); log.info(data.toString()); } public void logResponse(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object body) { if (httpServletRequest.getRequestURI().contains("medias")) { return; } Object requestId = httpServletRequest.getAttribute(REQUEST_ID); StringBuilder data = new StringBuilder(); data.append("nLOGGING RESPONSE-----------------------------------n") .append("[REQUEST-ID]: ").append(requestId).append("n") .append("[BODY RESPONSE]: ").append("nn") .append(GsonParserUtils.parseObjectToString(body)) .append("nn") .append("LOGGING RESPONSE-----------------------------------n"); log.info(data.toString()); } } |
Class GsonParserUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.example.demologging.utils; import com.google.gson.Gson; public class GsonParserUtils { public static String parseObjectToString(Object object) { return new Gson().toJson(object); } public static <T> T parseStringToObject(String json, Class<T> classObject) { try { return new Gson().fromJson(json, classObject); } catch (Exception e) { return null; } } } |
Results after you run the test here:
The results display log extremely easy to see and convenient for you to investigate logs