SkyWalking监控告警

young 1,324 2022-07-04

SkyWalking在6.x版本中新增了告警功能,其核心在于config/alarm-settings.yaml文件中,该文件分为ruleswebhooks两部分。

rules用于定义告警的条件,webhook则用于定于告警触发时,需要通知哪些服务。

告警规则配置项的说明:

  • **Rule name:**规则名称,也是在告警信息中显示的唯一名称。必须以_rule结尾,前缀可自定义
  • **Metrics name:**度量名称,取值为oal脚本中的度量名,目前只支持long、double和int类型。详见 Official OAL script
  • **Include names:**该规则作用于哪些实体名称,比如服务名,终端名(可选,默认为全部)
  • **Exclude names:**该规则作不用于哪些实体名称,比如服务名,终端名(可选,默认为空)
  • **Threshold:**阈值
  • OP: 操作符,目前支持 >、<、=
  • **Period:**多久告警规则需要被核实一下。这是一个时间窗口,与后端部署环境时间相匹配
  • **Count:**在一个Period窗口中,如果values超过Threshold值(按op),达到Count值,需要发送警报
  • **Silence period:**在时间N中触发报警后,在TN -> TN + period这个阶段不告警。 默认情况下,它和Period一样,这意味着相同的告警(在同一个Metrics name拥有相同的Id)在同一个Period内只会触发一次
  • **message:**告警消息

webhook会在触发告警时,向配置的地址发送http POST请求,并将Content-Type为application/json,也就是说会发送json格式的POST请求。

webhooks发送的字段包含

  • **scopeId、scope:**所有可用的 Scope 详见 org.apache.skywalking.oap.server.core.source.DefaultScopeDefine
  • **name:**目标 Scope 的实体名称
  • **id0:**Scope 实体的 ID
  • **id1:**保留字段,目前暂未使用
  • **ruleName:**告警规则名称
  • **alarmMessage:**告警消息内容
  • **startTime:**告警时间,格式为时间戳
  • **tags:**alarm-settings.yml中配置的tags

具体字段类型可以参考官方定义的AlarmMessage

由于邮件系统之前的通讯方式都是基于MQ进行的,所以需要定义一个Controller,用于接收Skywalking的请求,官方给出的请求数据示例为

[{
"scopeId": 1,
"scope": "SERVICE",
"name": "serviceA",
"id0": "12",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "alarmMessage xxxx",
"startTime": 1560524171000,
"tags": [{
"key": "level",
"value": "WARNING"
}]
}, {
"scopeId": 1,
"scope": "SERVICE",
"name": "serviceB",
"id0": "23",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "alarmMessage yyy",
"startTime": 1560524171000,
"tags": [{
"key": "level",
"value": "CRITICAL"
}]
}]

所以可以定义接口为

@PostMapping("/skywalking/alarm")
public void alarm(@RequestBody List<SkyWalkingAlarmMessage> alarmList) {
......
......
}

接收到数据之后,如果不为空,就可以组装邮件发送给配置的相关开发或运维人员。

定义SkyWalking邮件的相关配置

@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "alarm")
public class AlarmMailConfig {
private AlarmMailInfo skyWalking;
@Data
public static class AlarmMailInfo{
/**
* 发件昵称
*/
private String nickName;
/**
* 收件地址
*/
private List<String> toAddress;
/**
* 是否转义告警字段
*/
private boolean translateField = true;
}
}

定义告警字段的中文映射

enum Mapping{
scopeId("scopeId"),
scope("scope"),
name("目标Scope的实体名称"),
id0("Scope实体的ID"),
id1("id1"),
ruleName("警告规则名称"),
alarmMessage("告警消息内容"),
startTime("告警时间"),
tags("tags")
;
private String fieldName;
Mapping(String fieldName) {
this.fieldName = fieldName;
}
private static final Map<String,String> cacheMap =
Arrays.stream(Mapping.values()).collect(Collectors.toMap(Enum::name, e->e.fieldName));
}

