SpringBoot 使用自定义注解(基于AOP) 实现操作日志管理

本文介绍了如何在SpringBoot中利用AOP和自定义注解实现操作日志管理,包括记录用户行为、日志实现思路、具体实现步骤、依赖引入以及自定义注解和AOP的使用,旨在提供一种业务运行日志的解决方案。

操作日志的记录

因为我们不光要记录代码的运行,如(logback log4j),而且还应该记录用户的行为(Controller 请求日志),这叫做业务运行日志。

业务运行日志的作用

  1. 记录用户的行为 用于后续的分析
  2. 记录用户的所有的操作

日志实现的思路

1.我们需要记录哪些数据(谁在什么时间干了什么事情,结果如何)? 数据要存入到哪里(存储到数据库表,推送到指定日志存储服务,打印到日志文件等)?

2.在项目中什么位置记录(一般在Controller请求,进行around 环绕增强)

如何实现记录功能

实现方式:注解+AOP

Aop日志记录 具体代码实现

aop的使用流程,这里使用注解式aop来实现
具体步骤:

  1. 设置切入点
    1. 可以切在方法上
    2. 可以切在注解上
      @Transactional 事务注解 注解加在类上 aop 切在注解上
      
  2. 写增强 日志记录增强
    1. 获取日志的相关信息用户的id ip地址, 时间, 操作的描述, 类型等信息
    2. 将日志对象 添加到指定服务(文件,数据库,系统)

             

但是,操作的描述如何获取呢?

     使用自定义注解:

  1. 在 目标 方法上添加自定义注解 (@Logger) 如下

  2. 在增强中获取注解(@Logger)的属性获取

具体实现

依赖引入

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>eu.bitwalker</groupId>
        <artifactId>UserAgentUtils</artifactId>
        <version>1.21</version>
    </dependency>

 

自定义注解

package com.wang.chao.micro.log.annotation;

import com.wang.chao.micro.log.enums.LogSeverity;
import java.lang.annotation.*;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logger {

    /**
     * 日志来源 操作模块
     *
     * @return
     */
    String operSource() default "";

    /**
     * 日志级别
     *
     * @return
     */
    LogSeverity severity() default LogSeverity.WARNING;

    /**
     * 日志操作名称
     *
     * @return
     */
    String operName() default "";

    /**
     * 是否 持久化(persistence)
     *
     * @return
     */
    boolean isPersistence() default false;
}

使用AOP统一处理日志

package com.wang.chao.micro.log.annotation.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.chao.micro.id.genertor.IdWorker;
import com.wang.chao.micro.log.annotation.Logger;
import com.wang.chao.micro.log.bean.ExceptionOperLog;
import com.wang.chao.micro.log.bean.OperationLog;
import com.wang.chao.micro.log.service.LoggerSupport;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
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.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

@Component
@Aspect
@Slf4j
public class LoggerAspect {
  private static final  ObjectMapper mapper = new ObjectMapper();

    @Pointcut("@annotation(com.wang.chao.micro.log.annotation.Logger)")
    public void logger() {
    }

    @Around("logger()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        Object ret = pjp.proceed();
        OperationLog operLog = buildOperationLog(pjp, ret);
       // log.info(writeValueAsString(operLog));
        LoggerSupport.addAuditLog(operLog);
        return ret;

    }

    @AfterThrowing(value = "logger()", throwing = "throwable")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
        // 保存异常日志记录
        OperationLog operLog = buildExceptionOperationLog(joinPoint, throwable);
        LoggerSupport.addAuditLog(operLog);
    }

    private OperationLog buildExceptionOperationLog(JoinPoint joinPoint, Throwable t) {
        ExceptionOperLog operLog = new ExceptionOperLog(buildOperationLog(joinPoint, null));
        operLog.setExceptionName(t.getClass().getName()); // 异常名称
        operLog.setExceptionMessage(stackTraceToString(t.getClass().getName(), t.getMessage(), t.getStackTrace())); // 异常信息
        return operLog;
    }

    private OperationLog buildOperationLog(JoinPoint joinPoint, Object ret) {
        OperationLog operLog = new OperationLog();
        // 接收到请求,记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        operLog.setTranceId(IdWorker.nextId());
        operLog.setStartTime(System.currentTimeMillis());
        operLog.setUrl(request.getRequestURL().toString());
        operLog.setMethod(request.getMethod());

        // 从切面织入点处通过反射机制获取织入点处的方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取切入点所在的方法
        Method method = signature.getMethod();
        // 获取操作日志注解信息
        Logger loggerAnnotation = method.getAnnotation(Logger.class);
        if (loggerAnnotation != null) {
            operLog.setSource(loggerAnnotation.operSource());
            operLog.setSeverity(loggerAnnotation.severity());
            operLog.setOperName(loggerAnnotation.operName());
            operLog.setPersistence(loggerAnnotation.isPersistence());
        }
        operLog.setRequestMethod(signature.getDeclaringTypeName() + "." + signature.getName());
        operLog.setRemoteAddr(request.getRemoteAddr());
        operLog.setArgs(writeValueAsString(request.getParameterMap()));
        //获取请求头中的User-Agent
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
        operLog.setUserAgent(userAgent);
        // pjp.proceed():当我们执行完切面代码之后,还有继续处理业务相关的代码。proceed()方法会继续执行业务代码,并且其返回值,就是业务处理完成之后的返回值。
        operLog.setEndTime(System.currentTimeMillis());
        operLog.setResult(ret);
        return operLog;
    }

    private String writeValueAsString(Object object){
        String json = null;
        try {
            json = mapper.writeValueAsString(object);
        } catch (JsonProcessingException e) {
           log.error("object covert json exception,",e);
        }
        return json;
    }
    /**
     * 转换异常信息为字符串
     *
     * @param exceptionName    异常名称
     * @param exceptionMessage 异常信息
     * @param elements         堆栈信息
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "\n");
        }
        String message = exceptionName + ":" + exceptionMessage + "\n\t" + strbuff.toString();
        return message;
    }
}

另外日志处理采用了阻塞队列进行数据缓冲处理,日志对象具体消费使用多线程实现。

具体日志服务代码如下:

https://gitee.com/mystarry-sky/microservice-component/tree/master/log-genertor

https://gitee.com/mystarry-sky/microservice-component/

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望星空@脚踏实地

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值