[springboot]拦截器实现统一访问日志

在这里插入图片描述

一、需求

我们本节要实现的需求

  • 针对当前系统的每一次接口访问,要记录是什么人访问的(用户名)、什么时间访问的、访问耗时多长时间、使用什么HTTP method方法访问的、访问结果如何等。可以称为审计日志。
  • 将访问记录审计日志,输出到一个单独的日志文件access.log

二、定义访问日志内容记录实体类

@Data
public class AccessLog {
    
    
    //访问者用户名
    private String username;
    //请求路径
    private String url;
    //请求消耗时长
    private Integer duration;
    //http 方法:GET、POST等
    private String httpMethod;
    //http 请求响应状态码
    private Integer httpStatus;
    //访问者ip
    private String ip;
    //此条记录的创建时间
    private Date createTime;
}

三、自定义日志拦截器

通过自定义拦截器的方式,记录审计日志。

  • 拦截器的preHandle方法,可以用于拦截请求处理开始。用于记录请求开始时间等信息保存到Http Request,用于后续计算请求时长。
  • 拦截器的postHandle方法,可以用于拦截请求处理完成。可以从Request对象获取开始时间,计算本次请求总的处理时长等信息。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;


public class AccessLogInterceptor implements HandlerInterceptor {
    
    
    //请求开始时间标识
    private static final String LOGGER_SEND_TIME = "SEND_TIME";
    //请求日志实体标识
    private static final String LOGGER_ACCESSLOG = "ACCESSLOG_ENTITY";


    private static final Logger logger = LoggerFactory.getLogger("ACCESS-LOG");

    /**
     * 进入SpringMVC的Controller之前开始记录日志实体
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
    
    

        //创建日志实体
        AccessLog accessLog = new AccessLog();

        // 设置IP地址
        accessLog.setIp(AdrressIpUtils.getIpAdrress(request));

        //设置请求方法,GET,POST...
        accessLog.setHttpMethod(request.getMethod());

        //设置请求路径
        accessLog.setUrl(request.getRequestURI());

        //设置请求开始时间
        request.setAttribute(LOGGER_SEND_TIME,System.currentTimeMillis());

        //设置请求实体到request内,方便afterCompletion方法调用
        request.setAttribute(LOGGER_ACCESSLOG,accessLog);
        return true;
    }


    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
    
    

        //获取本次请求日志实体
        AccessLog accessLog = (AccessLog) request.getAttribute(LOGGER_ACCESSLOG);

        //获取请求错误码,根据需求存入数据库,这里不保存
        int status = response.getStatus();
        accessLog.setHttpStatus(status);

        //设置访问者(这里暂时写死)
        // 因为不同的应用可能将访问者信息放在session里面,有的通过request传递,
        // 总之可以获取到,但获取的方法不同
        accessLog.setUsername("admin");

        //当前时间
        long currentTime = System.currentTimeMillis();

        //请求开始时间
        long snedTime = Long.valueOf(request.getAttribute(LOGGER_SEND_TIME).toString());


        //设置请求时间差
        accessLog.setDuration(Integer.valueOf((currentTime - snedTime)+""));

        accessLog.setCreateTime(new Date());
        //将sysLog对象持久化保存
        logger.info(accessLog.toString());
    }
}

上文中LoggerFactory.getLogger("ACCESS-LOG")获取一个日志配置中的Logger的名字,用于打印日志输出,持久化到日志文件里面。"ACCESS-LOG"的日志Logger定义我们下文中介绍。

四、拦截器注册

拦截器注册就不细讲了,前面的章节已经介绍过,回看。

@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
    
    

    //设置排除路径,spring boot 2.*,注意排除掉静态资源的路径,不然静态资源无法访问
    private final String[] excludePath = {
    
    "/static"};

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        registry.addInterceptor(new AccessLogInterceptor()).addPathPatterns("/**").excludePathPatterns(excludePath);
    }
}

五、获取ip访问地址的工具类

上面的代码中涉及到一个工具类,用于获取请求客户端的ip。代码如下:

public class AdrressIpUtils {
    
    
    /**
     * 获取Ip地址
     * @param request HttpServletRequest
     * @return ip
     */
    public static String getIpAdrress(HttpServletRequest request) {
    
    
        String Xip = request.getHeader("X-Real-IP");
        String XFor = request.getHeader("X-Forwarded-For");
        if(StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
    
    
            //多次反向代理后会有多个ip值,第一个ip才是真实ip
            int index = XFor.indexOf(",");
            if(index != -1){
    
    
                return XFor.substring(0,index);
            }else{
    
    
                return XFor;
            }
        }
        XFor = Xip;
        if(StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)){
    
    
            return XFor;
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
    
    
            XFor = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
    
    
            XFor = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
    
    
            XFor = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
    
    
            XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
    
    
            XFor = request.getRemoteAddr();
        }
        return XFor;
    }
}

六、"ACCESS-LOG"的日志Logger定义

下文中的配置参考以Log4J2配置为例(参考上一节的内容进行学习)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y0r1Nn7P-1644633419822)(images/screenshot_1598061650386.png)]

  • LoggerFactory.getLogger("ACCESS-LOG") 代码去配置文件里面找一个name为ACCESS-LOG的Logger配置。
  • 该Logger是一个AsyncLogger,指向的输出目标是ACCESS-APPENDER
  • ACCESS-APPENDER是一个日志文件输出配置,日志文件是access-log.log
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <properties>
        <!--日志输出位置-->
        <property name="LOG_HOME">D:/logs</property>
    </properties>
    <Appenders>
        <!-- 将日志输出到文件-->
        <RollingFile name="ACCESS-APPENDER"
                     fileName="${LOG_HOME}/access.log"
                     filePattern="${LOG_HOME}/access-%d{yyyy-MM-dd}-%i.log">
            <!--设置日志格式-->
            <PatternLayout>
                <pattern>[%d][%p][%t][%C] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <SizeBasedTriggeringPolicy size="100MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置最大存档数-->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <AsyncLogger name="ACCESS-LOG" level="debug" additivity="false">
            <AppenderRef ref="ACCESS-APPENDER" level="info"/>
        </AsyncLogger>
    </Loggers>
</configuration>

猜你喜欢

转载自blog.csdn.net/hanxiaotongtong/article/details/122893220