log4j2与拦截器实现统一访问日志
<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>
logging:
config: classpath:log4j2-spring.xml
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);
}
}
启动参数设置全局异步日志
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
得到最终的结果。
点击下方阅读原文,查看上一篇