前言
项目开发过程中,查看日志有利于更好的排查问题和收集访问数据,对产品有更大的帮助。
单回顾一下aop:
AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
一 、AOP的基本概念
-
Aspect(切面):通常是一个类,里面可以定义切入点和通知
-
JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
-
Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
-
Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
-
AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
下面我们简单使用下
二、增加pom jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<! -- 获取浏览器的基本信息 -->
<dependency>
<groupId>eu.bitwalker</groupId>
<artifactId>UserAgentUtils</artifactId>
<version>1.20</version>
</dependency>
三、编写LogAspect类
@Aspect
@Component
@Slf4j
public class LogAspect {
//mq记录日志,非必要
@Autowired
private AmqpTemplate rabbitTemplate;
/**
* 进入方法时间戳
*/
private Long startTime;
/**
* 方法结束时间戳(计时)
*/
private Long endTime;
public LogAspect() {
}
/**
* 定义请求日志切入点,其切入点表达式有多种匹配方式,这里是指定路径
*/
@Pointcut("execution(public * cn.fengpt..*.web.*.*(..))")
public void webLogPointcut() {
}
/**
* 在执行方法前后调用Advice,这是最常用的方法,相当于@Before和@AfterReturning全部做的事儿
* @param pjp
* @return
* @throws Throwable
*/
@Around("webLogPointcut()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(attributes != null ){
HttpServletRequest request = attributes.getRequest();
if(request != null ){
//获取请求头中的User-Agent
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
//打印请求的内容
String ip = getRemoteIpByServletRequest(request, false);
String url=request.getRequestURL().toString();
startTime = System.currentTimeMillis();
log.info("请求Url : {}" , url);
log.info("请求方式 : {}" , request.getMethod());
log.info("请求ip : {}" ,ip);
log.info("请求方法 : {}" , pjp.getSignature().getDeclaringTypeName() , "." , pjp.getSignature().getName());
log.info("请求参数 : {}" , Arrays.toString(pjp.getArgs()));
// 系统信息
log.info("浏览器:{}", userAgent.getBrowser().toString());
log.info("浏览器版本:{}",userAgent.getBrowserVersion());
log.info("操作系统: {}", userAgent.getOperatingSystem().toString());
// pjp.proceed():当我们执行完切面代码之后,还有继续处理业务相关的代码。proceed()方法会继续执行业务代码,并且其返回值,就是业务处理完成之后的返回值。
Object ret = pjp.proceed();
log.info("请求结束时间:"+ LocalDateTime.now());
log.info("请求耗时:{}" , (System.currentTimeMillis() - startTime));
// 处理完请求,返回内容
log.info("请求返回 : {}" , ret);
LogDTO logDTO=new LogDTO();
logDTO.setIp(ip);
logDTO.setBrowerVersion(userAgent.getBrowserVersion().getVersion());
logDTO.setOperatingSystem(userAgent.getOperatingSystem().toString());
logDTO.setBrowser(userAgent.getBrowser().toString());
logDTO.setUrl(url);
logDTO.setMethod(request.getMethod());
//JSON.toJSONString(logDTO)
//这里可以采用mq记录相关日志
rabbitTemplate.convertAndSend("log-message", JSON.toJSONString(logDTO));
return ret;
}
}
Object ret = pjp.proceed();
return ret;
}
/**
* 获取真实ip
*
* @param request HttpServletRequest
* @param acceptInnerIp 是否可以返回内网ip
* @return 真实ip
*/
public static String getRemoteIpByServletRequest(HttpServletRequest request, boolean acceptInnerIp) {
String ip = request.getHeader("x-forwarded-for");
log.info("x-forwarded-for:{}" , ip);
log.info("Proxy-Client-IP:{}" , request.getHeader("Proxy-Client-IP"));
log.info("WL-Proxy-Client-IP:{}" , request.getHeader("WL-Proxy-Client-IP"));
log.info("HTTP_CLIENT_IP:{}" , request.getHeader("HTTP_CLIENT_IP"));
log.info("HTTP_X_FORWARDED_FOR:{}" , request.getHeader("HTTP_X_FORWARDED_FOR"));
log.info("X-Real-IP:{}" , request.getHeader("X-Real-IP"));
if (StringUtils.isNotBlank(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (isIpValid(ip)) {
return ip;
}
ip = request.getHeader("Proxy-Client-IP");
if (isIpValid(ip)) {
return ip;
}
ip = request.getHeader("WL-Proxy-Client-IP");
if (isIpValid(ip)) {
return ip;
}
ip = request.getHeader("HTTP_CLIENT_IP");
if (isIpValid(ip)) {
return ip;
}
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
if (isIpValid(ip)) {
return ip;
}
ip = request.getHeader("X-Real-IP");
if (isIpValid(ip)) {
return ip;
}
ip = request.getRemoteAddr();
return ip;
}
/**
* 判断是否有效
* @param ip ip
* @param acceptInnerIp 是否接受内网ip
* @return
*/
private static boolean isIpValid(String ip, boolean acceptInnerIp) {
return acceptInnerIp ? isIpValid(ip) : isIpValidAndNotPrivate(ip);
}
/**
* 仅仅判断ip是否有效
* @param ip
* @return
*/
private static boolean isIpValid(String ip) {
if (StringUtils.isBlank(ip)) {
return false;
}
String[] split = ip.split("\\.");
if (split.length != 4) {
return false;
}
try {
long first = Long.valueOf(split[0]);
long second = Long.valueOf(split[1]);
long third = Long.valueOf(split[2]);
long fourth = Long.valueOf(split[3]);
return first < 256 && first > 0
&& second < 256 && second >= 0
&& third < 256 && third >= 0
&& fourth < 256 && fourth >= 0;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 判断ip是否有效,并且不是内网ip
* @param ip
* @return
*/
private static boolean isIpValidAndNotPrivate(String ip) {
if (StringUtils.isBlank(ip)) {
return false;
}
String[] split = ip.split("\\.");
try {
long first = Long.valueOf(split[0]);
long second = Long.valueOf(split[1]);
long third = Long.valueOf(split[2]);
long fourth = Long.valueOf(split[3]);
if (first < 256 && first > 0
&& second < 256 && second >= 0
&& third < 256 && third >= 0
&& fourth < 256 && fourth >= 0) {
if (first == 10) {
return false;
}
if (first == 172 && (second >= 16 && second <= 31)) {
return false;
}
if (first == 192 && second == 168) {
return false;
}
return true;
}
return false;
} catch (NumberFormatException e) {
return false;
}
}
/*
*//**
* 前置通知:
* 1. 在执行目标方法之前执行,比如请求接口之前的登录验证;
* 2. 在前置通知中设置请求日志信息,如开始时间,请求参数,注解内容等
*
* @param joinPoint
* @throws Throwable
*//*
@Before("webLogPointcut()")
public void doBefore(JoinPoint joinPoint) {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取请求头中的User-Agent
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
//打印请求的内容
startTime = System.currentTimeMillis();
log.info("请求开始时间:{}" + LocalDateTime.now());
log.info("请求Url : {}" + request.getRequestURL().toString());
log.info("请求方式 : {}" + request.getMethod());
log.info("请求ip : {}" + request.getRemoteAddr());
log.info("请求方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
log.info("请求参数 : {}" + Arrays.toString(joinPoint.getArgs()));
// 系统信息
log.info("浏览器:{}", userAgent.getBrowser().toString());
log.info("浏览器版本:{}", userAgent.getBrowserVersion());
log.info("操作系统: {}", userAgent.getOperatingSystem());
}
*//**
* 返回通知:
* 1. 在目标方法正常结束之后执行
* 1. 在返回通知中补充请求日志信息,如返回时间,方法耗时,返回值,并且保存日志信息
*
* @param ret
* @throws Throwable
*//*
@AfterReturning(returning = "ret", pointcut = "webLogPointcut()")
public void doAfterReturning(Object ret) throws Throwable {
endTime = System.currentTimeMillis();
log.info("请求结束时间:{}" + LocalDateTime.now());
log.info("请求耗时:{}" + (endTime - startTime));
// 处理完请求,返回内容
log.info("请求返回 : {}" + ret);
}
*/
/**
* 异常通知:
* 1. 在目标方法非正常结束,发生异常或者抛出异常时执行
* 1. 在异常通知中设置异常信息,并将其保存
*
* @param throwable
*/
@AfterThrowing(value = "webLogPointcut()", throwing = "throwable")
public void doAfterThrowing(Throwable throwable) {
// 保存异常日志记录
log.error("发生异常时间:{}" + LocalDateTime.now());
log.error("抛出异常:{}" + throwable.getMessage());
}
}
四、我们编写接口测试
当然 ,我们也可以把日志记录到数据库,可以进一步分析。
评论区