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

使用 Spring AOP 实现日志记录

利用 Spring AOP 实现日志记录

功能概述

  • 切面定义:

    • 使用 @Aspect 注解标记该类为一个切面。
    • 使用 @Component 注解将该类注册为一个 Spring Bean。
    • 使用 @Order(0) 注解指定该切面的执行顺序,值越小优先级越高。
  • 切点定义:

    • 使用 @Pointcut 注解定义一个切点 controllerLog(),匹配 com.szx.exam.controller 包及其子包下的所有控制器方法。
      前置日志记录:
    • logBefore 方法负责在控制器方法执行前记录请求的相关信息。
    • 获取当前请求的 HttpServletRequest 对象。
    • 记录请求的 URL、HTTP 方法、客户端 IP、客户端名称、控制器类名、方法名以及方法参数。
    • 使用 lombok 注解引入日志记录器 log,并在 finally 块中输出日志信息。
  • 环绕通知:

    • 使用 @Around 注解定义一个环绕通知 around,在控制器方法执行前后进行操作。
    • 记录方法开始执行的时间。
    • 调用 logBefore 方法记录前置日志。
    • 执行控制器方法,并记录返回结果。
    • 记录方法执行时间。
    • 清除 TraceId。

引入依赖

<!--aop-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.0</version>
</dependency>

因为接口中有json输出,所以需要额外引入一个fastjson

代码编写

新建 ControllerLogAspect 类 ,编写如下代码

import com.alibaba.fastjson.JSON;
import com.szx.exam.util.RequestKit;
import com.szx.exam.util.TraceIdKit;
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.LinkedHashMap;
import java.util.Map;

@Aspect
@Order(0)
@Component
@Slf4j
public class ControllerLogAspect {

    // 拦截com.szx.exam下面的所有controller
    @Pointcut("within(com.szx.exam.controller..*)")
    public void controllerLog() {
    }


    private void logBefore(ProceedingJoinPoint pjd) {
        Map<String, String> logMap = new LinkedHashMap<>();

        try {
            Object[] args = pjd.getArgs();

            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                throw new Exception("requestAttributes is null");
            }

            HttpServletRequest httpServletRequest = requestAttributes.getRequest();
            TraceIdKit.getTraceId(httpServletRequest);

            logMap.put("URL", httpServletRequest.getRequestURI());
            logMap.put("HTTP_METHOD", httpServletRequest.getMethod());
            logMap.put("IP", RequestKit.getIp(httpServletRequest));
            logMap.put("ClientName",RequestKit.getClientName(httpServletRequest));

            String className = pjd.getTarget().getClass().getName();
            logMap.put("CLASS", className);

            String methodName = pjd.getSignature().getName();
            logMap.put("METHOD", methodName);

            StringBuilder sb = new StringBuilder();
            for (Object object : args) {
                try {
                    if (sb.length() > 0) {
                        sb.append(", ");
                    }
                    sb.append(JSON.toJSONString(object));
                } catch (Exception ignored) {
                    sb.append("null, ");
                }
            }

            logMap.put("ARGS", sb.toString());
        } catch (Exception e) {
            log.error("logBefore", e);
        } finally {
            for (Map.Entry<String, String> entry : logMap.entrySet()) {
                log.info("{}: {}", entry.getKey(), entry.getValue());
            }
        }
    }

    @Around("controllerLog()")
    public Object around(ProceedingJoinPoint pjd) throws Throwable {
        long start = System.currentTimeMillis();

        logBefore(pjd);

        try {
            Object result = pjd.proceed();
            log.info("RESP: {}", JSON.toJSONString(result));

            return result;
        } finally {
            log.info("COST: {} ms", System.currentTimeMillis() - start);
            TraceIdKit.clearTraceId();
        }
    }

}

额外使用了两个工具类

RequestKit

import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;

/**
 * 请求转化
 *
 */
@SuppressWarnings("unused")
public class RequestKit {
    /**
     * 获取真实 IP 地址
     *
     * @param request 通用请求
     * @return IP 地址
     */
    public static String getIp(HttpServletRequest request) {
        try {
            String ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
            }
            if (ipAddress != null && ipAddress.length() > 15) {
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
            return ipAddress;
        } catch (Exception e) {
            throw new IllegalArgumentException("get ip exception: " + e.getMessage());
        }
    }

