对于工程的开发,必然会伴随着各种bug,工程量越大,出现bug的频率也会越高。一般对于代码量较小的工程来说,一个人可能就足够去做开发与维护;但是对于代码量较大的工程往往是需要一个小团队协作开发。当工程基本完成,开始部署测试环境或者生产环境时,这些环境并不能像开发环境一样能快速的调试与维护,线上的工程一旦出现异常时,开发团队就需要主动感知异常并协调处理,当然人不能一天24小时去盯着线上工程,所以就需要一种机制来自动化的对异常进行通知,并精确到谁负责的那块代码。这样会极大地方便后续的运维。因此,本项目的团队版上线
JDK1.8+、maven3+、springboot2+
0.4.1-personal
mvn clean install
打包到本地仓库中。pom.xml
中做如下依赖 <dependency>
<groupId>com.kuding</groupId>
<artifactId>prometheus-spring-boot-starter</artifactId>
<version>0.4.1-personal</version>
</dependency>
application.properties
或者application.yml
中做如下的配置:(至于以上的配置说明后面的章节会讲到)exceptionnotice:
open-notice: true
project-enviroment: develop
included-trace-package:你的工程目录
dingding:
phone-num: 手机号(数组)
web-hook: 钉钉的webhook
@Component
@ExceptionListener // 异常通知的监控来自这个注解
public class NoticeComponents {
public void someMethod(String name) {
System.out.println("这是一个参数:" + name);
throw new NullPointerException("第一个异常");
}
public void anotherMethod(String name, int age) {
System.out.println("这又是一个参数" + age);
throw new IllegalArgumentException(name + ":" + age);
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
private NoticeComponents noticeComponents;
@Test
public void contextLoads() {
noticeComponents.someMethod("hello");
}
}
当运行单元测试后,假如钉钉配置没有问题的话,你的钉钉中就会出现如下类似的消息:
假如在你配置的钉钉中出现类似这个信息的化,恭喜你,你成功的产生了一个异常通知。
综上,一个最简单的例子就完成了。
本框架遵循spring boot starter的自动化配置规范而开发的自动化异常通知框架,在原有的单人版基础上进行了多处改进并升级成团队版,整体业务流程如下:
本框架配置主要分为4部分:
exceptionnotice:
open-notice: true
project-name: XXX
included-trace-package: com.kuding
listen-type: common
notice-type: dingding
project-enviroment: develop
dingding:
phone-num: XXXX
web-hook: https://oapi.dingtalk.com/robot/send?access_token=XXXX
sign-secret: XXXX
enable-signature-check: true
dingding-text-type: markdown
include-header-name:
- name
- tenantId
exclude-exceptions:
- java.lang.IllegalArgumentException
strategy:
frequency-type: showcount
notice-show-count: 10
enabled: true
store:
enable-redis-storage: false
redis-key: XXX
email:
bcc:
- XXX
cc:
- XXX
email-text-type: text
to:
- XXX
enable-async-notice: false
async:
thread-name-prefix: prometheus-task-
core-pool-size: 1
max-pool-size: 100
daemon: false
queue-capacity: 100
名称 | 参数类型 | 说明 | 必要配置 |
---|---|---|---|
全局配置 | |||
open-notice | boolean | 用于开启异常通知(必填) | 是 |
project-name | string | 一般忽略,以spring.application.name替代 | 否 |
included-trace-package | string | 异常追踪的包路径,一般情况下,此配置项就是配置你工程的包路径就可以了 | 是 |
listen-type | enum | 监听类型,有两种:common/web-mvc,默认为common | 是 |
notice-type | enum | 通知类型,有两种:dingding/email | 是 |
project-enviroment | enum | 表示此工程环境,用于标注是哪个环境中的工程出异常 | 是 |
exclude-exceptions | list | 排除异常,表示这些异常不需要进行异常通知 | 否 |
钉钉配置(dingding.) | |||
phone-num | list | 通知人的手机号(可以多个) | 是 |
web-hook | string | 钉钉机器人链接 | 是 |
enable-signature-check | boolean | 是否开始验签验证 | 否 |
sign-secret | string | 验签秘钥,开启验签验证后必填 | 否 |
dingding-text-type | enum | 钉钉通知的文本类型:text/markdown,默认为text | 否 |
邮件配置(email.) | |||
bcc | list | 秘密抄送人的邮箱 | 否 |
cc | list | 抄送人的邮箱 | 否 |
to | list | 发送人邮箱 | 是 |
email-text-type | enum | 邮件通知的文本类型(只有text,没做扩展) | 否 |
web-mvc限定 | |||
include-header-name | list | 异常通知中需要包含的header信息(不写全部返回) | 否 |
异步配置(async.) | |||
enbaled | boolean | 是否开启异步通知(默认否) | 否 |
thread-name-prefix | string | 线程名称前缀 | 否 |
core-pool-size | int | 核心池数量 | 否 |
max-pool-size | int | 最大线程池数量 | 否 |
daemon | boolean | 是否需要守护线程 | 否 |
策略配置(strategy) | |||
enabled | boolean | 是否开启通知策略(默认否) | 否 |
frequency-type | enum | 通知频率策略类型:showcount/timeout | 是 |
notice-show-count | int | 当策略类型为showcount时,表示距上次通知再出钱多少次需要再次通知 | 否 |
notice-time-interval | duration | 当策略类型为timeout是,表示距上次通知经过多长时间后再次通知 | 否 |
通知存储(store.) | |||
enable-redis-storage | boolean | 是否开启redis存储(默认否) | 否 |
redis-key | string | (开启redis限定)redis存储的键值 | 否 |
exceptionnotice.listen-type
,此配置表示工程的监听方式,目前有两种监听方式:普通监听(common);mvc监听(web-mvc) 。这两种监听方式各有千秋,普通监听方式主要运用aop的方式对有注解的方法或类进行监听,可以加在任何类与方法上。mvc监听只能对controller层进行监听,对其它层无效,不过异常通知的信息更丰富,不仅仅包括了普通监听的所有信息(不包含参数),还包含了请求中的路径信息(path)、参数信息(param)、请求中的请求体信息(body)和请求体中的头信息(header)。例如:@RestController
@RequestMapping("/demo")
public class DemoController {
@Autowired
private DingdingNoticeService dingdingNoticeService;
@Autowired
private CustomService customService;
@PostMapping("/dingding1/{pathParam}")
@ExceptionListener
public String dingding1(@PathVariable @ApiParam(value = "来个路径参数", required = true) String pathParam,
@RequestParam @ApiParam(value = "来个参数", required = true) String param,
@RequestHeader @ApiParam(value = "来个请求头", required = true) String headParam,
@RequestBody @ApiParam(value = "来个请求体", required = true) String body) {
dingdingNoticeService.dinding1();
return "Keep on going never give up.";
}
...more code
具体效果如下:
exceptionnotice.dingding-text-type=markdown
即可,最终你可以得到以下面类似的效果: exceptionnotice.exclude-exceptions=java.lang.IllegalArgumentException,com.yourpackage.YourLogicException
exceptionnotice.enable-redis-storage=true
exceptionnotice.redis-key=你自己的redis键
这里开启redis存储需要依赖spring-boot-starter-data-redis,需要用户自行配置,不过目前redis的存储不建议使用,后续我会完善异常持久化的流程
exceptionnotice.async.enabled=true
,开启异常配置需要配置一个自有的线程池,具体配置如下:exceptionnotice:
async:
core-pool-size: 1
max-pool-size: 100
daemon: false
queue-capacity: 50
thread-name-prefix: notice-
以上配置实际上对应着spring中的ThreadPoolTaskExecutor
一部分配置,配置的东西也比较简单,要是有具体的其他特殊的线程配置请留言提建议
exceptionnotice:
dingding:
phone-num: 手机号(数组)
web-hook: 钉钉的web钩子
enable-signature-check: 是否开启签名验证(true/false)
sign-secret: 签名秘钥
...........
开启签名验证时,需要在钉钉机器人的安全设置中选中加签设置:
spring-boot-starter-mail
及其配置spring:
mail:
host: smtp.xxx.com
port: 25
username: 开启smtp权限的邮箱用户名
password: 密码
exceptionnotice:
email:
to: 给谁发
cc: 抄送给谁
bcc: 秘密抄送给谁
一般而言,一个方法出现异常信息,意味着每当同样的方式进行调用时都会抛出相同的异常方法,放任不管的话,钉钉异常通知与邮件异常通知会重复的收到同一个异常,所以为了限制发送频率,默认情况下,某个方法出现的异常需要通知,那么这条通知每天只会出现一次。当然这样也可能会出现问题,假如邮件或钉钉信息没有收到,很可能会漏掉此通知,所以这里创造了一个通知的策略。
ExceptionNoticeFrequencyStrategy
,开启策略需要在application.properties
中加入如下配置exceptionnotice.strategy.enabled=true
对应的配置为:
exceptionnotice.strategy.frequency-type=timeout/showcount
exceptionnotice.strategy.notice-time-interval
,类型为duration,表示的是自上次通知时间起,超过了此配置的时间后,便会再次通知。exceptionnotice.strategy.notice-show-count
,表示的是自上次通知起,出现次数超过了此配置测次数后,便会再次通知基本上以上策略足够使用,假如说还有更好的配置策略可以连系我
目前需要外援配置的信息有以下几个,其实上面也提到了
pom.xml
中加入如下依赖 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
加入依赖后需要开始配置redis
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
password: 密码
pom.xml
中加入如下依赖 <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
加入依赖后开始配置邮件信息
spring:
mail:
host: smtp.xxx.com
port: 25
username: 开启smtp权限的邮箱用户名
password: 密码
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
@ExceptionListener
,需要注意的是,凡是你需要异常通知的类或方法上必须加此注解。exceptionnotice.listen-type
的不同配置注解的位置也是不一样的exceptionnotice.listen-type=common
时@ExceptionListener
可以加到任意类上,任意方法上exceptionnotice.listen-type=web-mvc
时,@ExceptionListener
只能加在Controller层即带有@Controller
或@RestController
的类或者方法上,方法上也需要有对应的@RequestMapping
相关的注解INoticeSendComponent
进行消息发送:public interface INoticeSendComponent {
public void send(String blamedFor, ExceptionNotice exceptionNotice);
public Collection<String> getAllBuddies();
}
其中主要的方法为public void send(String blamedFor, ExceptionNotice exceptionNotice)
表示为我的异常通知(ExceptionNotice)需要发给谁(blameFor)。public Collection<String> getAllBuddies()
表示那些buddy需要用这个消息通知组件进行消息发送:
@Component
public class MySendExceptionComponent implements INoticeSendComponent {
private List<String> allBoddies = Arrays.asList("Tom", "Jerry");
private final Log logger = LogFactory.getLog(getClass());
@Override
public void send(String blamedFor, ExceptionNotice exceptionNotice) {
logger.error(blamedFor + "-->" + exceptionNotice.getExceptionMessage());
}
@Override
public Collection<String> getAllBuddies() {
return allBoddies;
}
}
配置好以后需要加入到现有的配置体系中,所以需要增加一个spring的config配置并实现ExceptionSendComponentConfigure
接口:
@Configuration
public class CustomSendComponentConfig implements ExceptionSendComponentConfigure {
@Autowired
private MySendExceptionComponent mySendExceptionComponent;
@Override
public void addSendComponent(ExceptionHandler exceptionHandler) {
exceptionHandler.registerNoticeSendComponent(mySendExceptionComponent);
}
}
这样,自定义的消息通知系统就可以通过@ExceptionListener
注解来使用了
@PutMapping("/custom")
@ExceptionListener("Tom")
public String custom() {
customService.custom();
return "Whatever is worth doing is worth doing well.";
}
//2020-01-03 20:30:46.480 ERROR 1576 --- [ometheus-task-1] c.h.components.MySendExceptionComponent : Tom-->[java.lang.NullPointerException:自定义通知第一个]
ExceptionNoticeResolverFactory
进行管理的,通过管理ExceptionNoticeResolver
来输出不同的文本。所以需要自己的异常消息配置需要实现实现ExceptionNoticeResolver
接口:@Component
public class MyExceptionNoticeResolver implements ExceptionNoticeResolver {
@Override
public String resolve(ExceptionNotice exceptionNotice) {
StringBuilder builder = new StringBuilder();
builder.append("人非圣贤,孰能无错?").append("\n");
exceptionNotice.getExceptionMessage().forEach(x -> builder.append(x).append("\n"));
return builder.toString();
}
}
然后将此bean交由ExceptionNoticeResolverFactory
进行管理,可以通过spring的config配置并实现ExceptionNoticeResolverConfigure
接口
@Configuration
public class MyExceptionTextResolverConfig implements ExceptionNoticeResolverConfigure {
@Autowired
private MyExceptionNoticeResolver myExceptionNoticeResolver;
@Override
public void addResolver(ExceptionNoticeResolverFactory factory) {
factory.addNoticeResolver("dingding", myExceptionNoticeResolver);
}
}
这里需要说明一下,ExceptionNoticeResolverFactory
中的方法public void addNoticeResolver(String resolveKey, ExceptionNoticeResolver resolver)
的resolveKey
表示的对ExceptionNoticeResolver
对象的唯一标识。框架提供的dingding通知与email通知有其默认的解析标识,分别为dingding
与email
,所以假如你有特殊的钉钉通知或email通知需要,可以通过上述的方式进行自定义。
作者:donald2008abc 来源:https://gitee.com/ITEater/prometheus-spring-boot-starter