springboot配置请求日志

一般情况下,接口请求都需要日志记录,Java springboot中的日志记录相对复杂一点

经过实践,以下方案可行,记录一下完整过程

一、创建日志数据模型

创建实体类,也就是日志文件中要记录的数据格式

我是放在我的实体类包中,也就算pojo包下

package org.example.pojo;import lombok.Data;@Data
public class RequestLog {private String url; // 请求URLprivate String method; // HTTP方法private long startTime; // 开始时间private long endTime; // 结束时间private String remoteIp; // 客户端IPprivate String queryString; // 查询参数private String requestBody; // 请求体(根据需求谨慎记录)private Integer status; // 响应状态码private Long costTime; // 耗时(ms)@Overridepublic String toString(){return "{" +"\"url\":\"" + url + '\"' +", \"method\":\"" + method + '\"' +", \"remoteIp\":\"" + remoteIp + '\"' +", \"status\":" + status +", \"costTime\":" + costTime +", \"queryString\":\"" + (queryString != null ? queryString : "") + '\"' +", \"requestBody\":\"" + (requestBody != null ? requestBody : "") + '\"' +'}';}
}

这里有个小问题,requestBody似乎取不到,比较麻烦,但不影响记录

二、创建拦截器

其实就是每次请求前,读取请求信息,并添加到请求实体中去

我放在interceptor包下

package org.example.interceptor;import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.pojo.RequestLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;@Component
public class RequestLoggingInterceptor implements HandlerInterceptor {private static final Logger logger = LoggerFactory.getLogger("REQUEST_LOG");private static final String ATTRIBUTE_REQUEST_LOG = "requestLog";@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {RequestLog requestLog = new RequestLog();requestLog.setStartTime(System.currentTimeMillis());requestLog.setUrl(request.getRequestURI());requestLog.setMethod(request.getMethod());// request请求中的原始iprequestLog.setRemoteIp(request.getRemoteHost());// 自定义的获取ip方法,考虑了使用代理的情况
//        requestLog.setRemoteIp(getClientIp(request));requestLog.setQueryString(request.getQueryString());
//        下面这两行会报错
//        requestLog.setRequestBody(request.getReader().toString());
//        requestLog.setRequestBody(request.getRequestBody());request.setAttribute(ATTRIBUTE_REQUEST_LOG, requestLog);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response,  Object handler, Exception ex) throws Exception {RequestLog requestLog = (RequestLog) request.getAttribute(ATTRIBUTE_REQUEST_LOG);if (requestLog != null) {requestLog.setEndTime(System.currentTimeMillis());requestLog.setCostTime(System.currentTimeMillis() - requestLog.getStartTime());requestLog.setStatus(response.getStatus());logger.info(requestLog.toString());}}/*** 获取客户端真实IP(考虑了代理情况)*/private String getClientIp(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}

几个基础问题记录下:

  • Component注解,将这个方法注入到springboot项目中,使其成为项目组件
  • preHandle和afterCompletion都是HandlerInterceptor中原有的方法,重写方法时,必须保证参数完全一致,同时需要抛出异常
  • slf4j在maven中并没有显示引入,应该是springboot自带的

三、注册拦截器

需要写一个配置类,我放在config包中

package org.example.config;import org.example.interceptor.RequestLoggingInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate RequestLoggingInterceptor requestLoggingInterceptor;@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOrigins("http://localhost:5173", "http://localhost:5174").allowedMethods("*").allowedHeaders("*").allowCredentials(true).maxAge(3600);}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(requestLoggingInterceptor).addPathPatterns("/**");  // 拦截所有请求路径,都需要添加日志}
}

几点说明:

  • addCorsMappings用来处理跨域,重写父类方法
  • 拦截器在addInterceptors,也是重写的

四、配置 Logback 以将日志保存到文件

src/main/resources目录下创建或修改 logback-spring.xml文件,为请求日志配置一个独立的追加器 (Appender),并将其输出到文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration><!-- 定义日志文件存储路径和文件名 --><property name="LOG_PATH" value="./logs" /><property name="REQUEST_LOG_FILE" value="${LOG_PATH}/request.log" /><!-- 控制台输出配置(可选) --><appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern></encoder></appender><!-- 为请求日志配置独立的滚动文件Appender --><appender name="REQUEST_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"><file>${REQUEST_LOG_FILE}</file> <!-- 当前活动的日志文件 --><encoder><!-- 配置输出格式,因为我们在Interceptor中已输出JSON字符串,这里只需消息本身 --><pattern>%msg%n</pattern> </encoder><!-- 滚动策略:按日期和大小滚动 --><rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"><!-- 滚动后的文件命名模式:按天归档,超过大小则递增,并自动压缩 --><fileNamePattern>${REQUEST_LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern><maxFileSize>50MB</maxFileSize> <!-- 每个文件最大大小 --><maxHistory>30</maxHistory> <!-- 保留30天的历史日志 --><totalSizeCap>5GB</totalSizeCap> <!-- 所有请求日志文件总大小上限 --></rollingPolicy></appender><!-- 专门为我们的请求日志logger配置,指向独立的文件Appender,并不再向上传递(additivity=false) --><logger name="REQUEST_LOG" level="INFO" additivity="false"><appender-ref ref="REQUEST_FILE" /><!-- 如果需要同时在控制台看到,可以加上 <appender-ref ref="CONSOLE"/> --></logger><!-- 根日志记录器(其他日志的输出配置) --><root level="INFO"><appender-ref ref="CONSOLE" /></root>
</configuration>

