spring集成aop进行个性化日志记录

背景

项目日志记录框架一般采用 log4jlogback,日志框架基本支持自动化日志记录和生成日志文件,但有时对于一些比较重要的日志信息往往需要保存到数据库中进行存储,此时需要自定义日志记录规则,同时不能影响现有业务的代码,因此需要基于 spring aop 的思想,动态的将日志记录功能代码切入到程序中指定位置;

优点
  • 日志记录十分灵活,代码复用率高

  • 通过切入点表达式切入指定位置,对现有程序无侵入

  • 能够实现较为复杂的日志记录逻辑代码

实现方式

这里以 spring 4.x web 项目为例:

  1. 引入依赖:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>4.3.20.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.8.9</version>
    </dependency>
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib-nodep</artifactId>
        <version>3.1</version>
    </dependency>

     

  2. spring xml中开启 aop 代理;

    <!-- 开启 aop -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
  3. 自定义日志注解,LogAnnotation,标记需要记录日志的方法;

    package com.dongzz.cms.common.annotation;
    
    import java.lang.annotation.*;
    
    /**
     * 日志注解
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface LogAnnotation {
    
        /**
         * 模块
         */
        String module() default "";
    
        /**
         * 操作
         */
        String operate() default "";
    
    }
  4. 自定义切面类和通知方法,拦截并处理 @LogAnnotation 注解标记的方法,进行动态日志记录;

    package com.dongzz.cms.common.annotation.aspect;
    
    import cn.hutool.core.collection.CollUtil;
    import cn.hutool.json.JSONObject;
    import cn.hutool.json.JSONUtil;
    import com.dongzz.cms.common.annotation.LogAnnotation;
    import com.dongzz.cms.common.utils.StringUtil;
    import com.dongzz.cms.modules.security.service.dto.ActiveUser;
    import com.dongzz.cms.modules.system.entity.SysLogs;
    import com.dongzz.cms.modules.system.service.LogsService;
    import com.dongzz.cms.common.utils.SecurityUtil;
    import com.dongzz.cms.common.utils.WebUtil;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    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 javax.servlet.http.HttpServletRequest;
    import java.util.Date;
    import java.util.Map;
    
    /**
     * aop 统一日志处理,定义切面类和通知方法
     */
    @Aspect
    @Component
    public class LogHandlerAdvice {
    
        public static final Logger logger = LoggerFactory.getLogger(LogHandlerAdvice.class);
    
        @Autowired
        private LogsService logsService;
    
        /**
         * 环绕通知
         * 切入到带有@LogAnnotation注解的方法
         *
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around(value = "@annotation(com.dongzz.cms.common.annotation.LogAnnotation)")
        public Object handleSysLogs(ProceedingJoinPoint joinPoint) throws Throwable {
            return recordSysLogs(joinPoint);
        }
    
        /**
         * 记录系统日志
         *
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        public Object recordSysLogs(ProceedingJoinPoint joinPoint) throws Throwable {
            // 在线用户
            ActiveUser u = SecurityUtil.getCurrentUserDetail();
            SysLogs logs = new SysLogs();
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); // 方法签名
    
            // 获取 @LogAnnotation 注解内容
            if (methodSignature.getMethod().isAnnotationPresent(LogAnnotation.class)) {
                LogAnnotation logAnnotation = methodSignature.getMethod().getAnnotation(LogAnnotation.class);
                String module = logAnnotation.module();
                String operate = logAnnotation.operate();
                logs.setModule(module);
                logs.setContent(operate);
            }
    
            //获取 操作位置类,方法
            Object target = joinPoint.getTarget();
            String className = target.getClass().getName();
            String methodName = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes()).getName();
            logs.setClazz(className);
            logs.setMethod(methodName);
            HttpServletRequest request = WebUtil.getHttpRequest();
            String ip = StringUtil.getIp(request);
            logs.setIp(ip);
            logs.setUrl(request.getRequestURL().toString());
            Map<String, Object> params = WebUtil.getParamsMap(request);
            if (CollUtil.isNotEmpty(params)) {
                logs.setParam(JSONUtil.toJsonStr(params));
            } else {
                // 获取 @RequestBody 相关参数
                JSONObject jsonObject = WebUtil.getParamsJson(request);
                logs.setParam(jsonObject.toString());
            }
    
            long startTime = System.currentTimeMillis();
            logs.setStartTime(new Date());
            try {
                Object object = joinPoint.proceed();
                logs.setStatus("1");
                logs.setEndTime(new Date());
                logs.setTotalTime((int) (System.currentTimeMillis() - startTime));
                // 返回值
                if (null != object) {
                    String returnData = JSONUtil.toJsonStr(object);
                    logs.setReturnData(returnData);
                }
                return object;
            } catch (Exception e) {
                logs.setStatus("0");
                logs.setEndTime(new Date());
                logs.setTotalTime((int) (System.currentTimeMillis() - startTime));
                logs.setContent(e.getMessage());
                throw e;
            } finally {
                if (u == null) {
                    logs.setUser("匿名");
                } else {
                    logs.setUser(u.getUsername());
                }
                logs.setTime(new Date());
                logsService.insertSelective(logs);
            }
        }
    }
  5. 在需要记录日志的方法上添加 @LogAnnotation 注解;

    /**
    * 修改密码
    */
    @PutMapping("/changePwd")
    @ApiOperation("修改密码")
    @LogAnnotation(module = "系统", operate = "修改密码")
    public RestResponse<Object> update(@RequestBody PwdDto pwdDto) throws Exception {
        userService.updatePwd(pwdDto);
        return new RestResponse<>(HttpStatus.OK.value(), HttpStatus.OK.getReasonPhrase());
    }

日志表:

image-20220113223358630

日志表可以参考截图自行设计,以上即为基于spring aop进行日志记录的演示!