Realization of Request Log Printing Mode Using Filter in SpringMVC Framework

  • 2021-12-05 06:07:53
  • OfStack

After searching the data in the catalogue, the specific implementation summary of the two technical schemes is determined under 1

requestBody retrieval of a request was implemented using HttpServletRequest. getInputStream () and RequestWrapper before, and now it is proposed to present both RequestBody and ResponseBody of a request and print the log & Fall into the database for statistics and finding problems.

Two technical schemes are determined after searching the data

1. Use AOP to surround all Controller methods;

2. Use Filter to intercept all Request and Response and get body.

Finally, the second way is selected, and the specific implementation records are as follows.

Concrete realization

Logging filter


public class RequestFilter implements Filter{
private static final String LOG_FORMATTER_IN = " Request path: {%s} Request method: {%s} Parameters: {%s} Source IP : {%s} Request start time {%s} , return to: {%s} Request end time {%s} , time :{%s}ms , type of operation: {%s} Operator: {%s}";
public static final String USER_TOKEN_REDIS_PREFIX = "token_prefix";
private static final Logger log = LoggerFactory.getLogger(RequestFilter.class);
//request Intercepted conten-type List 
private List<String> contentTypes;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    // Request path 
    String path = httpServletRequest.getRequestURI();
    String method = httpServletRequest.getMethod();
    // Of all request parameters Map
    Map<String,String> paramMap = new HashMap<>();
    // Truth of request IP
    String requestedIP = RequestUtils.getRealIP(httpServletRequest);
    // Whether to intercept and wrap the request, and if you need to intercept it, get the RequestBody , 1 Be general application/json To intercept 
    boolean filterRequestFlag = checkFilter(request.getContentType());
    if (filterRequestFlag) {
        httpServletRequest = new MyRequestBodyReaderWrapper(httpServletRequest);
    }
    // Get all queryString And requestBody
    Map<String, String> requestParamMap = RequestUtils.getRequestParamMap(httpServletRequest);
    if (requestParamMap != null && !requestParamMap.isEmpty()){
        paramMap.putAll(requestParamMap);
    }
    // Get header Parameter 
    Map<String, String> headerMap = RequestUtils.getHeaders(httpServletRequest);
    if (headerMap != null && !headerMap.isEmpty()){
       paramMap.putAll(headerMap);
    }
    // Get path parameters 
    Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest);
    if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){
        paramMap.putAll(uriTemplateMap);
    }
    // Packaging Response , rewrite getOutputStream() And getWriter() Method and use the custom OutputStream And Writer To intercept and save ResponseBody
    MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse);
    // Request start time 
    Long dateStart = System.currentTimeMillis();
    //Spring Pass DispatchServlet Processing requests 
    chain.doFilter(httpServletRequest, responseWrapper);
    // Request end time 
    Long dateEnd = System.currentTimeMillis();
    String responseBody;
    if (responseWrapper.getMyOutputStream() == null){
            if (responseWrapper.getMyWriter() != null){
                responseBody = responseWrapper.getMyWriter().getContent();
                //1 Be sure flush , responseBody Will be reused 
                responseWrapper.getMyWriter().myFlush();
            }
        }else {
            responseBody = responseWrapper.getMyOutputStream().getBuffer();
            //1 Be sure flush , responseBody Will be reused 
            responseWrapper.getMyOutputStream().myFlush();
    }
    String params = JSONObject.toJSONString(paramMap);
    log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart));
}
/**
 *  Judgment request / Returns whether application/json
 *  If yes, intercept it, 
 *  Otherwise quit 
 * @param contentType  Request / Response type 
 */
private boolean checkFilter(String contentType) {
    boolean filterFlag = false;// Do you want to continue interception 
    for (String p : getContentTypes()) {
        if (StringUtils.contains(contentType, p)){
            filterFlag = true;
        }
    }
    if (StringUtils.isEmpty(contentType)){
        filterFlag = true;
    }
    return filterFlag;
}
}

Request wrapper


