SpringBoot使用Aspect切面日志

SpringBoot日志处理

一、使用Aspect切面技术做日志处理

1、前言

SpringBoot框架中提供了Aspect切面技术来供我们进行使用,Aspect技术是一种通过预编译和运行期动态代理的统一维护技术。
实际的使用过程中,Aspect技术基本能够和业务逻辑相互独立。
简而言之、就是在请求接口的过程管道中横切一下,然后添加增强信息。

2、专业名词

@Aspect:(切面)用来将一个类声明成为Aspect类;
@Pointcut:(切点)切面与业务逻辑相交的点叫做切点;
JoinPoint:(连接点)关键节点、通过连接点来获取信息
ProceedingJoinPonit:(进行连接点)关键节点、获取信息;

3、通知类型

使用通知之前我们首先需要自定义一个方法、将其作为切入点

    /**
     * 定义一个切点,后续通知方法将会使用该节点来进行获取
     *    将Controller层中的所有方法作为切面与业务逻辑交互点
     */
    @Pointcut("execution(public * com.song.*.controller..*Controller.*(..))")
    public void controllerPointcut(){}

@Before:在被请求的方法执行之前进行Advice增强。

/**
     * 前置通知
     *      在执行业务代码之前进行信息处理
     *
     * @param joinPoint 切点
     */
    @Before("controllerPointcut()")
    public void doBefore(JoinPoint joinPoint){
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();

        // 打印请求信息
        logger.info("-----Aspect 开始-----");
        logger.info("请求地址:{} {}", request.getRequestURL().toString(), request.getMethod());
        logger.info("类名方法:{} {}", signature.getDeclaringTypeName(), name);
        logger.info("远程地址:{}", request.getRemoteAddr());

        // 从request中获取IP并设置到线程本地变量
        RequestContext.setRemoteAddr(getRemoteIp(request));

        // 定义请求参数
        Object[] args = joinPoint.getArgs();
//        logger.info("请求参数:{}", JSONObject.toJSONString(args));

        // 循环多个参数
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++){
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile){
                continue;
            }
            arguments[i] = args[i];
        }

        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"password", "file"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);
        logger.info("请求参数:{}", JSONObject.toJSONString(arguments, excludefilter));
    }

@Around:在整个请求方法过程中执行,获取请求结果等信息。

    /**
     * 环绕通知
     *      业务内容前面执行一些信息、业务内容后面再执行一些信息
     *
     * @param proceedingJoinPoint
     *
     * @return 返回请求方法返回的结果
     */
    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 获取返回结果
        Object result = proceedingJoinPoint.proceed();

        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"password", "file"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);

        logger.info("返回结果:{}", JSONObject.toJSONString(result, excludefilter));
        logger.info("-----Aspect 结束 耗时:{}ms-----", System.currentTimeMillis() - startTime);

        return result;
    }

4、完整代码示例

package com.song.wiki.aspect;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.song.wiki.utils.RequestContext;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;


import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;


/**
 * ClassName:   SpringBoot切面控制类
 * Author:      挽风
 * Date:        2020
 * Copyright:   2020 by 挽风1.0版本
 * Description:
 *
 *  SpringBoot切面(面向切面编程)
 *
 *  @Aspect: 将该类标注为一个切面类
 *  @Component 为该类添加组件扫描注解、将其交给SpringBoot进行管理
 *
 **/

@Aspect
@Component
public class LogAspect{

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    /**
     * 定义一个切点,后续通知方法将会使用该节点来进行获取
     *      将Controller层中的所有方法作为切面与业务逻辑交互点
     */
    @Pointcut("execution(public * com.song.*.controller..*Controller.*(..))")
    public void controllerPointcut(){}