定义SkyWalking的处理Controller

@Slf4j
@RestController
public class AlarmMailController {
// 时间格式化pattern
public static final String TIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS";
// 发送邮件Service
@Autowired
private SendMailService sendMailService;
// 告警邮件配置
@Autowired
private AlarmMailConfig alarmMailConfig;
// 通过反射获取请求字段的字段名
private final List<String> skyWalkingAlarmMessageFieldName =
ReflectUtils.getAllNoStaticFieldList(SkyWalkingAlarmMessage.class).stream().map(Field::getName)
.collect(Collectors.toList());
@PostMapping("/skywalking/alarm")
public void alarm(@RequestBody List<SkyWalkingAlarmMessage> alarmList) {
log.info("接收到skywalking监控调用");
if (CollectionUtil.isEmpty(alarmList)) {
log.info("监控调用为空");
return;
}
SendMailRequest sendMailRequest = fillSkyWalkingMailRequest(alarmList);
sendMailService.sendCustomizeMail(sendMailRequest);
}
private SendMailRequest fillSkyWalkingMailRequest(List<SkyWalkingAlarmMessage> alarmList) {
SendMailRequest sendMailRequest = new SendMailRequest();
// 设置主题
sendMailRequest.setSubject("SkyWalking监控邮件");
// 定义请求的系统
sendMailRequest.setSystemMark("SkyWalking");
sendMailRequest.setRequestTime(LocalDateTime.now());
AlarmMailConfig.AlarmMailInfo skyWalkingConfig = alarmMailConfig.getSkyWalking();
// 发件人昵称
sendMailRequest.setSenderNickname(skyWalkingConfig.getNickName());
// 收件人地址
sendMailRequest.setToAddress(skyWalkingConfig.getToAddress());
// 邮件主题是否为html格式
sendMailRequest.setHtmlText(true);
// 添加默认的免责声明
sendMailRequest.setAddDefaultDisclaimer(true);
// 生成邮件正文
String mailBody = fillSkyWalkingAlarmMessage(alarmList);
sendMailRequest.setContentText(mailBody);
return sendMailRequest;
}
private String fillSkyWalkingAlarmMessage(List<SkyWalkingAlarmMessage> alarmList) {
AlarmMailConfig.AlarmMailInfo skyWalkingConfig = alarmMailConfig.getSkyWalking();
String mailBody = alarmList.stream().map(e -> skyWalkingAlarmMessageFieldName.stream().map(f -> {
Object value;
// 如果参数是startTime
if (StringUtils.equals(f, "startTime")) {
Long startTime = e.getStartTime();
// 如果为空
if (startTime == null) {
// 展示数据为空
value = StringUtils.EMPTY;
} else {
// 否则进行格式化便于展示
value = DateUtils.format(DateUtils.getDate(startTime), TIME_PATTERN);
}
// 如果是tags字段
} else if (StringUtils.equals(f, "tags")) {
List<SkyWalkingAlarmMessage.Tag> tags = e.getTags();
if (CollectionUtil.isEmpty(tags)) {
value = StringUtils.EMPTY;
} else {
// 进行拼接
value = tags.stream().map(v -> v.getKey() + ":" + v.getValue())
.collect(Collectors.joining(";<br/>", "[", "]"));
}
} else {
// 直接获取属性值
value = ReflectUtils.getFieldValue(e, f);
}
// 是否需要翻译告警字段
boolean translateField = skyWalkingConfig.isTranslateField();
if (translateField){
// 拼接翻译字段的和对应的值
return SkyWalkingAlarmMessage.getFieldName(f) + ":" + value;
}else {
// 拼接字段和对应的值
return f + ":" + value;
}
}).collect(Collectors.joining("<br/>"))).collect(Collectors.joining("<br/><br/><HR><br/><br/>"));
return mailBody;
}
}