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>12345678910复制代码类型:[java]配置文件在application.yml中进行配置logging: config: classpath:log4j2-spring.xml12复制代码类型:[java]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>12345678910111213141516171819202122232425262728293031323334353637383940414243444546复制代码类型:[java]设置日志格式时使用的占位符:%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>12345复制代码类型:[java]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主键的一种)@ServletComponentScanpublic class FamilyDemoApplication { public static void main(String[] args) { System.setProperty("Log4jContextSelector", "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector"); SpringApplication.run(FamilyDemoApplication.class, args); }}1234567891011121314151617181920复制代码类型:[java]System.setProperty语句使Log4j2日志输出使用异步处理,减小输出日志对性能的影响。启动参数设置全局异步日志java -jar -DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector javafamily.jar1复制代码类型:[java]通过以上两种中任意一种方法配置好以后,在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>12345678910111213复制代码类型:[java]拦截器实现统一访问日志有时我们需要针对系统的每一次接口访问,记录用户名、访问时间、耗时长、使用什么方法访问的、访问结果如何等。这种做法被称为审计日志。首先在model文件夹下创建Log.java:package com.javafamily.familydemo.model;import lombok.Data;import java.util.Date;@Datapublic 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;}12345678910111213141516171819202122复制代码类型:[java]之后通过自定义拦截器的方式,记录审计日志: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()); }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778复制代码类型:[java]之后在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; }}1234567891011121314151617181920212223242526272829303132333435363738394041复制代码类型:[java]对之前编写好的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;@Configurationpublic 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); }}12345678910111213141516171819复制代码类型:[java]最后在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>12345678910111213141516171819202122232425262728293031复制代码类型:[java]执行代码,在浏览器访问:http://localhost:8888/test得到最终的结果。