一、需求
我们本节要实现的需求
- 针对当前系统的每一次接口访问,要记录是什么人访问的(用户名)、什么时间访问的、访问耗时多长时间、使用什么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>