利用AOP做一个完善的日志LogAspect切入以SpringBoot为框架基础

利用AOP做一个完善的日志LogAspect切入以SpringBoot为框架基础

 

日常的记录日志,不会还有人还在每个方法都重复书写log.info log....,还有些更初级的sout 哈哈哈,我们要学会用Spring中的利器,那就是AOP思想,替我们把这些重复的工作做了。AOP思想其实我们接触得很多了,像日志记录,权限控制,事务管理啊,基本都用过,里面运用的核心就是AOP。具体AOP详细的介绍和使用我这里就不赘述了,网上有很多资料。

今天我想学做个比较细致些的日志记录方式

利用AOP做一个完善的日志LogAspect切入以SpringBoot为框架基础_第1张图片

以SpringBoot为基础

1.先导入需要使用到包

        
            org.springframework.boot
            spring-boot-starter-aop
        
        
            com.alibaba
            fastjson
            1.2.70
        

        
            org.projectlombok
            lombok
            true
        

2.准备好需要使用到工具类,CommonUtil,GetUserUtils,RequestHolder,StringUtil,ThrowableUtil

CommonUtil

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;


public class CommonUtil {



        private static final String UNKNOWN = "unknown";


        /**
         * 获取ip地址
         */
        public static String getIp(HttpServletRequest request) {
            String ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            String comma = ",";
            String localhost = "127.0.0.1";
            if (ip.contains(comma)) {
                ip = ip.split(",")[0];
            }
            if  (localhost.equals(ip))  {
                // 获取本机真正的ip地址
                try {
                    ip = InetAddress.getLocalHost().getHostAddress();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
            }
            return ip;
        }


        public static String getUserAgent(HttpServletRequest request){

            return request.getHeader("User-Agent");
        }

}

GetUserUtils

public class GetUserUtils {

    public static User getCurrentUser(){
        return new User("123","admin");
    }
}

RequestHolder

public class RequestHolder {

    public static HttpServletRequest getHttpServletRequest(){
        return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
    }
}

StringUtil

public class StringUtil {
    /** 空字符串 */
    private static final String NULLSTR = "";

    /**
     * 截取字符串
     *
     * @param str 字符串
     * @param start 开始
     * @param end 结束
     * @return 结果
     */
    public static String substring(final String str, int start, int end)
    {
        if (str == null)
        {
            return NULLSTR;
        }

        if (end < 0)
        {
            end = str.length() + end;
        }
        if (start < 0)
        {
            start = str.length() + start;
        }

        if (end > str.length())
        {
            end = str.length();
        }

        if (start > end)
        {
            return NULLSTR;
        }

        if (start < 0)
        {
            start = 0;
        }
        if (end < 0)
        {
            end = 0;
        }

        return str.substring(start, end);
    }


}

ThrowableUtil

public class ThrowableUtil {

    /**log.setExceptionDetail(ThrowableUtil.getStackTrace(e).getBytes());
     * 获取堆栈信息
     */
    public static String getStackTrace(Throwable throwable){
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }
}

3.准备实体类

@Data
public class OperationLog {
    private String operId;//主键
    private String module;//功能模块
    private String type;//操作类型
    private String requestMethod;//请求方式
    private String operDesc;//操作描述
    private String operUserAgent;//操作描述
    private String operRequstParam;//请求参数
    private String operResponseParam;//返回参数
    private String operUserId;//操作员id
    private String operUsername;//操作员名称
    private String operLocation;//操作地点
    private Integer status;//操作状态
    private String operMethod;//操作方法
    private String operURL;//请求URL
    private String operIp;//请求ID
    private String excName;//异常名称
    private String errorMsg;//错误信息
    private Date createTime;//操作时间
    private Long costTime;//耗时


}
@Data
public class User {
    private String userId;
    private String username;

    public User(String userId, String username) {
        this.userId = userId;
        this.username = username;
    }
}

4.准备注解 用于 标注需要 切入的点

@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
public @interface Log {
    String operModule() default ""; // 操作模块
    OperType operType() default OperType.ADD;  // 操作类型
    String operDesc() default "";  // 操作说明

}
public enum OperType {

