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

切面收集日志

切面收集日志

  • 1. 使用切面在方法执行完毕后收集日志
    • 1.1 注解
    • 1.2 切面
    • 1.3 使用
    • 1.4 YML中对该注解的操作开关

1. 使用切面在方法执行完毕后收集日志

在这里插入图片描述

1.1 注解

import cn.ac.ict.knowledge.graph.enums.annotation.OptTypeEnum;

import java.lang.annotation.*;

/**
 * 日志收集注解
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperate {
    /**
     * 操作类型,默认为其他类型(OptTypeEnum.OTHER)
     * 用于记录方法执行的操作属于哪种预定义类型
     * 这有助于在日志或审计过程中快速识别操作的类别
     */
    OptTypeEnum optTypeEnum() default OptTypeEnum.OTHER;

    /**
     * 操作详细描述,默认为空字符串
     * 提供方法执行的具体操作描述,用于详细记录操作内容
     * 在日志记录时,这将帮助开发者更好地理解操作的细节
     */
    String optDetail() default "";

===========================================================
import lombok.Getter;

/**
 * 日志收集操作枚举
 */
@Getter
public enum OptTypeEnum {
    LOGIN,
    LOGOUT,
    QUERY,
    VIEW,
    EDIT,
    DELETE,
    IMPORT,
    EXPORT,
    ADD,
    DISABLE,
    ENABLE,
    OTHER
}

1.2 切面

这个切面是先通过方法调用之后,通过方法是正常执行完成,还是跑出了异常,来进行不同的方式处理。最后在通过POST请求把封装好VO信息发送给远端接口,来保存本次方法调用的请求参数以及想要的参数。

import cn.ac.ict.knowledge.graph.common.auth.LoginUser;
import cn.ac.ict.knowledge.graph.enums.annotation.OptTypeEnum;
import cn.ac.ict.knowledge.graph.framework.common.pojo.CommonResult;
import cn.ac.ict.knowledge.graph.utils.TimeUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import ua_parser.Client;
import ua_parser.Parser;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * 日志操作切面:拦截 @LogOperate 注解的方法,并将操作日志发送到日志采集服务器
 * <p>
 * 支持 application-dev.yml 配置:
 * log-operate.enabled=true  开启日志切面
 * log-operate.enabled=false 关闭日志切面
 *
 */
@Slf4j
@Aspect
@Component
public class LogOperateAspect {

    @Resource
    private LoginUser loginUser;

    /**
     * 应用id
     */
    @Value("${xxx000.xxx:1}")
    private String appId;

    /**
     * 是否启用日志切面(通过 application.yml 配置)
     */
    @Value("${log-operate.enabled:true}")
    private boolean logOperateEnabled;

    /**
     * 日志采集服务器地址
     */
    @Value("${xxx-xxx.host}")
    private String collectLogsHost;

    /**
     * 日志采集 API 路径
     */
    @Value("${xxx-xxx.collect}")
    private String collectReqPath;

    /**
     * 切点:拦截所有标注了 @LogOperate 的方法
     */
    @Pointcut("@annotation(xxx.xxx.common.annotation.log.LogOperate)")
    public void logOperatePointcut() {
    }

    /**
     * 方法执行完毕后,记录操作日志
     *
     * @param joinPoint 连接点
     * @param result    方法返回值
     */
    @AfterReturning(pointcut = "logOperatePointcut()", returning = "result")
    public void logOperation(JoinPoint joinPoint, Object result) {
        if (!logOperateEnabled) {
            return;
        }

        // 获取被拦截方法的签名信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法上标注的LogOperate注解
        LogOperate annotation = signature.getMethod().getAnnotation(LogOperate.class);

        // 操作结果(0-成功;1-失败)
        LogOperateReqVO vo = buildLogOperateReqVO(joinPoint, annotation, result, "", "0");
        log.info("======>[LogOperateAspect::sendLog]logOperateReqVO: {}", JSONUtil.toJsonStr(vo));

        // 发送日志到日志收集服务器
        sendLog(vo);
    }

