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) {
}
}
}