文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringMVC框架中使用Filter实现请求日志打印方式

2024-04-02 19:55

关注

之前利用HttpServletRequest.getInputStream()和RequestWrapper实现了请求的requestBody获取,现在提出将一个请求的RequestBody和ResponseBody都提出来并打印日志&落入数据库,以便统计和查找问题。

查找资料后确定两种技术方案

1. 使用AOP对所有Controller的方法进行环绕通知处理;

2. 使用Filter拦截所有的Request和Response,并获取body。

最后选择了第二种方式,具体实现记录如下。

具体实现

日志记录过滤器


public class RequestFilter implements Filter{
private static final String LOG_FORMATTER_IN = "请求路径:{%s},请求方法:{%s},参数:{%s},来源IP:{%s},请求开始时间{%s},返回:{%s},请求结束时间{%s},用时:{%s}ms,操作类型:{%s},操作人:{%s}";
public static final String USER_TOKEN_REDIS_PREFIX = "token_prefix";
private static final Logger log = LoggerFactory.getLogger(RequestFilter.class);
//request拦截的conten-type列表
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;
    //请求路径
    String path = httpServletRequest.getRequestURI();
    String method = httpServletRequest.getMethod();
    //所有请求参数的Map
    Map<String,String> paramMap = new HashMap<>();
    //请求的真实IP
    String requestedIP = RequestUtils.getRealIP(httpServletRequest);
    //是否拦截并包装请求,如果需要拦截则会获取RequestBody,一般为application/json才拦截
    boolean filterRequestFlag = checkFilter(request.getContentType());
    if (filterRequestFlag) {
        httpServletRequest = new MyRequestBodyReaderWrapper(httpServletRequest);
    }
    //获取所有queryString和requestBody
    Map<String, String> requestParamMap = RequestUtils.getRequestParamMap(httpServletRequest);
    if (requestParamMap != null && !requestParamMap.isEmpty()){
        paramMap.putAll(requestParamMap);
    }
    //获取header参数
    Map<String, String> headerMap = RequestUtils.getHeaders(httpServletRequest);
    if (headerMap != null && !headerMap.isEmpty()){
       paramMap.putAll(headerMap);
    }
    //获取路径参数
    Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(httpServletRequest);
    if (uriTemplateMap != null && !uriTemplateMap.isEmpty()){
        paramMap.putAll(uriTemplateMap);
    }
    //包装Response,重写getOutputStream()和getWriter()方法,并用自定义的OutputStream和Writer来拦截和保存ResponseBody
    MyResponseWrapper responseWrapper = new MyResponseWrapper(httpServletResponse);
    //请求开始时间
    Long dateStart = System.currentTimeMillis();
    //Spring通过DispatchServlet处理请求
    chain.doFilter(httpServletRequest, responseWrapper);
    //请求结束时间
    Long dateEnd = System.currentTimeMillis();
    String responseBody;
    if (responseWrapper.getMyOutputStream() == null){
            if (responseWrapper.getMyWriter() != null){
                responseBody = responseWrapper.getMyWriter().getContent();
                //一定要flush,responseBody会被复用
                responseWrapper.getMyWriter().myFlush();
            }
        }else {
            responseBody = responseWrapper.getMyOutputStream().getBuffer();
            //一定要flush,responseBody会被复用
            responseWrapper.getMyOutputStream().myFlush();
    }
    String params = JSONObject.toJSONString(paramMap);
    log.info(String.format(LOG_FORMATTER_IN, path, method, params, requestedIP, dateStart, responseBody, dateEnd,(dateEnd - dateStart));
}

private boolean checkFilter(String contentType) {
    boolean filterFlag = false;//是否继续拦截
    for (String p : getContentTypes()) {
        if (StringUtils.contains(contentType, p)){
            filterFlag = true;
        }
    }
    if (StringUtils.isEmpty(contentType)){
        filterFlag = true;
    }
    return filterFlag;
}
}

Request包装器



@Slf4j
public class MyRequestBodyReaderWrapper extends HttpServletRequestWrapper {
//存放JSON数据主体
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()));
}

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()异常-->",e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error("MyRequestBodyReaderWrapper.getBody()异常-->",e);
            }
        }
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                log.error("MyRequestBodyReaderWrapper.getBody()异常-->",e);
            }
        }
    }
    return sb.toString();
}
}

RequestUtils



public class RequestUtils {
private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);

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;
}

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;
}

public static String getRealIP(HttpServletRequest request) {
    String ip = request.getHeader("X-Forwarded-For");
    if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
        //多次反向代理后会有多个ip值,第一个ip才是真实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();
}

public static Map<String,String> getRequestParamMap(HttpServletRequest request) {
    Map<String,String> paramMap = new HashMap<>();
    //获取QueryString中的参数,GET方式 或application/x-www-form-urlencoded
    Map<String, String> queryParamMap = RequestUtils.getUriQueryVar(request);
    if (queryParamMap != null){
        paramMap.putAll(queryParamMap);
    }
    //获取Body中的参数,POST/PATCH等方式,application/json
    Map<String,String> bodyParamMap = null;
    try {
        //当为POST请求且 application/json时,request被RequestFilter处理为wrapper类
        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)){
            
            bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference<LinkedHashMap<String,String>>(){}, Feature.OrderedField);
        }
    } catch (Exception e) {
        logger.error("获取请求Body异常-->",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("反射获取属性值异常-->",e);
            }
        }
    }
    return headers;
}
}

Response包装器



public class MyResponseWrapper extends HttpServletResponseWrapper {
private ResponsePrintWriter writer;
private MyServletOutputStream out;
public MyResponseWrapper(HttpServletResponse response) {
    super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
    //一定要先判断当前out为空才能去新建out对象,否则一次请求会出现多个out对象
    if (out == null){
        out = new MyServletOutputStream(super.getOutputStream());
    }
    return out;
}
@Override
public PrintWriter getWriter() throws IOException {
    //一定要先判断当前writer为空才能去新建writer对象,否则一次请求会出现多个writer对象
    if (writer == null){
        writer = new ResponsePrintWriter(super.getWriter());
    }
    return writer;
}
public ResponsePrintWriter getMyWriter() {
    return writer;
}
public MyServletOutputStream getMyOutputStream(){
    return out;
}
}

自定义Writer



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();
}
//清空buffer,以便下一次重新使用
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);
}
}

自定义OutputStream



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();
}
//清空buffer,以便下一次重新使用
public void myFlush(){
    outputStream = null;
    buffer = null;
}
public String getBuffer() {
    if (buffer != null){
        return buffer.toString();
    }
    return null;
}
}

总结一下

getOutputStream().write()

getWrite().write()

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