    //操作类型
    TEST("测试"),
    ADD("新增"),DELETE("删除"),QUERY("查询")
    ;

    private String type;

    OperType(String type) {
        this.type = type;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}

5.主角上场 operLogAspect

@Aspect
@Component
@Slf4j
public class OperLogAspect {

    ThreadLocal currentTime = new ThreadLocal<>();
    /**
     * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
     */
    @Pointcut("@annotation(com.example.operlog.annotation.Log)")
    public void operLogPointCut() {
    }

    /**
     * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
     */
    @Pointcut("execution(* com.example.operlog.controller..*.*(..))")
    public void operExceptionLogPointCut() {
    }

    @Around("operLogPointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        currentTime.set(System.currentTimeMillis());
        //正常处理开始
        result = joinPoint.proceed();
        //正常处理结束
        Long time=System.currentTimeMillis() - currentTime.get();
        currentTime.remove();

        handleLog(joinPoint,null,result,time);

        return result;
    }


    @AfterThrowing(pointcut = "operExceptionLogPointCut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Exception e) {
       handleLog(joinPoint,e,null,null);
    }

    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult,Long time){
        try
        {

            // 获取RequestAttributes
   /*         RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            // 从获取RequestAttributes中获取HttpServletRequest的信息
            HttpServletRequest request = (HttpServletRequest) requestAttributes
                    .resolveReference(RequestAttributes.REFERENCE_REQUEST);*/
            HttpServletRequest request = RequestHolder.getHttpServletRequest();

            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //获取注解
            Log  aopLog = method.getAnnotation(Log.class);


            // 获取当前的用户
            User currentUser = GetUserUtils.getCurrentUser();

            OperationLog operLog = new OperationLog();
            String id = UUID.randomUUID().toString().replaceAll("-", "");
            operLog.setOperId(id);
            operLog.setOperURL(request.getRequestURI());
            operLog.setStatus(1);//成功
            operLog.setCreateTime(new Date());
            operLog.setCostTime(time);
            operLog.setOperUserAgent(CommonUtil.getUserAgent(request));
            // 请求的地址
            operLog.setOperIp(CommonUtil.getIp(request));

            // 返回参数
            operLog.setOperResponseParam(JSON.toJSONString(jsonResult));

            if (currentUser != null)
            {
                operLog.setOperUserId(currentUser.getUserId());
                operLog.setOperUsername(currentUser.getUsername());
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            //设置请求方法名
            operLog.setOperMethod(className + "." + methodName + "()");
            // 设置请求方式GET..
            operLog.setRequestMethod(request.getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, aopLog, operLog);

            //利用是否有异常定性记录失败信息
            if (e != null)
            {
                operLog.setStatus(0);//失败
                operLog.setExcName(e.getClass().getName());
                operLog.setErrorMsg(ThrowableUtil.getStackTrace(e));

                log.error("耗时:{}  用户id:{}   用户名username: {}  请求ip:{}  User-Agent:{} 方法路径:{} 方法参数:{}",
                        operLog.getCostTime(),
                        operLog.getOperUserId(),
                        operLog.getOperUsername(),
                        operLog.getOperId(),
                        operLog.getOperUserAgent(),
                        methodName,
                        operLog.getOperRequstParam());
                log.error("==控制层方法通知异常==");
                log.error("异常信息:{}", e.getMessage());
                //e.printStackTrace();
                return;
            }
            log.info(JSON.toJSONString(operLog));
            // 保存数据库
            //可以借助异步持久化AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
            log.info("耗时:{}  用户id:{}   用户名username: {}  请求ip:{}  User-Agent:{} 方法路径:{} 方法参数:{}",
                    operLog.getCostTime(),
                    operLog.getOperUserId(),
                    operLog.getOperUsername(),
                    operLog.getOperId(),
                    operLog.getOperUserAgent(),
                    methodName,
                    operLog.getOperRequstParam());
        }
        catch (Exception exp)
        {
            // 记录本地异常日志
            log.error("==前置通知异常==");
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        }

    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     *
     * @param log 日志
     * @param operLog 操作日志
     * @throws Exception
     */
    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, OperationLog operLog) throws Exception
    {
        if(log!=null){
            operLog.setModule(log.operModule());
            operLog.setType(log.operType().getType());
            operLog.setOperDesc(log.operDesc());
        }else {
            //用于无注解的控制层方法 异常抛出 记录
            operLog.setModule("无默认模块");
            operLog.setType("无默认类别");
            operLog.setOperDesc("无默认描述");
        }

        // 获取参数的信息
        setRequestValue(joinPoint, operLog);
    }

    /**
     * 获取请求的参数,放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, OperationLog operLog) throws Exception
    {
        String params = argsArrayToString(joinPoint.getArgs());
        //避免过长的无用信息
        operLog.setOperRequstParam(StringUtil.substring(params, 0, 2000));
    }
    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray)
    {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0)
        {
            for (int i = 0; i < paramsArray.length; i++)
            {

                //排除不需要记录的参数
                if (!isFilterObject(paramsArray[i])){

                    Object value=paramsArray[i];
                    if (value instanceof MultipartFile) {
                        MultipartFile file = (MultipartFile) value;
                        Object jsonObj = JSON.toJSON(file.getOriginalFilename());
                        params.append(jsonObj.toString()).append(" ");
                    }
                    else if (value instanceof MultipartFile[]) {
                        MultipartFile[] files = (MultipartFile[]) value;
                        for(MultipartFile file:files){
                            Object jsonObj = JSON.toJSON(file.getOriginalFilename());
                            params.append(jsonObj.toString()).append(" ");
                        }
                    }else{
                        Object jsonObj = JSON.toJSON(value);
                        params.append(jsonObj.toString()).append(" ");
                    }
                }
            }
        }
        return params.toString();
    }


    /**
     * 判断是否需要过滤
     * @param o 对象信息。
     * @return 是需要过滤的对象,则返回true;否则返回false。
     */
    public boolean isFilterObject(final Object o)
    {
        return o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }





/*
    //用于未定义log注解,抛出异常也需要记录耗时情况
    @Before("operExceptionLogPoinCut()")
    public void beforeMethod(JoinPoint joinPoint){
        currentTime.set(System.currentTimeMillis());
    }
    @After("operExceptionLogPoinCut()")
    public void afterMethod(JoinPoint joinPoint){
        currentTime.remove();
    }*/

}

6.用于测试的controller

@RestController
@RequestMapping("/test")
public class TestController {