    /**
     * 在抛出异常后执行的方法,用于记录操作日志
     * 该方法使用了AspectJ的@AfterThrowing注解,以便在匹配的切点方法抛出异常后执行日志记录操作
     *
     * @param joinPoint 切入点对象,提供了关于目标方法的信息,如方法签名和参数
     * @param exception 抛出的异常对象,用于获取异常信息
     */
    @AfterThrowing(pointcut = "logOperatePointcut()", throwing = "exception")
    public void logOperationException(JoinPoint joinPoint, Exception exception) {
        // 如果日志记录功能未启用,则直接返回,不执行后续操作
        if (!logOperateEnabled) {
            return;
        }

        // 获取方法签名,以便后续获取方法上的注解信息
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法上的LogOperate注解,用于获取日志记录相关的元数据
        LogOperate annotation = signature.getMethod().getAnnotation(LogOperate.class);

        // 获取异常信息,如果异常消息为空,则使用空字符串
        String exceptionMessage = StringUtils.isNotBlank(exception.getMessage()) ? exception.getMessage() : "";

        // 构建日志对象
        LogOperateReqVO vo = buildLogOperateReqVO(joinPoint, annotation, "", exceptionMessage, "1");

        // 发送日志对象到日志系统
        sendLog(vo);
    }

    /**
     * 构建日志对象
     *
     * @param joinPoint  连接点
     * @param annotation 方法上的 @LogOperate 注解
     * @param result     方法返回值
     * @param exception  异常信息
     * @param optResult  操作结果 (0-成功, 1-失败)
     * @return 日志对象
     */
    private LogOperateReqVO buildLogOperateReqVO(JoinPoint joinPoint, LogOperate annotation, Object result, String exception, String optResult) {
        LogOperateReqVO vo = new LogOperateReqVO();

        // 应用id
        vo.setAppId(appId);

        // 获取浏览器信息
        String browserInfo = getBrowserInfo();
        vo.setBrowserInfo(browserInfo);

        // 获取操作IP
        String ip = getIpAddress();
        vo.setIp(ip);

        // 获取操作详情
        String optDetail = annotation.optDetail();
        vo.setOptDetail(optDetail);

        // 操作类型
        vo.setOptType(annotation.optTypeEnum() != null ? annotation.optTypeEnum().name() : OptTypeEnum.OTHER.name());

        // 操作类型说明
        vo.setOptTypeAddition(annotation.optTypeEnum() != null ? annotation.optTypeEnum().name() : OptTypeEnum.OTHER.name());

        // 操作结果
        vo.setOptResult(optResult);

        // 异常信息(仅在失败时记录)
        vo.setException(exception);

        // 获取请求参数
        Object[] args = joinPoint.getArgs();
        if (args.length > 0) {
            vo.setReqParam(JSONUtil.toJsonStr(args));
        } else {
            vo.setReqParam("");
        }

        // 获取返回参数
        vo.setReturnParam(result != null ? JSONUtil.toJsonStr(result) : "");

        // 获取接口路径
        String url = getRequestUrl();
        vo.setUrl(url);

        // 获取用户ID(假设用户信息从上下文或session中获取)
        String userId = getUserId();
        vo.setUserId(userId);

        // 获取操作时间
        String optTime = getCurrentTime();
        vo.setOptTime(optTime);

        // 设置创建时间为当前时间
        String createTime = getCurrentTime();
        vo.setCreateTime(createTime);

        return vo;
    }

    /**
     * 获取浏览器信息(名称 + 版本)
     *
     * @return 例如 "Chrome 129.0.0.0"
     */
    private String getBrowserInfo() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String userAgentStr = request.getHeader("User-Agent");

        if (userAgentStr == null || userAgentStr.isEmpty()) {
            return "Unknown Browser";
        }

        Parser parser = new Parser();
        Client client = parser.parse(userAgentStr);
        String browserName = client.userAgent.family;
        String browserVersion = client.userAgent.major != null ? client.userAgent.major : "Unknown";

