接口日志,统一记录(AOP+自定义注解)

news2025/1/13 7:25:52

需求

指定接口,记录请求的日志。

接口日志的核心内容包括:请求方法,接口路径,请求参数等。

方案

采用的方案是:AOP + 自定义注解

说明:

  1. 在需要记录日志的接口上,加上自定义注解@ApiLog,则此接口的请求所包含的信息,会被记录到日志;
  2. 提供开关配置,可以选择是否开启接口日志;
  3. 接口日志的记录方式,推荐使用消息队列(比如:RocketMQ),异步处理;将接口的日志发送到消息队列里,由专门的日志记录服务器去处理,比如写入专门的数据库。这样可以减少接口的同步处理的时间,避免客户端等待时间过长,提升总体性能。本文仅为示例,所以只做了最简单的日志打印。

核心代码

注解:@ApiLog

package com.example.core.log.annotation;

import java.lang.annotation.*;

/**
 * 接口日志注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiLog {
}

切面:日志记录逻辑

package com.example.core.log.aspect;

import com.example.core.property.BaseFrameworkConfigProperties;
import com.example.core.util.JsonUtil;
import io.swagger.v3.oas.annotations.Operation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
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 javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Aspect
@Order(20)
@Component
public class ApiLogAspect {

    @Value("${spring.application.name:}")
    private String applicationName;

    private final BaseFrameworkConfigProperties properties;


    public ApiLogAspect(BaseFrameworkConfigProperties properties) {
        this.properties = properties;
    }


    // 定义一个切点:所有被 ApiLog 注解修饰的方法会织入advice
    @Pointcut("@annotation(com.example.core.log.annotation.ApiLog)")
    private void pointcut() {
    }


    // Before表示 advice() 将在目标方法执行前执行
    @Before("pointcut()")
    public void advice(JoinPoint joinPoint) {

        if (!properties.getApiLog().isEnabled()) {
            return;
        }

        log.info("\n-------------------- 接口日志,开始 --------------------");

        log.info("applicationName:{}", applicationName);

        // 获取请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();

            // 用户IP
            String clientIp = request.getRemoteAddr();
            log.info("clientIp:{}", clientIp);

            // URL
            String requestURL = request.getRequestURL().toString();
            log.info("url:{}", requestURL);

            // 请求方法
            String requestMethod = request.getMethod();
            log.info("requestMethod:{}", requestMethod);

            // 接口路径
            String path = request.getServletPath();
            log.info("path:{}", path);
        }

        // 接口参数
        Object[] args = joinPoint.getArgs();
        // 获取有效的接口参数(排除 HttpServletRequest 和 HttpServletResponse,否则会导致接口卡死)
        List<Object> validArgs = Stream.of(args).filter(this::isInclusiveArgument).collect(Collectors.toList());
        log.info("args:{}", JsonUtil.toJson(validArgs));

        // 方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        log.info("methodSignature:{}", methodSignature);

        // 方法参数名称列表
        String[] parameterNames = methodSignature.getParameterNames();
        log.info("parameterNames:{}", JsonUtil.toJson(parameterNames));

        // 获取接口的注解
        Operation operation = methodSignature.getMethod().getAnnotation(Operation.class);
        if (operation != null) {
            // 接口概述
            String summary = operation.summary();
            log.info("summary:{}", summary);

            // 接口描述
            String description = operation.description();
            log.info("description:{}", description);
        }

        log.info("\n-------------------- 接口日志,结束 --------------------\n");
    }


    /**
     * 是需要包含的参数。<br><br>
     * 不需要包含的参数(会导致接口卡死):<br>
     * 1. HttpServletRequest<br>
     * 2. HttpServletResponse
     *
     * @param arg 参数对象
     */
    private Boolean isInclusiveArgument(Object arg) {
        return !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse);
    }

}

日志开关配置

package com.example.core.property;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


/**
 * BaseFramework 配置文件
 *
 * @author songguanxun
 * 2019/08/27 15:40
 * @since 1.0.0
 */
@Data
@Component
@ConfigurationProperties(prefix = "base-framework")
public class BaseFrameworkConfigProperties {

    /**
     * 接口日志配置
     */
    private ApiLog apiLog = new ApiLog();

    /**
     * 接口日志配置
     */
    @Data
    public static class ApiLog {

        /**
         * 是否开启接口日志
         */
        private boolean enabled = false;

    }

}

配置(yml)

# 自定义配置
base-framework:
  api-log:
    enabled: true

调用示例代码

package com.example.web.exception.controller;

import com.example.core.log.annotation.ApiLog;
import com.example.core.model.PageQuery;
import com.example.web.exception.query.UserQuery;
import com.example.web.model.vo.UserVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestController
@RequestMapping("exception")
@Tag(name = "异常统一处理")
public class ExceptionController {