/**
* HttpServletRequest In order to get the wrapper of the interceptor stage requestBody Without hindering SpringMVC Retrieve again requestBody
*/
@Slf4j
public class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper {
// Storage JSON Data body 
private final byte[] body;
public MyRequestBodyReaderWrapper(HttpServletRequest request) throws IOException {
    super(request);
    body = getBody(request).getBytes(Charset.forName("UTF-8"));
}
@Override
public ServletInputStream getInputStream() throws IOException {
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body);
    return new ServletInputStream() {
        @Override
        public int read() throws IOException {
            return byteArrayInputStream.read();
        }
    };
}
@Override
public BufferedReader getReader() throws IOException {
    return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
/**
 *  Acquisition request Body
 */
public static String getBody(ServletRequest request) {
    StringBuilder sb = new StringBuilder();
    InputStream inputStream = null;
    BufferedReader reader = null;
    try {
        inputStream = request.getInputStream();
        reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
        String line;
        while ((line = reader.readLine()) != null) {
            sb.append(line);
        }
    } catch (IOException e) {
        log.error("MyRequestBodyReaderWrapper.getBody() Anomaly -->",e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error("MyRequestBodyReaderWrapper.getBody() Anomaly -->",e);
            }
        }
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                log.error("MyRequestBodyReaderWrapper.getBody() Anomaly -->",e);
            }
        }
    }
    return sb.toString();
}
}

RequestUtils


/**
*  Request tool class 
*/
public class RequestUtils {
private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);
/**
 *  Get all request headers 
 * @param request
 * @return
 */
public static Map<String,String> getHeaders(HttpServletRequest request){
    Map<String,String> headerMap = new HashMap<>();
    List<String> headers = getCommonHeaders();
    headers.add("Postman-Token");
    headers.add("Proxy-Connection");
    headers.add("X-Lantern-Version");
    headers.add("Cookie");
    Enumeration<String> headerNames = request.getHeaderNames();
    while (headerNames.hasMoreElements()){
        String headerName = headerNames.nextElement();
        if (headers.contains(headerName)){
            continue;
        }
        headerMap.put(headerName,request.getHeader(headerName));
    }
    return headerMap;
}
/**
 *  Get the path parameters of the request 
 * @param request
 * @return
 */
public static Map<String, String> getUriTemplateVar(HttpServletRequest request) {
    NativeWebRequest webRequest = new ServletWebRequest(request);
    Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    return uriTemplateVars;
}
/**
 *  Get the true of the request IP
 * @param request
 * @return
 */
public static String getRealIP(HttpServletRequest request) {
    String ip = request.getHeader("X-Forwarded-For");
    if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
        // After multiple reverse proxies, there will be multiple ip Value, number 1 A ip Is the truth ip
        int index = ip.indexOf(",");
        if (index != -1) {
            return ip.substring(0, index);
        } else {
            return ip;
        }
    }
    ip = request.getHeader("X-Real-IP");
    if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
        return ip;
    }
    return request.getRemoteAddr();
}
/**
 *  From Request Get all the request parameters from, including GET/POST/PATCH And so on, excluding path parameters 
 * @param request
 * @return
 */
public static Map<String,String> getRequestParamMap(HttpServletRequest request) {
    Map<String,String> paramMap = new HashMap<>();
    // Get QueryString Parameters in the, GET Mode   Or application/x-www-form-urlencoded
    Map<String, String> queryParamMap = RequestUtils.getUriQueryVar(request);
    if (queryParamMap != null){
        paramMap.putAll(queryParamMap);
    }
    // Get Body Parameters in the, POST/PATCH Etc ,application/json
    Map<String,String> bodyParamMap = null;
    try {
        // When is POST Request and  application/json When, request Be RequestFilter Process as wrapper Class 
        if (!(request instanceof MyRequestBodyReaderWrapper)){
            return paramMap;
        }
        MyRequestBodyReaderWrapper readerWrapper = (MyRequestBodyReaderWrapper) request;
        String requestBody = new String(readerWrapper.getBody(), "UTF-8");
        if (com.zhongan.health.common.utils.StringUtils.isNotBlank(requestBody)){
            /**
             *  This method is to avoid  fastJson In   Deserialization multilayer json Change the order of objects when 
             */
            bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference<LinkedHashMap<String,String>>(){}, Feature.OrderedField);
        }
    } catch (Exception e) {
        logger.error(" Acquisition request Body Anomaly -->",e);
    }
    if (bodyParamMap != null){
        paramMap.putAll(bodyParamMap);
    }
    return paramMap;
}
private static List<String> getCommonHeaders(){
    List<String> headers = new ArrayList<>();
    Class<HttpHeaders> clazz = HttpHeaders.class;
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
        field.setAccessible(true);
        if (field.getType().toString().endsWith("java.lang.String") && Modifier.isStatic(field.getModifiers())){
            try {
                headers.add((String) field.get(HttpHeaders.class));
            } catch (IllegalAccessException e) {
                logger.error(" Reflection gets attribute value exception -->",e);
            }
        }
    }
    return headers;
}
}

