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) {request = wrapRequest(request);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());}private HttpServletRequest wrapRequest(HttpServletRequest request) {// 仅在 POST 请求的 JSON 请求体时进行包装,确保缓存请求体if (HttpMethod.POST.name().equalsIgnoreCase(request.getMethod()) && MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())) {return new ContentCachingRequestWrapper(request);}return request;}}
  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.mzph.cn/diannao/57886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在浏览器里就可以运行的本地AI模型 - 一键去除图片背景AI

前言 浏览器的功能越来越强大, 从Chrome 113 开始, 谷歌把WebGPU引入到了浏览器中, 通过WebGPU的API, 可以直接访问本机电脑的GPU资源. 既然GPU资源可以在浏览器里运行, 给AI模型推理等带来了便利, 使得一些AI模型可以直接在浏览器里运行. 本文主要介绍介绍以下WebGPU的基本概…

【前端开发入门】JavaScript快速入门--js变量

目录 引言一、为什么要定义变量二、定义变量的一些技巧1. 解构赋值1.1 Object解构赋值1.2 Array解构赋值1.3 总结规律 2. 字符串拼接 三、变量作用域四、总结 引言 本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容&#xf…

uniapp 发起post和get请求!uni.request(OBJECT)

在uni-app中&#xff0c;发起HTTP请求主要通过uni.request方法实现。 Get请求 使用uni.request请求api&#xff0c;并且将 method参数设置为GET&#xff0c;有参数的话直接data&#xff1a;{}传递&#xff0c; success是请求成功回调函数&#xff0c;fail是失败函数 <but…

ipv6地址子网划分

IPv6 从左至右一共有8段地址,每一段用16进制表示&#xff0c;共128位。 例如&#xff1a;2001:0DB8:0001:0000:0000:0000:0000:0000 每一段的子网掩码如下&#xff1a; 第1段的掩码为是 0~16 01616 第2段的掩码为是 17~32 161632 第3段的掩码为是 33~48 …

DBeaver + Oracle 数据库修改CLOB类型字段内容

数据库中存在大量错误数据&#xff0c; CLOB类型字段值需要批量修改&#xff0c;因数据结构比较复杂&#xff0c;无法做到使用常规的update语句。。。。 一、问题介绍 oracle数据库中&#xff0c;clob类型字段&#xff0c; 数据格式为 {“type”:“OOC”, …}, 如何使用一个sql…

QQ音乐绿钻音效+DTS音效解锁

​ 工具 mt管理器 simplehook QQ音乐&#xff08;自行下载&#xff09; DTS音效修改方法&#xff1a;com.tencent.qqmusic.business.user.a.r1 赋值为1 绿钻音效修改方法&#xff1a; com.tencent.qqmusic.business.user.a.q1 赋值为1 建议使用hook实现&#xff0c;这里贴上si…

设计模式——过滤器模式

一、定义和概念 定义 C 过滤器模式&#xff08;Filter Pattern&#xff09;也称为标准模式&#xff08;Criteria Pattern&#xff09;&#xff0c;是一种设计模式&#xff0c;用于根据不同的标准或条件从一组对象中筛选出符合条件的对象。它将筛选条件的逻辑封装在不同的过滤器…

动态IP是什么?

随着互联网成为人们生活的重要组成部分&#xff0c;以信息传递为主导的时代种&#xff0c;网络连接质量对我们的工作效率、学习进度以及娱乐体验等方面都有很大影响。 动态IP&#xff0c;作为网络连接中的一种重要IP代理形式&#xff0c;越来越受到用户的欢迎。本文将深入解析…

关于 Linux 内核“合规要求”与俄罗斯制裁的一些澄清

原文&#xff1a;Michael Larabel - 2024.10.24 当 一些俄罗斯的 Linux 开发者被从内核的 MAINTAINERS 文件中移除 时&#xff0c;原因被描述为“合规要求”&#xff0c;但并未明确这些要求具体涉及什么内容。随后&#xff0c;Linus Torvalds 对此发表了评论&#xff0c;明确指…

计算机网络(十二) —— 高级IO

#1024程序员节 | 征文# 目录 一&#xff0c;预备 1.1 重新理解IO 1.2 五种IO模型 1.3 非阻塞IO 二&#xff0c;select 2.1 关于select 2.2 select接口参数解释 2.3 timeval结构体和fd_set类型 2.4 socket就绪条件 2.5 select基本工作流程 2.6 简单select的服务器代…

【Linux】信号量,线程池

目录 信号量 初始化​编辑 销毁 等待 发布 基于环形队列的生产消费模型 问题解答&#xff1a; 代码&#xff1a; 线程池 线程池的实现 &#xff08;1&#xff09;初始化&#xff0c;构造大致框架 &#xff08;2&#xff09;创建线程 &#xff08;3&#xff09;创建任…

Unity 世界空间(World Space)UI被模型遮挡的解决办法(Overlay摄像机)

问题&#xff1a; 想要显示掉落的物品名&#xff0c;但是这个世界空间的UI层会被模型遮挡&#xff0c;如下&#xff1a; 解决&#xff1a; 1.新建一个专门的物品名图层&#xff0c;如ItemUI 2.在主摄像机下新建一个子摄像机ItemCamera&#xff0c;渲染类型设置为Overlay&#…

Unity加载界面制作

效果 UI部分 结构 说下思路: 因为是加载界面,所以最上层是一个Panel阻止所有的UI交互,这个Panel如果有图片就加一个图片,如果没有可以把透明度调到最大,颜色设为黑色. 下面最核心的就是一个进度条了,有图片的话,将进度条的底放进来,将进度条锚点设为下中,将滑动块的尺寸设为0.…

迈威通信西安采矿展大放异彩,驱动煤矿智能转型加速跑

金秋十月&#xff0c;一场矿业技术的盛宴如约而至。10月23日至25日&#xff0c;中国(西安)国际采矿技术交流及设备展览会在西安临空会展中心圆满落下帷幕。迈威通信&#xff0c;作为矿业通信与自动化解决方案的卓越提供商&#xff0c;此次以 “布局多元融合网络&#xff0c;赋能…

SwiftUI 中 List 或 Form 子视图关联的 swipeAction 导致展开动画异常的解决

问题现象 小伙伴们都知道,在 SwiftUI 中更快捷的增强 List 或 Form 子视图(Cell)交互功能的方法是使用 swipeAction 修改器。不过,对其使用稍有不慎也会横生枝节。 如上图所示,不适当的设置 Cell 视图布局会使 swipeAction 无法生成正确的收缩和展开动画。对此我们有什么…

python爬虫百度图片

直接给代码&#xff0c;可直接用&#xff0c;个人需要修改的地方有两处&#xff1a; self.directory 这是本地存储地址&#xff0c;修改为自己电脑的地址&#xff0c;另外&#xff0c;**{}**不要删spider.json_count 10 这是下载的图像组数&#xff0c;一组有30张图像&#x…

微信小程序文字转语音播报案例

插件申请 在小程序官方申请同声传译插件&#xff0c;地址&#xff1a; mp.weixin.qq.com 引入插件 在app.json中加入 "plugins": {"WechatSI": {"version": "0.3.6","provider": "wx069ba97219f66d99"}},封装…

机器学习2

一、模型评估方法 1.1 K折交叉验证法&#xff08;K-Fold Cross Validation&#xff09; 1.1.1 定义 K折交叉验证法是一种用于评估模型性能的技术。它将数据集分为K个相等的子集&#xff0c;模型会轮流使用一个子集作为测试集&#xff0c;其余K-1个子集作为训练集。这个过程会…

Springboot 导出Excel文件方式对比与注意事项

Springboot 导出Excel文件方式对比与注意事项 Excel导出系列目录&#xff1a;为什么不导出.xls后缀的文件&#xff1f;EasyExcel导出xls后缀文件POI导出xls后缀文件 POI导出与EasyExcel导出相比哪种方式最优呢&#xff1f;POI导出与EasyExcel导出两种方式有没有需要注意的坑呢&…

南京林业大学生态学博士在1区top期刊揭示人工林发育促进土壤团聚体的形成与稳定:对土壤碳氮固存的启示

本文首发于“生态学者”微信公众号&#xff01; 文章信息 第一作者&#xff1a;石珂 通讯作者&#xff1a;阮宏华教授 通讯单位&#xff1a;南京林业大学 原文链接&#xff1a;https://doi.org/10.1016/j.catena.2024.108363 亮点 •土壤团聚体的稳定性随着林分发育而增…