前言
先上两张图大家体验一下
看完是不是感觉自己心中想的不太一样,这里所统计的数据确实不够多,但是对于一个单结构工程项目来说完全已经够了,可能还差了报警功能,比如异常的时候发送报警邮件或者短信之类的,这些在搞懂触发点之后不是简简单单?
这里直介绍数据源的获取,数据这块的可视化就不用我说了吧
介绍
1.先说一下为什么会采用AOP,其实拦截器也可以实现啊,主要是因为AOP的优先级比拦截器低,在库表建好后,一份文件即插即用,不用考虑其他拦截器处理问题
2.数据源:这边统计的都是常规数据,一个日志主表一个错误日志附表
sys_log_info主表如下:
sys_info_error如下:
代码实现
WebLogControl如下:
@Aspect
@Component
public class WebLogControl {
private static final Logger logger = LoggerFactory.getLogger(WebLogControl.class);
@Autowired
private SysLogInfoMapper sysLogInfoMapper;
@Autowired
private SysLogErrorMapper sysLogErrorMapper;
@Pointcut("execution(public * com.inet.fitTrack.controller.*.*(..))")
public void webLog() {
}
@Around("webLog()")
public Object around(ProceedingJoinPoint point) throws Throwable {
//开始时间
long start = System.currentTimeMillis();
//执行方法
Object result = point.proceed();
long end = System.currentTimeMillis();
logger.info(" SPEND TIME : " + (end-start)+"ms");
SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
sysLogInfo.setRequestTime(Integer.valueOf(String.valueOf((end-start))));
sysLogInfoMapper.updateByPrimaryKeySelective(sysLogInfo);
return result;
}
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 1.接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
// 2.获取请求的方法所在类名
String beanName = joinPoint.getSignature().getDeclaringTypeName(); //方法所在的类名
//3.获取请求的方法
Method method = signature.getMethod();
//4.获取注解参数值
LogAnnotation annotation = signature.getMethod().getAnnotation(LogAnnotation.class);
// 打印日志
logger.info(" TypeName : " + beanName);
logger.info(" MethodName : " + method.getName());
logger.info(" U R L : " + request.getServletPath());
logger.info("HTTP_METHOD : " + request.getMethod());
logger.info(" I P : " + request.getRemoteAddr());
// 获取具体的请求值转JSon输出日志 慎用!
// logger.info(" PARAM : " + GsonUtils.toJson(joinPoint.getArgs()));
SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
WebContext webContext = WebContext.getWebContext();
//下面进行日志填充
//这里的主键我采用的是 时间戳+6位随机数
sysLogInfo.setId(Long.valueOf(String.valueOf(System.currentTimeMillis())+String.valueOf(RandomUtil.randomNumberString(6))));
sysLogInfo.setLogType(Constant.LOG_INFO); //代表是正常请求 为1
sysLogInfo.setUserId(webContext.getUserId()==null ? null:Integer.valueOf(String.valueOf(webContext.getUserId())));
sysLogInfo.setUserName(webContext.getUserName()==null ? null:webContext.getUserName());
sysLogInfo.setHttpType(request.getMethod());
sysLogInfo.setRequestBeanName(beanName);
sysLogInfo.setRequestUrl(request.getServletPath());
sysLogInfo.setRequestIp(request.getRemoteAddr());
sysLogInfo.setRequestMethod(method.getName());
sysLogInfo.setUrlRemark(annotation==null ? null:annotation.requestRemark());
// 获取具体的请求值转JSon填充 慎用!
// sysLogInfo.setRequestParam(GsonUtils.toJson(joinPoint.getArgs()));
sysLogInfo.setCreateTime(new Date());
sysLogInfoMapper.insertSelective(sysLogInfo);
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
// 下面是将返回值转成自己的Vo类 来获取具体的返回码状态
if (ret instanceof ResultVO) {
ResultVO resultVO = (ResultVO)ret;
sysLogInfo.setRequestReturnStatus(resultVO.getStatus());
}else if (ret instanceof Response){
Response resultVO = (Response)ret;
sysLogInfo.setRequestReturnStatus(Integer.valueOf(resultVO.getStatus()));
}else {
sysLogInfo.setRequestReturnStatus(0000);
}
// 处理完请求,返回内容 返回值转json 可以获取具体返回的内容 慎用!
// logger.info("RESPONSE : " + GsonUtils.toJson(ret));
// sysLogInfo.setRequestReturnValue(GsonUtils.toJson(ret));
sysLogInfoMapper.updateByPrimaryKeySelective(sysLogInfo);
}
@AfterThrowing( pointcut = "webLog()",throwing = "e")
public void logThrowing(JoinPoint joinPoint, Throwable e){
logger.error("**************开始抛出异常***************");
logger.error("请求类方法:"+joinPoint.getSignature().getName());
logger.error("异常内容: "+getTrace(e));
logger.error("***************抛出异常结束***************");
SysLogInfoWithBLOBs sysLogInfo=SysLogInfoWithBLOBs.getWebContext();
//下面填充
sysLogInfo.setLogType(Constant.LOG_ERRO);//代表请求异常 为2
sysLogInfoMapper.updateByPrimaryKeySelective(sysLogInfo);
//插入主表后获取主表id 后插入error附表
SysLogErrorWithBLOBs sysLogError = new SysLogErrorWithBLOBs();
sysLogError.setCreateTime(new Date());
sysLogError.setLogId(sysLogInfo.getId()==null ? -1:sysLogInfo.getId());
sysLogError.setErrorContent(getTrace(e));
sysLogError.setErrorInfo(e.getMessage());
sysLogErrorMapper.insertSelective(sysLogError);
}
//异常信息转化 主要是获取异常的详细信息
private static String getTrace(Throwable t) {
StringWriter stringWriter= new StringWriter();
PrintWriter writer= new PrintWriter(stringWriter);
t.printStackTrace(writer);
StringBuffer buffer= stringWriter.getBuffer();
return buffer.toString();
}
}
自定义注解:LogAnnotation如下:
用来说明此接口是用来干嘛的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogAnnotation {
String requestRemark() ;
}
关于sys_log_info和sys_log_error这两个表对应的实体类和Mapper文件这里就不贴了,因为没有其他多余的操作,但有个点需要注意
sys_log_info表的实体类要采用TreadLoal存储,可以修改实体类构造方法如下:
为什么要用TreadLoal,因为
1.我们需要在AOP中多个方法内共用同一个实例,并在第一个方法执行后插入数据,在其他方法获取到相同的实例并填充数据后更新
2.防止并发问题,aop内的方法并不是串行的执行的,就是说并不是一个请求就所有方法执行一遍,有可能200个请求同时执行完doBefore方法后,才去执行doAfterReturning方法,所以每个请求产生的实例都需要保存。所以主键没有采用自增长
3.关于这里的并发问题,我暂时也没有想到更好的方法解决,只是暂时采用了TreadLoal
这里说一下很多人以为insert插入是不会有并发问题的,但是在某种情况下就会有,那就是在insert后需要获取到insert的id的时候。
传参和返回值说明:为什么说这两个地方需要慎用呢?因为传入的参数或者返回的参数有可能是图片或者文件流,这个时候转json就会异常
异常说明:当中途有异常发生的时候,会跳过doAfterReturning方法直接到logThrowing方法,所以发生异常的时候,返回值是没有写到库的。
这个时候可以通过全局异常处理来解决,这里以自定义异常为例,如果异常没有被捕捉到那就没办法了
总结
对于单结构工程,自我感觉这种已经够用了,如果是聚合工程那可能又得多一层处理,分布式那就别想了,这里只是日志监控,关于liunx系统的监控或者说是服务监控的话,可以提供一个思路那就采用snmp,通过old获取系统各种状态然后可视化,像内存、cpu、流入流出网速都是可以的