1、定义一个日志注解,把模块的接口调用日志储存到数据库中。
2、后续可能会产生性能问题,但对于当前快速扩张的业务而言,这种过渡性的功能,还是可以接受的。
用法:
一、自定义注解对象
package com.pkg.modelname.anno;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogOperation {
String value() default "";
}
二、自定义切面处理类
@Aspect
@Component
@Slf4j
public class OperationAspect {
@Resource
ApiAopSaveComp logUtil;
@Pointcut("@annotation(com.pkg.modelname.anno.LogOperation)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
return logUtil.onAround(point);
}
}
三、日志保存组件
@Component
@Slf4j
@Setter
@Getter
public class ApiAopSaveComp {
/**
* 日志默认模块名
*/
private String modelName = "def-model";
@Resource
ApiLogService apiLogService;
/**
* 默认日志保存
*/
private InsertLog defInsert = (joinPoint, time, status, result) -> {
try {
// 默认保存日志
saveLogDef(joinPoint, time, status, result);
} catch (NoSuchMethodException | IOException e) {
log.error("保存日志异常: ", e);
}
};
/**
* 保存日志
* 重设此对象以实现自己的保存日志方法
*/
private InsertLog insertLog = defInsert;
/**
* 环切
*
* @param point 切入点
* @return 返回
* @throws Throwable 错误
*/
public Object onAround(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result;
try {
// 执行方法
result = point.proceed();
// 保存日志
onSuccess(point, result, beginTime);
return result;
} catch (Exception e) {
// 执行时长(毫秒)
long cost = cost(beginTime);
// 保存日志 返回异常
saveLog(point, cost, false, ExceptionUtils.getStackTrace(e));
throw e;
}
}
/**
* 成功的日志
*
* @param point 切入点
* @param result 返回结果
* @param beginTime 开始时间
*/
private void onSuccess(ProceedingJoinPoint point, Object result, long beginTime) {
try {
// 执行时长(毫秒)
long cost = cost(beginTime);
// 返回
String data = "";
if (null != result) {
data = result.toString();
}
// 保存日志
saveLog(point, cost, true, data);
} catch (Exception e) {
log.error("保存日志异常", e);
}
}
/**
* 计算耗时
*
* @param beginTime 开始时间
* @return 间隔时间
*/
private long cost(long beginTime) {
return System.currentTimeMillis() - beginTime;
}
/**
* 保存日志
*
* @param joinPoint 切入点
* @param time 耗时
* @param status 请求状态
* @param result 返回参数
*/
private void saveLog(ProceedingJoinPoint joinPoint, long time, boolean status, String result) {
try {
insertLog.insertLog(joinPoint, time, status, result);
} catch (Exception e) {
log.error("保存日志失败", e);
}
}
/**
* 默认的保存日志方法
*
* @param joinPoint 切入点
* @param time 耗时
* @param status 请求状态
* @param result 返回参数
* @throws NoSuchMethodException 异常
*/
private void saveLogDef(ProceedingJoinPoint joinPoint, long time, boolean status, String result) throws NoSuchMethodException, IOException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(), signature.getParameterTypes());
LogOperation annotation = method.getAnnotation(LogOperation.class);
// 组装日志
WebApiLogEntity entity = new WebApiLogEntity();
entity.setLogTime(new Date());
entity.setModuleName(modelName);
if (annotation != null) {
//注解上的描述
entity.setApi(annotation.value());
}
// 耗时
entity.setCostTime(time);
// 状态
entity.setStatus(status);
//请求相关信息
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
if (null != request) {
entity.setIpAddr(HttpContextUtils.getRealIp(request));
String header = HttpContextUtils.getAllHeader(request);
entity.setHeader(limitLen(header));
entity.setRequestUri(limitLen(request.getRequestURI()));
entity.setMethod(request.getMethod());
}
// 请求参数
try {
String args = getArgs(joinPoint);
entity.setArgs(limitLen(args, 2048));
log.info("args : {}", args);
} catch (Exception e) {
log.error("参数序列错误:", e);
e.printStackTrace();
}
// 返回参数
if (null != result) {
entity.setResult(limitLen(result, 2048));
log.info("result : {}", result);
}
apiLogService.saveLog(entity);
}
/**
* 限制长度
*
* @param header 头部
* @return 限制后的长度
*/
private static String limitLen(String header) {
int maxLen = 500;
return limitLen(header, maxLen);
}
/**
* 限制长度
*
* @param str 字符
* @param len 长度,不包含
* @return 限制后的长度
*/
private static String limitLen(String str, int len) {
if (null == str) {
return null;
}
return str.length() > len ? str.substring(0, len) : str;
}
/**
* 获取请求参数
*
* @param point 切入点
* @return 参数
*/
public static String getArgs(ProceedingJoinPoint point) {
Object[] args = point.getArgs();
if (null != args && args.length > 0) {
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < args.length; i++) {
map.put(i, args[i].toString());
}
return new Gson().toJson(map);
}
return null;
}
}
日志打印接口
默认实现一个接口,支持其它模块再次实现,提升扩展性
public interface InsertLog {
/**
* 插入日志
*
* @param joinPoint 切入点
* @param time 耗时
* @param status 请求结果状态
* @param result 返回参数
*/
void insertLog(ProceedingJoinPoint joinPoint, long time, boolean status, String result);
}
日志储存实体对象
@Data
@NoArgsConstructor
@ToString
@TableName("tb_web_api_log")
public class WebApiLogEntity {
@TableId(type= IdType.AUTO)
private Long id;
private Date logTime;
/**
* 模块名
*/
private String moduleName;
private String api;
/**
* 耗时
*/
private Long costTime;
private boolean status;
private String ipAddr;
private String header;
private String requestUri;
private String method;
private String args;
private String result;
}
实体表
SQL代码
CREATE TABLE `tb_web_api_log` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`log_time` datetime NULL DEFAULT NULL,
`module_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`api` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`cost_time` int(11) NULL DEFAULT NULL,
`status` tinyint(4) NULL DEFAULT NULL,
`ip_addr` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`header` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`request_uri` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`args` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`result` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'api日志表' ROW_FORMAT = Compact;
图例:
使用到的第三方包
ExceptionUtils.getStackTrace(e)
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
HTTP上下文工具类
public class HttpContextUtils {
/**
* 获取当前请求
* @return 请求
*/
public static HttpServletRequest getHttpServletRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return null;
}
return ((ServletRequestAttributes) requestAttributes).getRequest();
}
/**
* 获取真正的ip
* @param request 请求
* @return
*/
public static String getRealIp(HttpServletRequest request) {
//
String ip = request.getHeader("X-Real-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 处理多IP的情况(只取第一个IP)
if (ip != null && ip.contains(",")) {
String[] ipArray = ip.split(",");
ip = ipArray[0];
}
return ip;
}
/**
* 获取所有头
*
* @param request 请求
* @return 所有请求头
*/
public static String getAllHeader(HttpServletRequest request) {
Enumeration<String> eHeaders = request.getHeaderNames();
Map<String,String> map =new HashMap<>();
while (eHeaders.hasMoreElements()){
String name = eHeaders.nextElement();
String val = request.getHeader(name);
map.put(name,val);
}
return JSONObject.toJSONString(map);
}
}