    @ApiLog
    @GetMapping(path = "users")
    @Operation(summary = "查询用户列表", description = "测试:BindException。参数校验异常:Get请求,Query参数,以对象的形式接收。")
    public List<UserVO> listUsers(@Valid UserQuery userQuery, PageQuery pageQuery,
                                  HttpServletRequest request, HttpServletResponse response, HttpSession session) {
        log.info("查询用户列表。userQuery={},pageQuery={}", userQuery, pageQuery);

        String queryName = userQuery.getName();
        String queryPhone = userQuery.getPhone();

        return listMockUsers().stream().filter(user -> {
            boolean isName = true;
            boolean isPhone = true;
            if (StringUtils.hasText(queryName)) {
                isName = user.getName().contains(queryName);
            }
            if (StringUtils.hasText(queryPhone)) {
                isPhone = user.getPhone().contains(queryPhone);
            }
            return isName && isPhone;
        }).collect(Collectors.toList());
    }


    private List<UserVO> listMockUsers() {
        List<UserVO> list = new ArrayList<>();

        UserVO vo = new UserVO();
        vo.setId("1234567890123456789");
        vo.setName("张三");
        vo.setPhone("18612345678");
        vo.setEmail("zhangsan@qq.com");
        list.add(vo);

        UserVO vo2 = new UserVO();
        vo2.setId("1234567890123456781");
        vo2.setName("李四");
        vo2.setPhone("13412345678");
        vo2.setEmail("lisi@example.com");
        list.add(vo2);

        return list;
    }

}

调用效果

接口调用

在这里插入图片描述

控制台日志

在这里插入图片描述

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

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

相关文章

样品运输与贮存

声明 本文是学习GB-T 42959-2023 饲料微生物检验 采样. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本文件规定了以微生物检验为目的的采样原则、采样人员、设备和材料、采样方案、采样步骤和采样 报告。 本文件适用于以微生物检验为目的…

如何限制文件只能通过USB打印机打印,限制打印次数和时限并且无法在打印前查看或编辑内容

在今天这个高度信息化的时代&#xff0c;文档打印已经成为日常工作中不可或缺的一部分。然而&#xff0c;这也带来了诸多安全风险&#xff0c;如文档被篡改、知识产权被侵犯以及信息泄露等。为了解决这些问题&#xff0c;只印应运而生。作为一款独特的软件工具&#xff0c;只印…

《视觉 SLAM 十四讲》V2 第 4 讲 李群与李代数 【什么样的相机位姿 最符合 当前观测数据】

P71 文章目录 4.1 李群与李代数基础4.1.3 李代数的定义4.1.4 李代数 so(3)4.1.5 李代数 se(3) 4.2 指数与对数映射4.2.1 SO(3)上的指数映射罗德里格斯公式推导 4.2.2 SE(3) 上的指数映射SO(3),SE(3),so(3),se(3)的对应关系 4.3 李代数求导与扰动模型4.3.2 SO(3)上的李代数求导…

S-Clustr(影子集群)僵尸网络@Мартин.

公告 项目地址:https://github.com/MartinxMax/S-Clustr/tree/V1.0.0 1.成功扩展3类嵌入式设备,组建庞大的"僵尸网络" |——C51[开发中] |——Arduino |——合宙AIR780e[开发中] 2.攻击者端与服务端之间通讯过程全程加密,防溯源分析 3.Generate一键自动生成Arduino…

将数组和减半的最少操作【贪心2】

题目&#xff1a;将数组和减半的最少操作 贪心思路&#xff1a;每次挑选最大的数来减半。 解法&#xff1a;贪心大根堆 class Solution { public:int halveArray(vector<int>& nums) {priority_queue<double> heap;double sum 0.0;for(int& x : nums){hea…

Linux性能优化--性能工具:系统内存

3.0.概述 本章概述了系统级的Linux内存性能工具。本章将讨论这些工具可以测量的内存统计信息&#xff0c;以及如何使用各种工具收集这些统计结果。阅读本章后&#xff0c;你将能够&#xff1a; 理解系统级性能的基本指标&#xff0c;包括内存的使用情况。明白哪些工具可以检索…

vtk 动画入门 1 代码

实现效果如图&#xff1a; #include <vtkAutoInit.h> //VTK_MODULE_INIT(vtkRenderingOpenGL2); //VTK_MODULE_INIT(vtkInteractionStyle); VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); //VTK_MODULE_INIT(vtkRenderingFreeType); #in…

lv8 嵌入式开发-网络编程开发 01什么是互联网

