前言
最近因为疫情,不得不在家办公,而且也赶上了项目处于内测阶段,测试期间如果有问题,不能及时把问题复现出来。于是我在网上搜索有没有一些工具能对接口进行一个实时的监控,出现问题,立马通知相关人员。项目虽然有日志信息,但是因为项目是在集群中部署,日志信息,需要进入服务器的控制台中查看,比较麻烦。
忙活了一天,装了不少用来测试接口的框架,他们在介绍的时候,说的是对接口监控,其实是在不断的调用接口,来达到监控的效果,而且用的时候,需要准备很多工作,创建项目,创建测试用例等。
就在我没有思路的时候,看到一篇博客,讲的是,接口监控的原理都是在异常处理中进行的。
实现
注解+异常信息的处理+发送邮件
注解
该注解只是为了获取接口的创建者或者维护者的邮箱
1 2 3 4 5
| @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Notice { String value(); }
|
使用:创建注解之后,只需要在接口方法上添加@Notice即可,里面的内容为邮箱
1 2 3 4 5 6 7 8 9
| @ApiOperation(value = "获取上传凭证", notes = " \n author:ZhuGuangLiang") @AnonymousGetMapping("/upload") @Notice("786945363@qq.com") public Result<Object> upload(Integer type) { if (type == null) { throw new BadRequestException("文件种类为空"); } return Result.success(fileUtils.upload(type)); }
|
异常处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| import cn.hutool.core.util.ObjectUtil; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import marchsoft.annotation.Notice; import marchsoft.modules.spiritdeerpush.common.utils.email.EmailUtils; import marchsoft.utils.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects;
@Component @Aspect @Slf4j public class MyException { @Autowired private EmailUtils emailUtils;
@Value("${notice.admin}") private String adminEmail;
@Value("${notice.open}") private int on;
@Around("execution(* com.modules.xxxx.*.controller..*.*(..))") public Object around(ProceedingJoinPoint pj) throws Throwable { long starttime = System.currentTimeMillis(); try {
return pj.proceed(); } catch (Exception e) { if (on == 1) { concatError(starttime, getMessage(pj, e), pj); } throw e; } }
private void concatError(long starttime, String message, ProceedingJoinPoint pj) { StringBuilder stringBuilder = new StringBuilder("<h3>接口耗时:</h3>"); stringBuilder.append("接口花费时间:").append(System.currentTimeMillis() - starttime).append("ms<br/>"); stringBuilder.append(message); MethodSignature signature = (MethodSignature) pj.getSignature(); Method method = signature.getMethod(); Notice toNotice = method.getAnnotation(Notice.class); String to = adminEmail; if (ObjectUtil.isNotNull(toNotice) && StringUtils.isNotBlank(toNotice.value())) { to = toNotice.value(); } sendErrorNotice(stringBuilder.toString(), to); }
private String getMessage(ProceedingJoinPoint pj, Exception e) { if (e != null) { log.error(e.getMessage(), e); } RequestAttributes ra = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes sra = (ServletRequestAttributes) ra; HttpServletRequest request = sra.getRequest(); MethodSignature signature = (MethodSignature) pj.getSignature(); Method method = signature.getMethod(); ApiOperation toNotice = method.getAnnotation(ApiOperation.class); String charger = "无"; if (ObjectUtil.isNotNull(toNotice) && StringUtils.isNotBlank(toNotice.notes())) { charger = toNotice.notes().trim(); charger = charger.replace("\\n", ""); } StringBuilder joiner = new StringBuilder("<h3>负责人:</h3>") .append(charger) .append(";<br/><h3>接口地址:</h3>") .append(request.getRequestURI()) .append(";<br/><h3>类名:</h3>") .append(pj.getTarget().getClass().getSimpleName()) .append(";<br/><h3>方法:</h3>") .append(pj.getSignature().getName()); Object[] args = pj.getArgs(); List<Object> objects = new ArrayList<>(); for (Object object : args) { if (object instanceof MultipartFile || object instanceof File) { continue; } objects.add(object); } if (ObjectUtil.isNotNull(objects) && !objects.isEmpty()) { joiner.append(Arrays.toString(objects.toArray())); } if (Objects.nonNull(e)) { joiner.append(";<br/><h3>message:</h3>") .append(e.getMessage()) .append(";<br/><h3>异常:</h3>") .append(e.fillInStackTrace()); } return joiner.toString(); }
private void sendErrorNotice(String content, String to) { emailUtils.sendException(to, content); } }
|
发送邮件
可以自行百度关于发送的邮件的配置,这里就不一一陈述了。
最后
这个功能其实还是很简单,也有一定的局限性,比如没有包含具体报错的行数,只包含的方法和参数的信息,方便复现问题吧。在接口监控方面其实也有一些现成的框架,可以实现更加详细的报告,我还是个菜鸟,继续加油学习↖(^ω^)↗。