Spring/SpringBoot 请求/响应日志打印

Spring/SpringBoot 请求/响应日志打印

前言

一个请求日志拦截的Demo 使用Filer方式,不进行Tomact 骚操作处理

依赖

为了少些几行代码 使用了 Hutool 工具包

废话少说直接上代码

1、拦截器

import cn.hutool.extra.servlet.JakartaServletUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingResponseWrapper;

import java.io.IOException;

/**
 * <p>
 * http 请求-响应日志打印
 * </p>
 */
@Slf4j
@Component
public class RequestLogFilter extends OncePerRequestFilter {
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //请求接收时间毫秒数,用来计算请求处理时间
        long receiveTime = System.currentTimeMillis();

        //使用包装类将request body 进行缓存,解决request.getInputStream()不可重复调用问题
        RequestContentCachingWrapper requestWrapper = new RequestContentCachingWrapper(request);

        //response 包装类 缓存并获取最终响应数据报文信息
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        try {
            //打印请求日志
            logRequest(requestWrapper);

            //请求处理
            filterChain.doFilter(requestWrapper,responseWrapper);

            //响应日志处理
            logResponse(request,responseWrapper,receiveTime);
        } finally {
            //最终需要执行此方法,将响应数据写入response中
            responseWrapper.copyBodyToResponse();
        }
    }

    /**
     * <p>
     * 请求报文获取
     * </p>
     *
     * @param request  http 请求数据
     */
    private void logRequest(RequestContentCachingWrapper request){

        String requestParam = null;
        try {
            if (request.isJsonRequest()){
                String body = JakartaServletUtil.getBody(request);
                if (StringUtils.isNotBlank(body)){
                    requestParam =  body;
                }
            }else {
                requestParam = new ObjectMapper().writeValueAsString(request.getParameterMap());
            }
            log.info("HttpRequest [{}::{}] receive:{}",request.getMethod(),request.getRequestURI(), requestParam);

        } catch (Exception e) {
            log.error("获取请求参数异常!",e);
        }
    }

    /**
     * <p>
     * 响应报文获取
     * </p>
     *
     * @param request 请求参数
     * @param responseWrapper http 请求数据
     */
    private String logResponse(HttpServletRequest request, ContentCachingResponseWrapper responseWrapper, long receiveTime){
        try {
            log.info("HttpResponse [{}::{}ms] response:{}",request.getRequestURI(), System.currentTimeMillis() - receiveTime,new String(responseWrapper.getContentAsByteArray()));
        } catch (Exception e) {
            log.error("响应数据获取失败",e);
        }
        return null;
    }
}

2、 请求包装类

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.util.ContentCachingRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * <p>
 * 自定义 ContentCachingRequestWrapper
 * </p>
 */
public class RequestContentCachingWrapper extends ContentCachingRequestWrapper {

    /**
     * 是否第一次获取
     */
    private AtomicBoolean isFirst = new AtomicBoolean(true);

    /**
     * 参数构造
     * @param request 原请求数据
     */
    public RequestContentCachingWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        if(isFirst.get()){
            //首次读取直接调父类的方法,这一次执行完之后 缓存流中有数据了
            //后续读取就读缓存流里的。
            isFirst.set(false);
            return super.getInputStream();
        }

        return new CustomizeServletInputStream(super.getContentAsByteArray());
    }

    /**
     * 判断是都时json请求
     * @return 判断结果
     */
    protected boolean isJsonRequest() {
        String contentType = getContentType();
        return (contentType != null && contentType.contains("application/json"));
    }

    class CustomizeServletInputStream extends ServletInputStream{

        /**
         * sourceStream
         */
        private InputStream sourceStream;

        /**
         * 是否已完成
         */
        private boolean finished = false;

        /**
         * 参数构造
         */
        public CustomizeServletInputStream(byte [] bytes) {
            this.sourceStream = new ByteArrayInputStream(bytes);
        }


        @Override
        public int read() throws IOException {
            int data = this.sourceStream.read();
            if (data == -1) {
                this.finished = true;
            }
            return data;
        }

        @Override
        public int available() throws IOException {
            return this.sourceStream.available();
        }

        @Override
        public void close() throws IOException {
            super.close();
            this.sourceStream.close();
        }

        @Override
        public boolean isFinished() {
            return this.finished;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener listener) {

        }
    }
}

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值