目录 1 计算机网络的定义与分类 1.1 按照网络的作用范围进行分类 1.2 按照网络的使用者进行分类 2 网络的网络 2.1 名词解释 2.2 边缘与核心 3 互联网基础结构发展的三个阶段 3.1 第一阶段&#xff1a;1969 – 1990 3.2 第二阶段&#xff1a;1985 – 1993 3.3 第三阶…

Python大数据之PySpark(四)SparkBaseCore

文章目录 SparkBase&Core环境搭建-Spark on YARN扩展阅读-Spark关键概念[了解]PySpark角色分析[了解]PySpark架构后记 SparkBase&Core 学习目标掌握SparkOnYarn搭建掌握RDD的基础创建及相关算子操作了解PySpark的架构及角色 环境搭建-Spark on YARN Yarn 资源调度框…

UE中制作棋盘格材质效果

在UE中通过这个小技巧制作棋盘格材质效果&#xff0c;可以快速预览UV拉伸情况&#xff0c;方便调试导入的模型。 1.操作步骤 1.1 首先新建材质&#xff0c;Shading Model&#xff08;着色模式&#xff09;设置为Unlit&#xff08;无光照&#xff09;&#xff1a; 1.2 我们…

管道-匿名管道

一、管道介绍 管道&#xff08;Pipe&#xff09;是一种在UNIX和类UNIX系统中用于进程间通信的机制。它允许一个进程的输出直接成为另一个进程的输入&#xff0c;从而实现数据的流动。管道是一种轻量级的通信方式&#xff0c;用于协调不同进程的工作。 1. 创建和使用管道&#…

Docker Alist 在线网盘部署

文章目录 拉取镜像创建并运行查看容器自动生成的密码在浏览器中进行访问 挂载本地磁盘 拉取镜像 docker pull xhofe/alist-aria2创建并运行 # -v /data/alist:/opt/alist/data 挂载本地目录 docker run -d --restartalways -v /data/alist:/opt/alist/data -p 5244:5244 -e P…

国庆作业6

TCP服务器 #include "head.h" #define PORT 2580 //端口号 #define IP "192.168.31.219" //本机IP int main(int argc, const char *argv[]) {sqlite3* dbNULL;if(sqlite3_open("./my.db",&db)!SQLITE_OK){fprintf(stde…

FFmpeg 命令:从入门到精通 | ffmpeg filter(过滤器 / 滤镜)

FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09; FFmpeg 命令&#xff1a;从入门到精通 | ffmpeg filter&#xff08;过滤器 / 滤镜&#xff09;ffmpeg fliter 基本内置变量视频裁剪文字水印图片水印画中画视频多宫格处理 FFmpeg 命…

BIOS 如何确定引导扇区的位置

机械硬盘的信息都是存储在磁盘的表面&#xff0c;他们是由一圈一圈的磁道组成的。以前的磁盘的不同磁道的扇区数量是相同的&#xff0c;这就导致了很大的浪费&#xff0c;因为最外面的磁道的密度是最低的&#xff0c;信息是最安全的&#xff0c;所以最外面的磁道是0磁道。后来由…

NPDP产品经理知识(产品创新管理)

复习文化&#xff0c;团队与领导力 产品创新管理&#xff1a; 如何树立愿景&#xff1a; 如何实现产品战略 计划 实施产品开发&#xff1a; 商业化&#xff0c;营销计划&#xff0c;推广活动 管理产品生命周期&#xff1a; 新式走向市场的流程&#xff1a;

win10 关闭病毒防护

windows10彻底关闭Windows Defender的4种方法 - 知乎

科技+智慧+颜值,智慧公厕黑科技提升城市形象

现代社会的科技和智慧正以惊人的速度渗透到我们生活的各个方面&#xff0c;包括公共设施。而作为城市形象的重要组成部分之一&#xff0c;公厕也在不断创新中迎来了智慧的时代。 在传统的公厕中&#xff0c;一些问题一直困扰着我们&#xff0c;比如厕位的占用情况、空气质量的…

腾讯云域名API解析升级版本(通过Java实现)腾讯云动态公网IP绑定域名实现内网服务器公网穿透

公众号推广: 目前CSDN进行VIP可见,文章可在微信公众号进行免费的阅读。 文章内容经过认证实践,比较的清晰易懂,适合初次接触的人员。 请关注微信公众号:菜鸟编程踩坑之路,进入公众号搜索关键词 内网穿透 需求场景: 首先我自己组装了一台自己的服务器,相比较购买的阿…

微信小程序——CSS3渐变

SS3 渐变&#xff08;gradients&#xff09;可以在两个或多个指定的颜色之间显示平稳的过渡。CSS3 定义了两种类型的渐变&#xff08;gradients&#xff09;&#xff1a; 说明 1、线性渐变&#xff08;Linear Gradients&#xff09;- 向下/向上/向左/向右/对角方向&#xff1…