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

网友投稿 1994 2022-11-27

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

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

目录查找资料后确定两种技术方案具体实现总结一下

之前利用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 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 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 requestParamMap = RequestUtils.getRequestParamMap(httpServletRequest);

if (requestParamMap != null && !requestParamMap.isEmpty()){

paramMap.putAll(requestParamMap);

}

//获取header参数

Map headerMap = RequestUtils.getHeaders(httpServletRequest);

if (headerMap != null && !headerMap.isEmpty()){

paramMap.putAll(headerMap);

}

//获取路径参数

Map 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));

}

/**

* 判断请求/返回是否为application/json

* 是则进行拦截,

* 否则退出

* @param contentType 请求/响应类型

*/

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包装器

/**

* HttpServletRequest的包装器,为了在-阶段获取requestBody且不妨碍SpringMVC再次获取requestBody

*/

@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()));

}

/**

* 获取请求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()异常-->",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);

/**

* 获取所有的请求头

* @param request

* @return

*/

public static Map getHeaders(HttpServletRequest request){

Map headerMap = new HashMap<>();

List headers = getCommonHeaders();

headers.add("Postman-Token");

headers.add("Proxy-Connection");

headers.add("X-Lantern-Version");

headers.add("Cookie");

Enumeration headerNames = request.getHeaderNames();

while (headerNames.hasMoreElements()){

String headerName = headerNames.nextElement();

if (headers.contains(headerName)){

continue;

}

headerMap.put(headerName,request.getHeader(headerName));

}

return headerMap;

}

/**

* 获取请求的路径参数

* @param request

* @return

*/

public static Map getUriTemplateVar(HttpServletRequest request) {

NativeWebRequest webRequest = new ServletWebRequest(request);

Map uriTemplateVars = (Map) webRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);

return uriTemplateVars;

}

/**

* 获取请求的真实IP

* @param request

* @return

*/

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();

}

/**

* 从Request中获取所有的请求参数,包括GET/POST/PATCH等请求,不包括路径参数

* @param request

* @return

*/

public static Map getRequestParamMap(HttpServletRequest request) {

Map paramMap = new HashMap<>();

//获取QueryString中的参数,GET方式 或application/x-www-form-urlencoded

Map queryParamMap = RequestUtils.getUriQueryVar(request);

if (queryParamMap != null){

paramMap.putAll(queryParamMap);

}

//获取Body中的参数,POST/PATCH等方式,application/json

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

/**

* 该方法为了避免 fastJson在 反序列化多层json时,改变对象顺序

*/

bodyParamMap = JSONObject.parseObject(requestBody, new TypeReference>(){}, Feature.OrderedField);

}

} catch (Excepthttp://ion e) {

logger.error("获取请求Body异常-->",e);

}

if (bodyParamMap != null){

paramMap.putAll(bodyParamMap);

}

return paramMap;

}

private static List getCommonHeaders(){

List headers = new ArrayList<>();

Class 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包装器

/**

*该包装器主要是重写getOutputStream()和getWriter()方法,给调用者返回自定义的OutputStream和Writer,以便参与输出的过程并记录保存responseBody。

*/

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

/**

*自定义Writer,重写write方法,并记录保存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();

}

//清空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

/**

* 自定义输出流包装器,重写write方法,并记录保存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();

}

//清空buffer,以便下一次重新使用

public void myFlush(){

outputStream = null;

buffer = null;

}

public String getBuffer() {

if (buffer != null){

return buffer.toString();

}

return null;

}

}

总结一下

Request.getInputStream一次请求中只能被调用一次;

Response.getOutputStream()无法获取ResponseBody;

Response的输出有两种方式,都需要考虑到并重写

getOutputStream().write()

getWrite().write()

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【K8S运维知识汇总】第5天6:交付dubbo服务的消费者集群到K8S
下一篇:【K8S运维知识汇总】第5天7:实战dubbo集群的日常维护
相关文章

 发表评论

暂时没有评论,来抢沙发吧~