Springboot AOP实现日志功能

在不改变其他代码的情况下实现日志记录并保存到数据库。

主要记录:请求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;
    }

}

6、测试运行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值