vlambda博客
学习文章列表

log4j2与拦截器实现统一访问日志


在上一篇讲解的Logback中我们没有引入maven依赖,那是因为spring-boot-starter-logging是SpringBoot默认集成的。


但是今天要讲解的log4j2则需要从spring-boot-starter-web中去掉spring-boot-starter-logging依赖并声明依赖包。


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

配置文件
在application.yml中进行配置
logging:
  configclasspath:log4j2-spring.xml

log4j2-spring.xml

在resources目录下新建一个log4j2-spring.xml文件:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <properties>
        <!--日志位置-->
        <property name="LOG_HOME">/Users/admin/Documents/log</property>
    </properties>

    <Appenders>
        <!-- 日志输出到控制台-->
        <Console name="CONSOLE" target="SYSTEM_OUT">
            <!--设置日志格式及颜色-->
            <PatternLayout
                    pattern="[%style{%d}{bright,green}][%highlight{%p}][%style{%t}{bright,blue}][%style{%C}{bright,yellow}]: %msg%n%style{%throwable}{red}"
                    disableAnsi="false" noConsoleNoAnsi="false"/>

        </Console>
        <!-- 将日志输出到文件-->
        <RollingFile name="FILE-APPENDER"
                     fileName="${LOG_HOME}/log4j2-demo.log"
                     filePattern="${LOG_HOME}/log4j2-demo-%d{yyyy-MM-dd}-%i.log">

            <!--设置日志格式-->
            <PatternLayout>
                <pattern>[%d][%p][%t][%C] %m%n</pattern>
            </PatternLayout>
            <Policies>
                <!-- 设置日志文件切分参数 -->
                <SizeBasedTriggeringPolicy size="100 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!--设置最大存档数-->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- 根日志设置 -->
        <Root level="debug">
            <AppenderRef ref="CONSOLE" level="debug"/>
            <AppenderRef ref="FILE-APPENDER" level="info"/>
        </Root>

        <!--spring日志-->
        <Logger name="org.springframework" level="info"/>
        <!-- mybatis日志 -->
        <Logger name="com.mybatis" level="warn"/>
    </Loggers>
</configuration>

设置日志格式时使用的占位符:
%d  date时间
%p  日志级别
%t   thread线程名称
%C  class类文件名称
%msg 日志信息
%n 换行
%style{%throwable}{red} 异常信息标红色

执行代码,在浏览器访问:

http://localhost:8888/test

log4j2与拦截器实现统一访问日志


异步日志配置

Log4j2是基于Disruptor(一个开源的无锁并发框架),具有超高的吞吐量和低延迟,所以如果想提升Log4j2的性能,就需要引入Disruptor开启异步记录日志功能。

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.3.6</version>
</dependency>

SpringBoot配置异步日志的方式有两种,一种是在应用启动类中使用System.setProperty,另一种是启动参数来设置全局异步日志。

启动类配置

package com.javafamily.familydemo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@SpringBootApplication
@MapperScan(basePackages = {"com.javafamily.familydemo.mapper"})
// 扫描Servlet主键(监听器是Servlet主键的一种)
@ServletComponentScan
public class FamilyDemoApplication {
    public static void main(String[] args) {

        System.setProperty("Log4jContextSelector",
                "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");

        SpringApplication.run(FamilyDemoApplication.class, args);
    }
}
System.setProperty语句使Log4j2日志输出使用异步处理,减小输出日志对性能的影响。


启动参数设置全局异步日志

java -jar -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector  javafamily.jar

通过以上两种中任意一种方法配置好以后,在Logcontroller.java中打好断点,执行代码,在浏览器中访问:

http://localhost:8888/test

log4j2与拦截器实现统一访问日志

当我们看见Log4j2-TF-1-AsyncLogger线程的时候,说明我们的全局异步日志配置成功了。


全局异步模式虽然是性能最好的日志输出方式,但是消耗主机的资源很大,对服务器的要求比较高,如果服务器比较一般还想要好的性能,我们还可以使用同步/异步混合模式。

更改log4j2-spring.xml中部分代码:

<Loggers>
    <!-- 针对com.javafamily.familydemo包下面的日志采用异步日志 -->
    <AsyncLogger name="com.javafamily.familydemo" level="debug" additivity="false">
        <AppenderRef ref="CONSOLE" level="debug"/>
        <AppenderRef ref="FILE-APPENDER" level="info"/>
    </AsyncLogger>

    <!-- 系统默认日志设置 -->
    <Root level="debug">
        <AppenderRef ref="CONSOLE" level="debug"/>
        <AppenderRef ref="FILE-APPENDER" level="info"/>
    </Root>
</Loggers>

拦截器实现统一访问日志

有时我们需要针对系统的每一次接口访问,记录用户名、访问时间、耗时长、使用什么方法访问的、访问结果如何等。这种做法被称为审计日志。


首先在model文件夹下创建Log.java:

package com.javafamily.familydemo.model;

import lombok.Data;
import java.util.Date;

@Data
public class Log {
    // 访问者用户名
    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;
}

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

package com.javafamily.familydemo.config;

import com.javafamily.familydemo.model.Log;
import com.javafamily.familydemo.utils.AdrressIpUtils;
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 LogInterceptor implements HandlerInterceptor {
    // 开始时间
    private static final String LOGGER_SEND_TIME = "SEND_TIME";
    // 日志实体标识
    private static final String LOGGER_LOG = "ENTITY";


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

    /**
     * 进入SpringMVC的Controller之前开始记录日志实体
     */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception 
{

        // 创建日志实体
        Log log = new Log();

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

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

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

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

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


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

        // 获取本次请求日志实体
        Log log = (Log) request.getAttribute(LOGGER_LOG);

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

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

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

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


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

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

之后在util文件夹中编写工具类AdrressIpUtils.java

package com.javafamily.familydemo.utils;

import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;

public class AdrressIpUtils {
    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;
    }
}

对之前编写好的MyWebMvcConfigurer.java进行改写:

package com.javafamily.familydemo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {


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

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

最后在resources文件夹下创建log.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <properties>
        <!--日志输出位置-->
        <property name="LOG_HOME">/Users/admin/Documents/log</property>
    </properties>
    <Appenders>
        <!-- 将日志输出到文件-->
        <RollingFile name="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="LOG" level="debug" additivity="false">
            <AppenderRef ref="APPENDER" level="info"/>
        </AsyncLogger>
    </Loggers>
</configuration>

执行代码,在浏览器访问:

http://localhost:8888/test



得到最终的结果。

点击下方阅读原文,查看上一篇