在不改变其他代码的情况下实现日志记录并保存到数据库。
主要记录:请求IP、请求方法、请求URL、出参、入参、时间。
跨域:nginx反向代理,这里需要获取真实IP
效果展示
1、添加依赖
<!-- AOP切面编程-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- JPA 依赖 实现类自动创建数据库表-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
2、配置jpa
jpa:
hibernate:
ddl-auto: update
show-sql: true
3、创建LogEntry类
package com.hao.edusys_demo01.pojo;
import jakarta.persistence.*;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "log_entries")
public class LogEntry {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "module")
private String module;
@Column(name = "method")
private String method;
@Column(name = "url")
private String url;
@Column(name = "ip")
private String ip;
@Column(name = "input_params",columnDefinition = "TEXT")
private String inputParams;
@Column(name = "output_params" ,columnDefinition = "TEXT")
private String outputParams;
@Column(name = "exception")
private String exception;
@Column(name = "time_taken")
private Long timeTaken;
@Column(name = "timestamp")
private LocalDateTime timestamp;
// Getters and setters...
}
4、创建日志存储库接口
package com.hao.edusys_demo01.aop;
import com.hao.edusys_demo01.pojo.LogEntry;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface LogEntryRepository extends JpaRepository<LogEntry, Long> {
}
5、创建日志切面类
package com.hao.edusys_demo01.aop;
import com.hao.edusys_demo01.pojo.LogEntry;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.time.LocalDateTime;
import java.util.Arrays;
@Aspect
@Component
public class LoggingAspect {
private static final int MAX_OUTPUT_LENGTH = 65535; // 根据数据库字段类型调整
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Autowired
private LogEntryRepository logEntryRepository;
@Pointcut("execution(public * com.hao..*.*(..)) && @target(org.springframework.web.bind.annotation.RestController)")
public void controllerPointcut() {
}
@Around("controllerPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
// 获取当前的 HttpServletRequest 对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
// 如果 attributes 为 null,记录日志并返回
logger.warn("无法获取 HttpServletRequest 对象 - 可能不在 Web 请求上下文中");
return joinPoint.proceed();
}
HttpServletRequest request = attributes.getRequest();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String module = methodSignature.getDeclaringTypeName();
String method = request.getMethod();
String url = request.getRequestURL().toString();
String ip = getClientIp(request);
String inputParams = Arrays.toString(joinPoint.getArgs());
Object result = null;
String outputParams = null;
String exceptionMessage = null;
try {
result = joinPoint.proceed();
outputParams = result != null ? result.toString() : null;
outputParams = truncateOutputParams(outputParams); // 截断输出参数
} catch (Throwable throwable) {
exceptionMessage = throwable.getMessage();
throw throwable;
} finally {
long endTime = System.currentTimeMillis();
long elapsedTime = endTime - startTime;
LogEntry logEntry = new LogEntry();
logEntry.setModule(module);
logEntry.setMethod(method);
logEntry.setUrl(url);
logEntry.setIp(ip);
logEntry.setInputParams(inputParams);
logEntry.setOutputParams(outputParams);
logEntry.setException(exceptionMessage);
logEntry.setTimeTaken(elapsedTime);
logEntry.setTimestamp(LocalDateTime.now());
logEntryRepository.save(logEntry);
logger.info("操作模块: {}, 请求方法: {}, 请求URL: {}, 请求IP: {}, 入参: {}, 出参: {}, 异常: {}, 耗时: {} ms",
module, method, url, ip, inputParams, outputParams, exceptionMessage, elapsedTime);
}
return result;
}
//获取真实IP
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
return ip.split(",")[0];
}
ip = request.getHeader("X-Real-IP");
if (ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}
//限制输出参数长度,超出部分截制
private String truncateOutputParams(String outputParams) {
if (outputParams != null && outputParams.length() > MAX_OUTPUT_LENGTH) {
return outputParams.substring(0, MAX_OUTPUT_LENGTH);
}
return outputParams;
}
}