SpringBoot项目打印接口请求日志,CommonsRequestLoggingFilter实现方式
创始人
2024-03-04 09:47:14
0

文章目录

  • 需求背景
  • 效果图
  • 实现思路
    • 其他方案对比
    • 优缺点分析
  • 具体实现

需求背景

  1. 线上项目出现bug时,可以通过接口的请求参数来排查定位问题。
  2. 和业务方battle时,能够回怼他是自己操作的问题。

效果图

在这里插入图片描述

在这里插入图片描述

实现思路

  • Spring提供了CommonsRequestLoggingFilter过滤器,该过滤器可以在接口请求前和请求后分别打印日志;
  • 通过继承CommonsRequestLoggingFilter过滤器,实现部分自己的逻辑;

其他方案对比

  1. aop的方式:必须要到controller层,才会打印, 有些接口在 过滤器、拦截器层被拦截。
  2. 拦截器的方式:request.getInputStream()只能调用一次,解决的方法为copy一份流,个人感觉在代码逻辑层面不太清晰

优缺点分析

缺点

  1. 无法获取返回值,适合一些不关心返回值的场景。
  2. 对于Payload参数,内部使用的是ContentCachingRequestWrapper读取的request.getInputStream()流,所以必须要先调用@RequstBody,才能取到值(可以去看下ContentCachingRequestWrapper的具体实现)

优点

  1. 实现简单,代码层次逻辑清晰,与业务逻辑解耦。

具体实现

继承CommonsRequestLoggingFilter过滤器,实现部分自己的逻辑

package com.yp.basic.log.filter;import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.yp.store.base.biz.common.constant.CommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.web.filter.CommonsRequestLoggingFilter;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;/*** 自定义的请求日志打印过滤器,打印所有接口请求参数、耗时等日志 
** 对比其他两种方式:
*
    *
  • 1、aop的方式:必须要到controller层,才会打印, 有些接口在 过滤器、拦截器层被拦截
  • *
  • 2、拦截器的方式:request.getInputStream()只能调用一次,解决的方法为copy一份流,个人感觉在代码逻辑层面不太清晰
  • *
** 缺点:
*
    *
  • 1、无法获取返回值,适合一些不关心返回值的场景。
  • *
  • 2. 内部使用的是ContentCachingRequestWrapper读取的request.getInputStream(),必须要先调用@RequstBody,才能取到值。
  • *
** 优点:
*
    *
  • 1、 实现简单,代码层次逻辑清晰。
  • *
** @author: wcong* @date: 2022/11/25 15:17*/ @Slf4j public class RequestLogPrintFilter extends CommonsRequestLoggingFilter {/*** 接口日志 开始 请求前缀,建议放在公共的工具类中*/public final static String BEFORE_REQUEST_PREFIX = "### Before request[";/*** 接口 结束 请求前缀,建议放在公共的工具类中*/public final static String AFTER_REQUEST_PREFIX = "### After request[";/*** 重写父类方法:封装打印消息的格式*/@Overrideprotected String createMessage(HttpServletRequest request, String prefix, String suffix) {final StringBuilder messageInfo = getMessageInfo(request, prefix, suffix);// 请求开始还是结束if (BEFORE_REQUEST_PREFIX.equals(prefix)) {// 请求开始MDC.put("logStartTime", String.valueOf(System.currentTimeMillis()));} else {// 请求结束,记录耗时final Long logStartTime = Convert.toLong(MDC.get("logStartTime"), 0L);messageInfo.append("\r\n接口耗时:").append(System.currentTimeMillis() - logStartTime).append("ms");}return messageInfo.toString();}/*** 重写父类方法:请求前调用逻辑*/@Overrideprotected void beforeRequest(HttpServletRequest request, String message) {doPrintLog(message);}/*** 重写父类方法:请求后调用逻辑*/@Overrideprotected void afterRequest(HttpServletRequest request, String message) {doPrintLog(message);}/*** 重写父类方法:是否打印日志*/@Overrideprotected boolean shouldLog(HttpServletRequest request) {// 父类中的逻辑是:logger.isDebugEnabled()return true;}/*** 统一封装打印的日志格式** @param request javax.servlet.http.HttpServletRequest* @param prefix 打印前缀* @param suffix 打印后缀* @return 封装好的日志格式*/private StringBuilder getMessageInfo(HttpServletRequest request, String prefix, String suffix) {StringBuilder msg = new StringBuilder();msg.append(prefix);msg.append(StrUtil.format("method={}; ", request.getMethod().toLowerCase()));msg.append("uri=").append(request.getRequestURI());if (isIncludeClientInfo()) {String client = request.getRemoteAddr();if (StrUtil.isNotBlank(client)) {msg.append("; client=").append(client);}HttpSession session = request.getSession(false);if (session != null) {msg.append("; session=").append(session.getId());}String user = request.getRemoteUser();if (user != null) {msg.append("; user=").append(user);}}if (isIncludeQueryString()) {String queryString = request.getQueryString();if (queryString != null) {msg.append("; ").append('?').append(queryString);}}if (isIncludePayload()) {String payload = getMessagePayload(request);if (payload != null) {msg.append("; payload=").append(payload);}}msg.append(suffix);return msg;}/*** 具体打印的方法** @param message 打印的消息*/private void doPrintLog(String message) {// 生产环境打印debug级别if (CommonConstant.IS_PRODUCT_ENVIRONMENT) {log.debug(message);} else {log.info(message);}}}

注册过滤器

package com.yp.basic.log;import com.yp.basic.jackson.JsonUtil;
import com.yp.basic.log.aspect.SysLogAspect;
import com.yp.basic.log.event.SysLogListener;
import com.yp.basic.log.filter.RequestLogPrintFilter;
import com.yp.basic.log.monitor.PointUtil;
import com.yp.basic.log.properties.OptLogProperties;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.filter.CommonsRequestLoggingFilter;/*** 日志自动配置** @author: wcong* @date: 2022/11/25 15:17*/
@EnableAsync
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties(OptLogProperties.class)
public class LogAutoConfiguration {@Bean@ConditionalOnProperty(prefix = OptLogProperties.PREFIX, name = "enable-http-log", havingValue = "true", matchIfMissing = true)public FilterRegistrationBean logFilterRegistration() {CommonsRequestLoggingFilter filter = new RequestLogPrintFilter();// 是否打印header中的内容,参数很多filter.setIncludeHeaders(false);// 是否打印查询字符串内容filter.setIncludeQueryString(true);// 是否打印 payLoad内容,内部使用的是ContentCachingRequestWrapper读取的request.getInputStream(),必须要先调用@RequstBody,才能取到值。filter.setIncludePayload(true);// 是否打印客户端信息(ip、session、remoteUser)filter.setIncludeClientInfo(true);// 1024字节(1kb),超出部分截取// 在UTF-8编码方案中,一个英文字符占用一个字节,一个汉字字符占用三个字节的空间。filter.setMaxPayloadLength(1024);// 设置 before request 日志前缀,默认为:Before request [filter.setBeforeMessagePrefix(RequestLogPrintFilter.BEFORE_REQUEST_PREFIX);// 设置 before request 日志后缀,默认为:]filter.setBeforeMessageSuffix("]");// 设置 before request 日志前缀,默认为:After request [filter.setAfterMessagePrefix(RequestLogPrintFilter.AFTER_REQUEST_PREFIX);// 设置 after request 日志后缀,默认为:]filter.setAfterMessageSuffix("]");FilterRegistrationBean registration = new FilterRegistrationBean<>(filter);registration.addUrlPatterns("/*");registration.setOrder(0);registration.setName("commonsRequestLoggingFilter");return registration;}}

相关内容

热门资讯

银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
ASM贪吃蛇游戏-解决错误的问... 要解决ASM贪吃蛇游戏中的错误问题,你可以按照以下步骤进行:首先,确定错误的具体表现和问题所在。在贪...