基于OpenAPI(Swagger3)使用AOP技术,进行日志记录
文章目录
- 1.Swagger3和AOP结合的好处
- 2.Swagger需要做什么
- 3.AOP需要做什么
- 4.完整代码示例
1.Swagger3和AOP结合的好处
当我们使用接口,或者问题定位,我们需要知道这么几个比较常见的信息:
- 这个接口干嘛用的?
- 这个接口的请求地址,请求方式,请求参数,返回参数都是什么样?
- 这个接口的代码在哪里?
- 当这个接口出现问题时,当时的运行的参数是什么?
如果你的项目中用了Swagger,你大概也能看到如下这种在线的接口文档,我们可以看到接口模块的描述,具体接口的功能描述,接口地址,接口的请求参数,返回参数等。
那么我们在记录日志的时候,如果把上述的Swagger中的这些参数都能记录下来,那么对于我们的工作来说,不管是定位问题,还是做日志审计,都非常有帮助。
例如下述的日志所示,这样做有什么好处?
- 我们可以不修改/添加任何日志记录代码就能记录我们我们的接口(controller)的访问日志;
- 我们可以将接口的日志用统一的方式管理起来,方便做日志审计;
2.Swagger需要做什么
OpenAPI(Swagger3)首先需要对两处地方进行标记,一个是标记Controller类,一个是标记Controller的方法,即
使用@Api(tags = "")来标注这个类干嘛用的,
使用@ApiOperation(value = "")来标注这个方法干嘛用的
3.AOP需要做什么
- 找到我们需要进行切入的点,即我们需要切入的点是在Swagger中被@ApiOperation所修饰的方法
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
- 增强@ApiOperation所修饰的方法,也就是记录@ApiOperation方法的访问日志
logger.info("请求{}模块的[{}]服务start",获取@Api的描述说明,获取ApiOperation的描述说明);
logger.info("请求地址:{}",获取请求地址);
logger.info("请求方法:{}.{}",获取@Api修饰的类名, 获取ApiOperatio修饰的方法名);
logger.info("请求参数:{}",获取请求参数);
result = joinPoint.proceed();
logger.info("请求{}模块的{}服务end",获取@Api的描述说明,获取ApiOperation的描述说明);
//将日志进行持久化,比如保存到数据库,代码略
4.完整代码示例
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.cloud.simulation.utils.JsonUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
/**
* @description:基于swagger进行AOP日志记录
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2022年10月17日 下午4:58:54
*/
@Aspect
@SpringBootConfiguration
public class LogApiConfig {
private final Logger logger = LoggerFactory.getLogger(LogApiConfig.class);
/**
* @description:需要被切入的点是被@ApiOperation注解修饰的方法
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2022年10月17日 下午4:59:20
*/
@Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
public void pointcut() {
}
/**
* @description:进行日志切入
* @author:hutao
* @mail:hutao1@epri.sgcc.com.cn
* @date:2022年10月17日 下午5:00:09
*/
@Around("pointcut() && @annotation(apiOperation)")
public Object around(ProceedingJoinPoint joinPoint, ApiOperation apiOperation) {
Object result = null;
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//获取请求地址
HttpServletRequest request = attributes.getRequest();
String url = request.getRequestURL().toString();
//获取OpenAPI的类说明@Api
Class<?> controller = joinPoint.getThis().getClass();
Api annotation = controller.getAnnotation(Api.class);
String[] apiDes = annotation.tags();
//获取被调用的方法名
String methodName = joinPoint.getSignature().getName();
//获取OpenAPI的方法说明@ApiOperation
ApiOperation methodApiOperation = null;
String apiOperationDes = "";
MethodSignature ms = (MethodSignature) joinPoint.getSignature();
methodApiOperation = ms.getMethod().getDeclaredAnnotation(ApiOperation.class);
if (methodApiOperation != null) {
apiOperationDes = methodApiOperation.value();
}
logger.info("请求{}模块的[{}]服务start",apiDes,apiOperationDes);
logger.info("请求地址:{}",url);
logger.info("请求方法:{}.{}", joinPoint.getSignature().getDeclaringTypeName(), methodName);
logger.info("请求参数:{}",JsonUtils.javaBeanToString(joinPoint.getArgs()));//这里使用任意的Json转换工具即可
result = joinPoint.proceed();
logger.info("请求{}模块的{}服务end",apiDes,apiOperationDes);
} catch (Throwable e) {
logger.error("进行日志切入失败,失败原因:{}", e);
}
return result;
}
}