    //在带注解,抛出异常
    @GetMapping("/1")
    @Log(operModule = "查询",operDesc = "就查查而已",operType = OperType.QUERY)
    public void get(){
        throw new RuntimeException("test");

    }
    //注解,正常执行
    @GetMapping("/2")
    @Log(operModule = "查询",operDesc = "就查查而已222222",operType = OperType.QUERY)
    public void get2(){
        System.out.println("=======get2");

    }
    //不带注解,抛出异常
    @GetMapping("/3")
    public void get3(){
        throw new RuntimeException("get3");

    }
    //不带注解,正常执行与返回数据
    @GetMapping("/4")
    public Object get4(){
        System.out.println("=======get4");
        return new User("123","aaaaaa");
    }
    //带文件参数测试
    @PostMapping("/file")
    @Log
    public void uploadFile(String param,MultipartFile[] file){
        System.out.println("=======upload file");
    }
    @PostMapping("/file1")
    @Log
    public void uploadFile1(@RequestBody User param){
        System.out.println("=======upload file1");
    }
}

测试结果

test/1

2020-11-14 14:19:46.808 ERROR 52588 --- [nio-8080-exec-9] com.example.operlog.aop.OperLogAspect    : 耗时:null  用户id:123   用户名username: admin  请求ip:547985c7451d4aeaa7632f57cf128044  User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 方法路径:get 方法参数:
2020-11-14 14:19:46.808 ERROR 52588 --- [nio-8080-exec-9] com.example.operlog.aop.OperLogAspect    : ==控制层方法通知异常==
2020-11-14 14:19:46.808 ERROR 52588 --- [nio-8080-exec-9] com.example.operlog.aop.OperLogAspect    : 异常信息:test
2020-11-14 14:19:46.818 ERROR 52588 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: test] with root cause

java.lang.RuntimeException: test
	at com.example.operlog.controller.TestController.get(TestController.java:27) ~[classes/:na]

test/2

2020-11-14 14:22:12.976  INFO 52588 --- [nio-8080-exec-2] com.example.operlog.aop.OperLogAspect    : {"costTime":0,"createTime":1605334932952,"module":"查询","operDesc":"就查查而已222222","operId":"ec33d56eb10a442cbb75663034575544","operIp":"0:0:0:0:0:0:0:1","operLocation":"内网IP","operMethod":"com.example.operlog.controller.TestController.get2()","operRequstParam":"","operResponseParam":"null","operURL":"/test/2","operUserAgent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36","operUserId":"123","operUsername":"admin","requestMethod":"GET","status":1,"type":"查询"}
2020-11-14 14:22:12.977  INFO 52588 --- [nio-8080-exec-2] com.example.operlog.aop.OperLogAspect    : 耗时:0  用户id:123   用户名username: admin  请求ip:ec33d56eb10a442cbb75663034575544  User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 方法路径:get2 方法参数:

test/3

2020-11-14 14:23:09.488 ERROR 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect    : 耗时:null  用户id:123   用户名username: admin  请求ip:eba1a0c0dd1f43ba999416a29202c497  User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 方法路径:get3 方法参数:
2020-11-14 14:23:09.488 ERROR 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect    : ==控制层方法通知异常==
2020-11-14 14:23:09.488 ERROR 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect    : 异常信息:get3
2020-11-14 14:23:09.489 ERROR 52588 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: get3] with root cause