Response wrapper


/**
* This wrapper mainly overrides the getOutputStream() And getWriter() Method, returning a custom OutputStream And Writer In order to participate in the output process and record saving responseBody . 
*/
public class MyResponseWrapper extends HttpServletResponseWrapper {
private ResponsePrintWriter writer;
private MyServletOutputStream out;
public MyResponseWrapper(HttpServletResponse response) {
    super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
    //1 Be sure to judge the current out You can only create a new one if it is empty out Object, otherwise 1 Multiple requests will appear out Object 
    if (out == null){
        out = new MyServletOutputStream(super.getOutputStream());
    }
    return out;
}
@Override
public PrintWriter getWriter() throws IOException {
    //1 Be sure to judge the current writer You can only create a new one if it is empty writer Object, otherwise 1 Multiple requests will appear writer Object 
    if (writer == null){
        writer = new ResponsePrintWriter(super.getWriter());
    }
    return writer;
}
public ResponsePrintWriter getMyWriter() {
    return writer;
}
public MyServletOutputStream getMyOutputStream(){
    return out;
}
}

Custom Writer


/**
* Customize Writer , rewrite write Method, and record saving ResponseBody
*/
public class ResponsePrintWriter extends PrintWriter{
private StringBuffer buffer;
public ResponsePrintWriter(PrintWriter out) {
    super(out);
    buffer = new StringBuffer();
}
public String getContent(){
    return buffer == null ? null : buffer.toString();
}
@Override
public void flush() {
    super.flush();
}
// Empty buffer In order to get under 1 Secondary reuse 
public void myFlush(){
    buffer = null;
}
@Override
public void write(char[] buf, int off, int len) {
    super.write(buf, off, len);
    char[] destination = new char[len];
    System.arraycopy(buf,off,destination,0,len);
    buffer.append(destination);
}
@Override
public void write(String s) {
    super.write(s);
    buffer.append(s);
}
}

Custom OutputStream


/**
*  Custom output stream wrapper, override write Method, and record saving ResponseBody
*/
public class MyServletOutputStream extends ServletOutputStream {
private ServletOutputStream outputStream;
private StringBuffer buffer;
public MyServletOutputStream(ServletOutputStream outputStream) {
    this.outputStream = outputStream;
    buffer = new StringBuffer();
}
@Override
public void write(int b) throws IOException {
    outputStream.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
    outputStream.write(b, off, len);
    byte[] bytes = new byte[len];
    System.arraycopy(b, off, bytes, 0, len);
    buffer.append(new String(bytes,"UTF-8"));
}
@Override
public void write(byte[] b) throws IOException {
    outputStream.write(b);
}
@Override
public void flush() throws IOException {
    super.flush();
}
// Empty buffer In order to get under 1 Secondary reuse 
public void myFlush(){
    outputStream = null;
    buffer = null;
}
public String getBuffer() {
    if (buffer != null){
        return buffer.toString();
    }
    return null;
}
}

Summary 1

Request.getInputStream It can only be called once in a request; Response.getOutputStream() Unable to get ResponseBody; Response There are two ways to output, both of which need to be considered and rewritten

getOutputStream().write()

getWrite().write()


Related articles: