当前位置: 首页 > news >正文

web 应用层接口请求日志

需求:
前文已经讲过如何使用MDC在日志中为每个请求生成一个唯一traceID,日志生成traceID。
请求作为入口,一般的系统都会有一个 或者 文件 记录每个请求,方便运维统计接口调用情况,实现方案大体两种:

  • 使用 Spring AOP
  • 使用 Filter
  • 使用 Interceptor

感兴趣的,代码可以通过我的Demo工程获取。

一、Filter

1.1 CommonsRequestLoggingFilter

Spring Boot 自带了一个现成的 CommonsRequestLoggingFilter,它可以记录请求的详细信息并支持非常灵活的配置,省去了手动管理 Filter 的复杂性。
但是个人不建议在生产环境使用!

  1. 配置Filter
@Configuration
public class RequestLoggingConfig {@Beanpublic CommonsRequestLoggingFilter requestLoggingFilter() {CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();loggingFilter.setIncludeClientInfo(true);loggingFilter.setIncludeQueryString(true);loggingFilter.setIncludePayload(true);loggingFilter.setMaxPayloadLength(1000);  // 设置要记录的最大请求体长度loggingFilter.setIncludeHeaders(true);  // 可选:是否记录请求头return loggingFilter;}
}
  1. 配置日志打印

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><logger name="org.springframework.web.filter.CommonsRequestLoggingFilter"><level value="DEBUG" /></logger>
</configuration>    

要注意如果没有重写CommonsRequestLoggingFilter的方法,日志级别必须是 DEBUG

1.1.1 INFO级别 日志不打印问题

通过 CommonsRequestLoggingFilter 源码可以知晓,shouldLog 默认是使用 DEBUG的,错误原因就很简单了。
根据类的设计可以知晓,CommonsRequestLoggingFilter 设计是为了开发人员在开发阶段、排查错误阶段打印接口日志,所以对于统计接口信息来讲就不太合适,所以个人不推荐使用。
CommonsRequestLoggingFilter 继承 AbstractRequestLoggingFilter 再往上继承 OncePerRequestFilter 再往上继承 GenericFilterBeanGenericFilterBean 实现了Filter。所以本质上都是 Filter 方案,只不过可以选择使用现有的类去满足自己的需求而已。

CommonsRequestLoggingFilter.java

public class CommonsRequestLoggingFilter extends AbstractRequestLoggingFilter {@Overrideprotected boolean shouldLog(HttpServletRequest request) {return logger.isDebugEnabled();}/*** Writes a log message before the request is processed.*/@Overrideprotected void beforeRequest(HttpServletRequest request, String message) {logger.debug(message);}/*** Writes a log message after the request is processed.*/@Overrideprotected void afterRequest(HttpServletRequest request, String message) {logger.debug(message);}}

修改后的代码:

@Configuration
public class WebConfig {@Beanpublic CommonsRequestLoggingFilter logFilter() {CommonsRequestLoggingFilter filter= new CommonsRequestLoggingFilter() {@Overrideprotected boolean shouldLog(HttpServletRequest request) {return true;}@Overrideprotected void beforeRequest(HttpServletRequest request, String message) {logger.info(message);}@Overrideprotected void afterRequest(HttpServletRequest request, String message) {logger.info(message);}};filter.setIncludeQueryString(true);filter.setIncludePayload(true);filter.setMaxPayloadLength(10000);filter.setIncludeHeaders(false);filter.setAfterMessagePrefix("REQUEST DATA: ");return filter;}}

1.2 OncePerRequestFilter

OncePerRequestFilter 是 Spring 提供的一个抽象类,它可以确保一个请求只会经过一次过滤。

  1. 日志类继承实现
@Slf4j
@Component
public class CustomRequestLoggingFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {long startTime = System.currentTimeMillis();// 忽略文件上传请求if (isFileUpload(request)) {filterChain.doFilter(request, response);return;}try {// 继续处理请求filterChain.doFilter(request, response);} finally {// 请求结束后记录日志long duration = System.currentTimeMillis() - startTime;logRequest(request, duration);}}private void logRequest(HttpServletRequest request, long duration) {StringBuilder logMessage = new StringBuilder();logMessage.append("Method=").append(request.getMethod()).append("; ");logMessage.append("URI=").append(request.getRequestURI()).append("; ");logMessage.append("Query=").append(request.getQueryString()).append("; ");logMessage.append("RemoteIP=").append(request.getRemoteAddr()).append("; ");logMessage.append("Duration=").append(duration).append("ms;");log.info(logMessage.toString());}private boolean isFileUpload(HttpServletRequest request) {return "POST".equalsIgnoreCase(request.getMethod()) && request.getContentType() != null&& request.getContentType().startsWith("multipart/form-data");}
}
  1. 配置日志打印

logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration><logger name="com.github.nan.web.core.filter.CustomRequestLoggingFilter" level="INFO" additivity="false"><!-- 一般来讲是写到一个单独的文件,这里只是一个参考 --><appender-ref ref="STDOUT"/></logger>
</configuration>    

二、AOP

AOP(面向切面编程) 也是一个非常灵活且强大的方式来记录请求数据。AOP 可以在不修改现有代码的情况下,横切关注点(如日志记录、事务管理等),并且能够更加精细地控制在哪些方法或控制器上进行日志记录。

  1. 请求日志切面
@Slf4j
public class RequestLoggingAspect {@Before("execution(* com.github.nan.web.demos.web..*(..))")public void logBefore(JoinPoint joinPoint) {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();// 忽略文件上传请求if ("POST".equalsIgnoreCase(request.getMethod()) && request.getContentType() != null&& request.getContentType().startsWith("multipart/form-data")) {return;}log.info("Request received: [Method: {}] [URI: {}] [Query: {}] [Remote IP: {}] [Arguments: {}]",request.getMethod(),request.getRequestURI(),request.getQueryString(),request.getRemoteAddr(),Arrays.toString(joinPoint.getArgs()));}@Around("execution(* com.github.nan.web.demos.web..*(..))")public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {long startTime = System.currentTimeMillis();Object result = joinPoint.proceed();long duration = System.currentTimeMillis() - startTime;log.info("Request completed: [Duration: {} ms] [Return value: {}]",duration,result != null ? result.toString() : "null");return result;}}

AOP 的优点

  • 灵活性:你可以根据具体的类或方法定义切点,选择性地记录某些控制器的请求。
  • 高可定制性:可以记录更加详细的日志内容,例如请求参数、执行耗时、返回值等。
  • 避免重复代码:AOP 可以在不同的控制器中实现统一的日志记录逻辑,不需要在每个控制器中写相同的代码。

三、Interceptor

Spring 提供了 HandlerInterceptor 接口,用于在处理 HTTP 请求之前、处理之后以及完成请求时执行一些操作。拦截器通常用于日志记录、权限检查等场景。

  1. 拦截器代码
@Slf4j
@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String payload = "";if (request instanceof ContentCachingRequestWrapper) {ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request;byte[] content = wrapper.getContentAsByteArray();try {payload = new String(content, wrapper.getCharacterEncoding());} catch (UnsupportedEncodingException e) {payload = "[unknown encoding]";}}log.info("Request received: [Method: {}] [URI: {}] [Query: {}] [Remote IP: {}] [Payload : {}]",request.getMethod(),request.getRequestURI(),request.getQueryString(),request.getRemoteAddr(),payload);return true;  // 返回 true 继续处理请求,false 则终止请求}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {log.info("Request completed: [Status: {}]", response.getStatus());}
}
  1. 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate RequestLoggingInterceptor requestLoggingInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(requestLoggingInterceptor).addPathPatterns("/**");}
}

四、常见问题

4.1 Post 请求 payLoad 丢失

HttpServletRequest 的请求体只能被消费一次,之后再尝试读取时就会发现请求体已经被“耗尽”。

解决方案:使用 ContentCachingRequestWrapper

你可以将原始的 HttpServletRequest 包装为 ContentCachingRequestWrapper,这样就可以在拦截器中读取请求体,而不会影响后续控制器的处理。 可以参考 Interceptor 的代码。

五、总结

上述,FilterAOPInterceptor 的代码方案只是阐述了实现的方式,一些实现的细节,需要根据自己的需求去补充,例如上传/下载文件要如何记录? 存在敏感信息的接口如何处理?


http://www.mrgr.cn/news/58333.html

相关文章:

  • 深度学习系列——RNN/LSTM/GRU,seq2seq/attention机制
  • 黑科技!Llama 3.2多模态AI震撼发布
  • 深入浅出理解BLE AUDIO CSIS
  • 【动手学电机驱动】 TI InstaSPIN-FOC(8)Lab07 在线测量定子电阻
  • 传奇架设GEE引擎数据库服务器提示:拒绝未授权ip连接服务器的解决办法
  • ESP32移植Openharmony外设篇(3)OLED屏
  • 面试题:JVM(一)
  • DLL修复工具 v4.2.0.40217 免安装一键修复
  • linux下gpio模拟spi三线时序
  • opencv - py_imgproc - py_geometric_transformations 几何变换
  • 设计师的新宠:7款不容错过的界面设计软件
  • Telegram机器人的手机部署
  • 股市动向背后有哪些因素?我们该如何应对?
  • 网络服务请求流程简单理解
  • uniapp renderjs页面传值
  • springboot大学校园报修管理平台-计算机毕业设计源码90736
  • 华为开放式耳机测评,南卡 、华为、Cleer开放式耳机超深度横评
  • FreeSWITCH JSON API
  • 国产linux系统(银河麒麟,统信uos)使用 PageOffice 在线打开Word文件最简单集成代码
  • 美国颁布史上最严数据安全规定:企业该如何应对网络安全挑战?
  • 企业通过FSC认证后如何保持合规
  • 从安全事故谈信息透明化的重要性
  • 入侵检测算法平台部署LiteAIServer视频智能分析平台行人入侵检测算法
  • 海外盲盒系统搭建:海外市场带来的全新机遇
  • Axure PR 9 多级下拉清除选择器 设计交互
  • Linux中的文件的常用命令