五、日志文件

{"url":"/api/layout/positions", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":200, "costTime":310, "queryString":""}
{"url":"/api/user/login", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":200, "costTime":19, "queryString":""}
{"url":"/api/user/login", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":200, "costTime":10, "queryString":"", "requestBody":"{"}
{"url":"/error", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":500, "costTime":27, "queryString":"", "requestBody":"    "username": "huangg","}
{"url":"/api/user/login", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":200, "costTime":283, "queryString":"", "requestBody":""}
{"url":"/api/user/login", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":200, "costTime":10, "queryString":"", "requestBody":"org.apache.catalina.connector.CoyoteReader@6906fda1"}
{"url":"/error", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":500, "costTime":27, "queryString":"", "requestBody":"org.apache.catalina.connector.CoyoteReader@6906fda1"}
{"url":"/api/user/login", "method":"POST", "remoteIp":"0:0:0:0:0:0:0:1", "status":200, "costTime":301, "queryString":"", "requestBody":""}
{"url":"/api/user/login", "method":"POST", "remoteIp":"127.0.0.1", "status":200, "costTime":3, "queryString":"", "requestBody":""}
{"url":"/api/user/login", "method":"POST", "remoteIp":"192.168.0.94", "status":200, "costTime":4, "queryString":"", "requestBody":""}
  • 如果本机上发起请求,请求域名用的是localhost,则remoteIp是ipv6地址,0:0:0:0:0:0:0:1
  • 如果本机上发起请求,请求域名用的是127.0.0.1,则remoteIp是127.0.0.1
  • 如果使用真实ip地址192.168.0.94,则remoteIp是真实IP192.168.0.94

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/96062.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/96062.shtml
英文地址,请注明出处:http://en.pswp.cn/web/96062.shtml

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

相关文章

Redis(50) Redis哨兵如何与客户端进行交互?

Redis 哨兵&#xff08;Sentinel&#xff09;不仅负责监控和管理 Redis 主从复制集群的高可用性&#xff0c;还需要与客户端进行有效的交互来实现故障转移后的透明连接切换。下面详细探讨 Redis 哨兵如何与客户端进行交互&#xff0c;并结合代码示例加以说明。 哨兵与客户端的交…

【.Net技术栈梳理】04-核心框架与运行时(线程处理)

文章目录1. 线程管理1.1 线程的核心概念&#xff1a;System.Threading.Thread1.2 现代线程管理&#xff1a;System.Threading.Tasks.Task 和 Task Parallel Library (TPL)1.3 状态管理和异常处理1.4 协调任务&#xff1a;async/await 模式2. 线程间通信2.1 共享内存与竞态条件2…

(JVM)四种垃圾回收算法

在 JVM 中&#xff0c;垃圾回收&#xff08;GC&#xff09;是核心机制之一。为了提升性能与内存利用率&#xff0c;JVM 采用了多种垃圾回收算法。本文总结了 四种常见的 GC 算法&#xff0c;并结合其优缺点与应用场景进行说明。1. 标记-清除&#xff08;Mark-Sweep&#xff09;…

论文阅读:VGGT Visual Geometry Grounded Transformer

论文阅读&#xff1a;VGGT: Visual Geometry Grounded Transformer 今天介绍一篇 CVPR 2025 的 best paper&#xff0c;这篇文章是牛津大学的 VGG 团队的工作&#xff0c;主要围绕着 3D 视觉中的各种任务&#xff0c;这篇文章提出了一种多任务统一的架构&#xff0c;实现一次输…

python编程:一文掌握pypiserver的详细使用

更多内容请见: python3案例和总结-专栏介绍和目录 文章目录 一、 pypiserver 概述 1.1 pypiserver是什么? 1.2 核心特性 1.3 典型应用场景 1.4 pypiserver优缺点 二、 安装与基本使用 2.1 安装 pypiserver 2.2 快速启动(最简模式) 2.3 使用私有服务器安装包 2.4 向私有服务…

Git reset 回退版本

- 第 121 篇 - Date: 2025 - 09 - 06 Author: 郑龙浩&#xff08;仟墨&#xff09; 文章目录Git reset 回退版本1 介绍三种命令区别3 验证三种的区别3 如果不小心git reset --hard将「工作区」和「暂存区」中的内容删除&#xff0c;刚才的记录找不到了&#xff0c;怎么办呢&…

ARM 基础(2)

ARM内核工作模式及其切换条件用户模式(User Mode, usr) 权限最低&#xff0c;运行普通应用程序。只能通过异常被动切换到其他模式。快速中断模式(FIQ Mode, fiq) 处理高速外设中断&#xff0c;专用寄存器减少上下文保存时间&#xff0c;响应周期约4个时钟周期。触发条件为FIQ中…

Flutter 性能优化

Flutter 性能优化是一个系统性的工程&#xff0c;涉及多个层面。 一、性能分析工具&#xff08;Profiling Tools&#xff09; 在开始优化前&#xff0c;必须使用工具定位瓶颈。切忌盲目优化。 1. DevTools 性能视图 DevTools 性能视图 (Performance View) 作用&#xff1a;…

Spring事件监听机制(三)

为了理解EvenListener注解的底层原理&#xff0c;我们可以自己实现一个类似的注解模拟实现。1.定义MyListener注解Target({ElementType.METHOD})Retention(RetentionPolicy.RUNTIME)public interface MyListener {}2.注解使用Componentstatic class SmsService {private static…

基于Springboot + vue3实现的小区物业管理系统

项目描述本系统包含管理员和用户两个角色。管理员角色&#xff1a;用户管理&#xff1a;管理系统中所有用户的信息&#xff0c;包括添加、删除和修改用户。房屋信息管理&#xff1a;管理房屋信息&#xff0c;包括新增、查看、修改和删除房屋信息。车辆信息管理&#xff1a;管理…

交叉熵和KL散度

这个问题之前我也是傻傻分不清&#xff0c;决定整理一下&#xff0c;用更印象深刻的方式让人记住。核心联系&#xff1a;交叉熵 KL 散度 真实分布的熵 交叉熵作为 “绝对” 度量&#xff0c;会综合真实分布的熵&#xff08;固有难度&#xff09;与预测误差&#xff0c;直接体…

HTML 各种事件的使用说明书

HTML 各种事件的使用说明书 1. HTML 事件简介 HTML事件是浏览器或用户在网页上执行的动作或发生的事情。当这些事件发生时&#xff0c;可以通过JavaScript来响应和处理这些事件&#xff0c;从而实现网页的交互功能。事件处理是Web前端开发中实现动态交互的核心机制。 基本概…

Kafka面试精讲 Day 10:事务机制与幂等性保证

【Kafka面试精讲 Day 10】事务机制与幂等性保证 在分布式消息系统中&#xff0c;如何确保消息不丢失、不重复&#xff0c;是系统可靠性的核心挑战。Kafka自0.11版本起引入了幂等性Producer和事务性消息机制&#xff0c;彻底解决了“至少一次”语义下可能产生的重复消息问题&am…

时序数据库简介和安装

一、简介1. 什么是时序数据库&#xff1f;时序数据库是专门用于存储和处理时间序列数据的数据库系统。时间序列数据是指按时间顺序索引的一系列数据点。每个数据点都包含&#xff1a;一个时间戳&#xff1a;记录数据产生的时间。一个或多个指标值&#xff1a;例如温度、湿度、C…

comfyUI 暴露网络restful http接口

https://zhuanlan.zhihu.com/p/686893291 暴露websocket接口。 打开开发者选项 如图

linux系统address already in use问题解决

linux系统上某个端口被占用&#xff0c;如何解决&#xff1f;1.找到占用的进程编号&#xff1a;netstat -tulnp | grep :80002.强制杀死该进程kill -9 80603其他说明&#xff1a;1.查找占用端口的进程&#xff0c;可以用&#xff1a;lsof -i :8001 # 或者使用 netstat -tulnp |…

基于SpringBoot的家政保洁预约系统【计算机毕业设计选题 计算机毕业设计项目 计算机毕业论文题目推荐】

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【Linux系统】 4. 权限(一)

一. shell 命令及运行原理基本理解1&#xff09;广义理解的操作系统包括&#xff1a;操作系统内核、外壳程序&#xff08;shell命令行、图形化界面&#xff09;、必要的软件。2&#xff09;狭义的操作系统&#xff1a;操作系统内核。3&#xff09;在用户和内核之间有一个外壳程…

6.python——字符串

python中用’ 和" "创建字符串 python的子字符串截取用[]取字符串拼接可以直接用相加。 python三引号允许一个字符串跨多行&#xff0c;其中无需进行转义&#xff08;所见即所得&#xff09;。 当你需要一块HTML或者SQL时&#xff0c;这时用字符串组合&#xff0c;特…

足球数据API接口的技术特性与应用价值分析

一、接口概述现代足球数据接口是基于RESTful架构的数据服务&#xff0c;通过标准化方式提供赛事相关信息。这类接口通常采用JSON格式传输数据&#xff0c;支持跨平台调用&#xff0c;为开发者提供结构化的足球赛事数据。二、数据覆盖范围主流足球数据接口通常包含以下数据类型&…