        return browserName + " " + browserVersion;
    }

    /**
     * 获取操作IP
     *
     * @return IP 地址
     */
    private String getIpAddress() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }

        return ip;
    }

    /**
     * 获取接口路径
     *
     * @return 请求接口路径
     */
    private String getRequestUrl() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

        // 获取协议(http 或 https)
        String scheme = request.getScheme();

        // 获取服务器的 IP 地址或主机名
        String serverName = request.getServerName();

        // 获取服务器端口
        int serverPort = request.getServerPort();

        // 获取请求路径(不包括协议、主机和端口)
        String requestURI = request.getRequestURI();

        // 拼接完整的 URL
        return scheme + "://" + serverName + ":" + serverPort + requestURI;
    }

    /**
     * 获取当前时间
     *
     * @return 当前时间的字符串表示
     */
    private String getCurrentTime() {
        return TimeUtil.getCurrentTime();
    }

    /**
     * 获取当前用户ID
     *
     * @return 当前用户的ID
     */
    private String getUserId() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 从请求头获取 token
        String token = request.getHeader("token");

        if (StringUtils.isBlank(token)) {
            return "";
        }

        return loginUser.getLoginUserId("token");
    }

    /**
     * 发送日志到日志收集服务器
     *
     * @param vo 日志对象
     */
    private void sendLog(LogOperateReqVO vo) {
        if (!logOperateEnabled) {
            return;
        }

        String requestBody = JSONUtil.toJsonStr(vo);
        String requestMapping = collectLogsHost + collectReqPath;

        log.info("======>[LogOperateAspect::sendLog] 日志采集请求路径:{}", requestMapping);
        log.info("======>[LogOperateAspect::sendLog] 日志请求体:{}", requestBody);

        try {
            HttpResponse response = HttpUtil.createPost(requestMapping)
                    .body(requestBody)
                    .execute();

            CommonResult<String> commonResult = JSONUtil.toBean(response.body(), CommonResult.class);
            if (commonResult.isError()) {
                log.error("======>[LogOperateAspect::sendLog] 请求失败,错误信息:{}", commonResult.getMsg());
            }
        } catch (Exception e) {
            log.error("======>[LogOperateAspect::sendLog] 请求异常,异常信息:", e);
        }
    }
}
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

import java.io.Serializable;

/**
 * 日志采集请求参数VO,详情见内部日志说明V1.0.1.docx
 *
 */
@Data
public class LogOperateReqVO implements Serializable {
    private static final long serialVersionUID = 1L;

    private String appId;

    private String browserInfo;

    private String exception;

    private String ip;
    
    private String optDetail;

    private String optResult;

    private String optType;

    private String optTypeAddition;

    private String reqParam;

    private String returnParam;

    private String userId;

    private String url;

    private String optTime;

    private String createTime;
}

1.3 使用

在Controller层上直接加上 @LogOperate(optTypeEnum = OptTypeEnum. x, optDetail = "xxxxxx")
在这里插入图片描述

1.4 YML中对该注解的操作开关

log-operate:
  enabled: true # 日志采集开启与关闭

相关文章:

  • MapStruct 中 @BeforeMapping 和 @AfterMapping 注解的使用详解
  • 大数据学习(49) - Flink按键分区状态(Keyed State)
  • 【Java基础】数组性能优化
  • DeepSeek崛起的本质分析:AI变局中的中国机会
  • C 程序多线程拆分文件
  • Linux---软连接与硬链接
  • 【PowerBI】使用形状地图创建地图可视化
  • less-8 boolen盲注,时间盲注 函数补全
  • 瑞萨RA-T系列芯片ADCGPT功能模块的配合使用
  • key-value---键值对
  • --- Mysql事务 ---
  • github在同步本地与远程仓库时遇到的问题
  • Exadata环境ORA-00603 AND ORA-27515报错的分析处理
  • leetcode刷题-动态规划05
  • PHP 可用的函数
  • Groovy语言的学习路线
  • 有关表单autocomplete = “off“ 失效问题解决方案
  • 如何commit后更新.gitignore实现push
  • DeepSeek-V3 技术报告
  • watermark解释
  • 个人是否可以做网站/网站域名综合查询
  • 东莞网站建设十大品牌/什么软件能搜索关键词能快速找到
  • 电商资讯网站有哪些/专业做app软件开发公司
  • 杭州行业网站建设/seo网站推广下载
  • 做微信公众号的是哪个网站吗/qq引流推广软件免费
  • 做交互式的网站怎么做/hs网站推广