java.lang.RuntimeException: get3

test/file

=======upload file
2020-11-14 14:25:14.444  INFO 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect    : {"costTime":0,"createTime":1605335114443,"module":"","operDesc":"","operId":"372f9bfad79b4938b2424dda659cfc89","operIp":"0:0:0:0:0:0:0:1","operLocation":"内网IP","operMethod":"com.example.operlog.controller.TestController.uploadFile()","operRequstParam":"123 2 (1).png 312312312.png ","operResponseParam":"null","operURL":"/test/file","operUserAgent":"PostmanRuntime/6.1.6","operUserId":"123","operUsername":"admin","requestMethod":"POST","status":1,"type":"新增"}
2020-11-14 14:25:14.444  INFO 52588 --- [nio-8080-exec-5] com.example.operlog.aop.OperLogAspect    : 耗时:0  用户id:123   用户名username: admin  请求ip:372f9bfad79b4938b2424dda659cfc89  User-Agent:PostmanRuntime/6.1.6 方法路径:uploadFile 方法参数:123 2 (1).png 312312312.png 

test/file1

=======upload file1
2020-11-14 14:25:53.541  INFO 52588 --- [nio-8080-exec-6] com.example.operlog.aop.OperLogAspect    : {"costTime":0,"createTime":1605335153537,"module":"","operDesc":"","operId":"dd808ebd88a34b83996b72f22e48a351","operIp":"0:0:0:0:0:0:0:1","operLocation":"内网IP","operMethod":"com.example.operlog.controller.TestController.uploadFile1()","operRequstParam":"{\"userId\":\"123\",\"username\":\"222\"} ","operResponseParam":"null","operURL":"/test/file1","operUserAgent":"PostmanRuntime/6.1.6","operUserId":"123","operUsername":"admin","requestMethod":"POST","status":1,"type":"新增"}
2020-11-14 14:25:53.541  INFO 52588 --- [nio-8080-exec-6] com.example.operlog.aop.OperLogAspect    : 耗时:0  用户id:123   用户名username: admin  请求ip:dd808ebd88a34b83996b72f22e48a351  User-Agent:PostmanRuntime/6.1.6 方法路径:uploadFile1 方法参数:{"userId":"123","username":"222"} 

 

你可能感兴趣的:(SpringBoot,java,spring)