    /**
     * 前置通知
     *      在执行业务代码之前进行信息处理
     *
     * @param joinPoint 切点
     */
    @Before("controllerPointcut()")
    public void doBefore(JoinPoint joinPoint){
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 获取签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();

        // 打印请求信息
        logger.info("-----Aspect 开始-----");
        logger.info("请求地址:{} {}", request.getRequestURL().toString(), request.getMethod());
        logger.info("类名方法:{} {}", signature.getDeclaringTypeName(), name);
        logger.info("远程地址:{}", request.getRemoteAddr());

        // 从request中获取IP并设置到线程本地变量
        RequestContext.setRemoteAddr(getRemoteIp(request));

        // 定义请求参数
        Object[] args = joinPoint.getArgs();
//        logger.info("请求参数:{}", JSONObject.toJSONString(args));

        // 循环多个参数
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++){
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile){
                continue;
            }
            arguments[i] = args[i];
        }

        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"password", "file"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);
        logger.info("请求参数:{}", JSONObject.toJSONString(arguments, excludefilter));
    }

    /**
     * 使用Nginx做反向代理,需要使用该方法才能获取到真实的远程IP
     *          因为前后端分离后,所有用户的操作都会先到nginx中,然后再转给SpringBoot
     *
     * @param request
     *
     * @return
     */
    private String getRemoteIp(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();
        }
        return ip;
    }

    /**
     * 环绕通知
     *      业务内容前面执行一些信息、业务内容后面再执行一些信息
     *
     * @param proceedingJoinPoint
     *
     * @return 返回请求方法返回的结果
     */
    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 获取返回结果
        Object result = proceedingJoinPoint.proceed();

        // 排除字段,敏感字段或太长的字段不显示
        String[] excludeProperties = {"password", "file"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludefilter = filters.addFilter();
        excludefilter.addExcludes(excludeProperties);

        logger.info("返回结果:{}", JSONObject.toJSONString(result, excludefilter));
        logger.info("-----Aspect 结束 耗时:{}ms-----", System.currentTimeMillis() - startTime);

        return result;
    }
}

二、使用Filter过滤器做日志处理

1、概述

使用过滤器来做日志处理时,需要实现Filter接口,覆写Filter里面的init方法和doFilter方法即可。
在实际的使用过程中,我们一般使用Aspect切面技术来进行日志的处理,而不推荐使用Filter来过滤日志信息。

2、代码实现

package com.song.wiki.filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * ClassName:   Wiki日志过滤器
 * Author:      挽风
 * Date:        2020
 * Copyright:   2020 by 挽风1.0版本
 * Description:
 *
 **/

@Component
public class LogFilter implements Filter {

    private static final Logger logger = LoggerFactory.getLogger(LogFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 打印请求信息
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        logger.info("-----LogFilter开启-----");
        logger.info("请求地址:{} {}", request.getRequestURL().toString(), request.getMethod());
        logger.info("远程地址:{}", request.getRemoteAddr());

        long startTime = System.currentTimeMillis();
        filterChain.doFilter(servletRequest, servletResponse);
        logger.info("-----LogFilter结束耗时:{}ms-----", System.currentTimeMillis() - startTime);
    }
}

三、使用Interceptor拦截器做日志处理

1、前言

SpringBoot中提供拦截器功能中,通过自定义拦截器类,实现HandlerInterceptor处理程序拦截器栏进行日志的处理。

2、代码实现

自定义拦截器、实现HandlerInterceptor处理程序拦截器,实现其中的preHandle预处理方法与posthandle后处理方法来进行请求接口日志处理。

package com.song.wiki.interceptor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * ClassName:   SpringBoot拦截器
 * Author:      挽风
 * Date:        2020
 * Copyright:   2020 by 挽风1.0版本
 * Description:
 *
 *      作用于登录校验、权限校验、请求日志打印等
 *
 **/

@Component
public class LogInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LogInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 打印请求信息
        logger.info("-----LogInterceptor 开始-----");
        logger.info("请求地址:{}{}", request.getRequestURL().toString(), request.getMethod());
        logger.info("远程地址:{}", request.getRemoteAddr());

        long startTime = System.currentTimeMillis();
        request.setAttribute("requestStartTime", startTime);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        long startTime = (long) request.getAttribute("requestStartTime");
        logger.info("-----LogInterceptor 结束 耗时:{}ms-----", System.currentTimeMillis() - startTime);
    }
}

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