    /**
     * 获取客户端信息
     *
     * @param request 通用请求
     * @return 客户端信息
     */
    public static String getClientName(HttpServletRequest request) {
        String clientName = "--";
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.isEmpty(userAgent)) {
            return clientName;
        }
        if (userAgent.toLowerCase().contains("windows")) {
            clientName = "windows";
        }
        if (userAgent.toLowerCase().contains("mac")) {
            clientName = "mac";
        }
        if (userAgent.toLowerCase().contains("x11")) {
            clientName = "unix";
        }
        if (userAgent.toLowerCase().contains("android")) {
            clientName = "android";
        }
        if (userAgent.toLowerCase().contains("iphone")) {
            clientName = "iphone";
        }
        return clientName;
    }

    public static String encode(String value) {
        try {
            return URLEncoder.encode(value, "UTF-8");
        } catch (Exception e) {
            return value;
        }
    }
}

TraceIdKit

import org.apache.commons.lang3.StringUtils;
import org.slf4j.MDC;

import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

@SuppressWarnings("unused")
public class TraceIdKit {

    private static final String TRACE_ID = "traceId";


    public static String getTraceId() {
        String traceId = MDC.get(TRACE_ID);
        if (StringUtils.isBlank(traceId)) {
            traceId = generateTraceId();
            MDC.put(TRACE_ID, traceId);
        }

        return traceId;
    }

    public static String getTraceIdHeader() {
        return TRACE_ID;
    }

    public static void getTraceId(HttpServletRequest httpServletRequest) {
        String traceId = httpServletRequest.getHeader(TRACE_ID);
        if (StringUtils.isBlank(traceId)) {
            traceId = getTraceId();
        }

        MDC.put(TRACE_ID, traceId);
    }

    public static void clearTraceId() {
        MDC.clear();
    }

    private static String generateTraceId() {
        UUID uuid = UUID.randomUUID();
        return uuid.toString().replace("-", "");
    }

}

编写完成后,重启项目,当我们访问一个接口时,就会有对应的日志输出了

测试

编写一个测试接口

@RestController
@RequestMapping("/exam")
public class ExamController {

    @GetMapping("getAllList")
    public Result getAllList(String name) {
        return Result.ok("success");
    }


    @PostMapping("add")
    public Result add(@RequestBody HashMap<String,Object> map) {
        return Result.ok(map);
    }
}

测试访问,查看日志输出结果

image-20241202114705487

image-20241202114921442

结合 logback,查看记录的日志文件

image-20241202115034224

我们后面的开发中,就不用为每个接口都去处理日志信息,通过aop切片即可自动的实现日志记录功能。


http://www.kler.cn/a/419579.html

相关文章:

  • 深入解析 Python 异步编程中的 `gather`、`as_completed` 和 `wait`
  • 【CSS】小球旋转loading加载动画
  • 【实战】Oracle基础之控制文件内容的5种查询方法
  • DBA面试题-1
  • 虚拟机玩游戏,轻松实现多开不同IP
  • springboot366高校物品捐赠管理系统(论文+源码)_kaic
  • Java 语言的起源发展与基本概念(JDK,JRE,JVM)
  • 基于Java Springboot电子书阅读器APP且微信小程序
  • AWS DynamoDB 与 AWS DocumentDB 与 MongoDB 之间有什么区别?
  • 【Robocasa】Code Review
  • Java基础访问修饰符全解析
  • 增删改查文档
  • Python 自动化办公的 10 大脚本
  • 【计算机网络】实验4:生成树协议STP的功能以及虚拟局域网VLAN
  • frp软件实现网络穿透
  • 【智慧园区】智慧产业园区能源管控系统解决方案,智慧工业园区解决方案(Word原件)
  • RT-Thread软件环境配置基础
  • React 路由(React Router):在 React 应用中管理路由
  • 信息安全之网络安全防护
  • 计算机网络(架构)
  • 专业解析 .bashrc 中 ROS 工作空间的加载顺序及其影响 ubuntu 机器人
  • 23种设计模式之适配器模式
  • neo4j如何存储关于liquidity structure的层次和关联结构
  • 设计模式之模板模式
  • qt6.4.0+visual studio2022+opencv
  • Linux系统编程之进程创建