SpringBoot项目配置拦截器打印访问日志

729 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。 项目中有时需要和外部系统对接,为了便于排查接口传参是否正确,可以新增一个拦截器打印访问日志。

  可以通过继承Filter接口,重写doFilter方法实现。将请求在进入Controller层前拦截下来,并打印出请求的方法、参数以及请求方IP。

package com.tct.ii.ucr.core.filter;
 
import com.tct.ii.ucr.common.BodyReaderHttpServletRequestWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.springframework.util.StringUtils;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
 
/**
 * 默认的access filter
 * 主要用于调试时打印访问日志
 * 可由配置文件的配置控制在filter registry生成注册
 * @author wl
 * @date 2021/7/26
 */
@Slf4j
public class DefaultAccessFilter implements Filter {
    private static final String UNKNOWN = "unknown";
 
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        ServletRequest requestWrapper = null;
        try {
            HttpServletRequest request = (HttpServletRequest) servletRequest;
            String requestMethod = request.getMethod();
            log.info("请求方式:" + requestMethod + " 请求地址: " + request.getRequestURI() + "; 请求来源:" + getRealIp(request));
            if ("POST".equals(requestMethod)) {
                if (ServletFileUpload.isMultipartContent(request)) {
                    log.info("请求包含文件");
                }else {
                    requestWrapper = new BodyReaderHttpServletRequestWrapper(
                            (HttpServletRequest) servletRequest);
                    log.info("请求体: {}", ((BodyReaderHttpServletRequestWrapper) requestWrapper).getBodyStr());
                }
            }else {
                log.info("请求参数: " + getParameterNames(request));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (null == requestWrapper) {
            filterChain.doFilter(servletRequest, servletResponse);
        }else {
            filterChain.doFilter(requestWrapper, servletResponse);
        }
    }
 
    private String getParameterNames(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        StringBuilder stringBuilder = new StringBuilder();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            stringBuilder.append(entry.getKey()).append("=");
            stringBuilder.append(Arrays.toString(entry.getValue())).append(", ");
        }
 
        return stringBuilder.length() > 2 ? stringBuilder.substring(0, stringBuilder.length() - 2) : "";
    }
 
    private String getRealIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (!StringUtils.isEmpty(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if(ip.contains(",")){
                ip = ip.split(",")[0];
            }
        }
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
package com.tct.ii.ucr.config;
 
import com.tct.ii.ucr.core.filter.DefaultAccessFilter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
 
/**
 * @author wl
 * @date 2021/7/28
 */
@Configuration
@Slf4j
public class DefaultFilterRegistry {
 
    @Bean
    @Conditional(LoggingAccessCheck.class)
    public FilterRegistrationBean<DefaultAccessFilter> registryAccessFilter() {
        FilterRegistrationBean<DefaultAccessFilter> filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setFilter(new DefaultAccessFilter());
        filterRegistrationBean.setOrder(1);
 
        return filterRegistrationBean;
    }
 
    private static class LoggingAccessCheck implements Condition {
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Environment environment = context.getEnvironment();
            String loggingAccess = environment.getProperty("logging.debug.log-access");
            return Boolean.parseBoolean(loggingAccess);
        }
    }
 
}
package com.tct.ii.ucr.common;
 
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
 
/**
 * 继承HttpServletRequestWrapper实现对 httpServletRequest 的装饰,用来获取 body 数据
 * @author wl
 * @date 2021/11/26
 */
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] body;
    private String bodyStr;
 
    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        String bodyString = getBodyString(request);
        body = bodyString.getBytes(Charset.forName("UTF-8"));
        bodyStr=bodyString;
    }
 
    public String getBodyStr() {
        return bodyStr;
    }
 
    @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 boolean isFinished() {
                return false;
            }
 
            @Override
            public boolean isReady() {
                return false;
            }
 
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
 
 
    public  String getBodyString(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(
                    new InputStreamReader(inputStream, Charset.forName("UTF-8")));
 
            char[] bodyCharBuffer = new char[1024];
            int len = 0;
            while ((len = reader.read(bodyCharBuffer)) != -1) {
                sb.append(new String(bodyCharBuffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }
}
logging:
  config: classpath:log4j2.xml
  debug:
    log-access: true
    log-slow-query:
      enable: true
      min-cost: 1000