暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

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

我们家Java 2021-09-10
1562

点击上方蓝色我们家Java,选择“关注


在上一篇讲解的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是基于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-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



    得到最终的结果。

    点击下方阅读原文,查看上一篇
    文章转载自我们家Java,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论