2021-06-28 00:42:39 +08:00
|
|
|
|
#!/usr/bin/python
|
|
|
|
|
# -*- coding: UTF-8 -*-
|
2021-08-06 18:03:36 +08:00
|
|
|
|
#
|
|
|
|
|
# n9e-server把告警事件通过stdin的方式传入notify.py,notify.py从事件中解析出接收人信息、拼出通知内容,发送通知
|
|
|
|
|
# 脚本的灵活性高,要接入短信、电话、jira、飞书等,都非常容易,只要有接口,notify.py去调用即可
|
|
|
|
|
#
|
2021-06-28 00:42:39 +08:00
|
|
|
|
import sys
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import smtplib
|
2021-06-29 14:55:30 +08:00
|
|
|
|
import time
|
2021-07-23 21:15:23 +08:00
|
|
|
|
import requests
|
2021-06-28 00:42:39 +08:00
|
|
|
|
from email.mime.text import MIMEText
|
|
|
|
|
from email.header import Header
|
2021-08-01 18:32:36 +08:00
|
|
|
|
from bottle import template
|
2021-06-28 00:42:39 +08:00
|
|
|
|
|
2021-07-27 11:38:42 +08:00
|
|
|
|
reload(sys) # reload 才能调用 setdefaultencoding 方法
|
|
|
|
|
sys.setdefaultencoding('utf-8') # 设置 'utf-8'
|
|
|
|
|
|
2021-08-06 18:03:36 +08:00
|
|
|
|
################################
|
|
|
|
|
## 邮件告警,修改下面的配置 ##
|
|
|
|
|
################################
|
2021-06-28 00:42:39 +08:00
|
|
|
|
mail_host = "smtp.163.com"
|
|
|
|
|
mail_port = 994
|
|
|
|
|
mail_user = "ulricqin"
|
|
|
|
|
mail_pass = "password"
|
|
|
|
|
mail_from = "ulricqin@163.com"
|
|
|
|
|
|
2021-06-29 14:55:30 +08:00
|
|
|
|
# 本地告警event json存储目录
|
|
|
|
|
LOCAL_EVENT_FILE_DIR = ".alerts"
|
|
|
|
|
NOTIFY_CHANNELS_SPLIT_STR = " "
|
|
|
|
|
|
2021-08-01 18:32:36 +08:00
|
|
|
|
NOTIFY_CHANNEL_DICT = {
|
|
|
|
|
"email":"email",
|
|
|
|
|
"sms":"sms",
|
|
|
|
|
"voice":"voice",
|
|
|
|
|
"dingtalk":"dingtalk",
|
|
|
|
|
"wecom":"wecom"
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-29 14:55:30 +08:00
|
|
|
|
# stdin 告警json实例
|
|
|
|
|
TEST_ALERT_JSON = {
|
|
|
|
|
"event": {
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"alert_duration": 10,
|
|
|
|
|
"notify_channels": "dingtalk",
|
2021-08-01 18:32:36 +08:00
|
|
|
|
"res_classpaths": "all",
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"id": 4,
|
|
|
|
|
"notify_group_objs": None,
|
|
|
|
|
"rule_note": "",
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"history_points": [
|
|
|
|
|
{
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"metric": "go_goroutines",
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"points": [
|
|
|
|
|
{
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"t": 1625213114,
|
|
|
|
|
"v": 33.0
|
2021-06-29 14:55:30 +08:00
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"tags": {
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"instance": "localhost:9090",
|
|
|
|
|
"job": "prometheus"
|
2021-06-29 14:55:30 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
"priority": 1,
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"last_sent": True,
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"tag_map": {
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"instance": "localhost:9090",
|
|
|
|
|
"job": "prometheus"
|
2021-06-29 14:55:30 +08:00
|
|
|
|
},
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"hash_id": "ecb258d2ca03454ee390a352913c461b",
|
|
|
|
|
"status": 0,
|
|
|
|
|
"tags": "instance=localhost:9090 job=prometheus",
|
|
|
|
|
"trigger_time": 1625213114,
|
2021-08-01 18:32:36 +08:00
|
|
|
|
"res_ident": "ident1",
|
|
|
|
|
"rule_name": "alert_test",
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"is_prome_pull": 1,
|
|
|
|
|
"notify_users": "1",
|
|
|
|
|
"notify_groups": "",
|
|
|
|
|
"runbook_url": "",
|
|
|
|
|
"values": "[vector={__name__=\"go_goroutines\", instance=\"localhost:9090\", job=\"prometheus\"}]: [value=33.000000]",
|
|
|
|
|
"readable_expression": "go_goroutines>0",
|
|
|
|
|
"notify_user_objs": None,
|
2021-08-01 18:32:36 +08:00
|
|
|
|
"is_recovery": 1,
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"rule_id": 1
|
2021-06-29 14:55:30 +08:00
|
|
|
|
},
|
|
|
|
|
"rule": {
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"alert_duration": 10,
|
|
|
|
|
"notify_channels": "dingtalk",
|
|
|
|
|
"enable_stime": "00:00",
|
|
|
|
|
"id": 1,
|
|
|
|
|
"note": "",
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"create_by": "root",
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"append_tags": "",
|
|
|
|
|
"priority": 1,
|
|
|
|
|
"update_by": "root",
|
|
|
|
|
"type": 1,
|
|
|
|
|
"status": 0,
|
|
|
|
|
"recovery_notify": 0,
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"enable_days_of_week": "1 2 3 4 5 6 7",
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"callbacks": "localhost:10000",
|
|
|
|
|
"notify_users": "1",
|
|
|
|
|
"notify_groups": "",
|
|
|
|
|
"runbook_url": "",
|
|
|
|
|
"name": "a",
|
|
|
|
|
"update_at": 1625211576,
|
|
|
|
|
"create_at": 1625211576,
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"enable_etime": "23:59",
|
|
|
|
|
"group_id": 1,
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"expression": {
|
|
|
|
|
"evaluation_interval": 4,
|
|
|
|
|
"promql": "go_goroutines>0"
|
|
|
|
|
}
|
2021-06-29 14:55:30 +08:00
|
|
|
|
},
|
|
|
|
|
"users": [
|
|
|
|
|
{
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"username": "root",
|
|
|
|
|
"status": 0,
|
|
|
|
|
"contacts": {
|
|
|
|
|
"dingtalk_robot_token": "xxxxxx"
|
|
|
|
|
},
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"create_by": "system",
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"update_at": 1625211432,
|
|
|
|
|
"create_at": 1624871926,
|
2021-06-29 14:55:30 +08:00
|
|
|
|
"email": "",
|
|
|
|
|
"phone": "",
|
|
|
|
|
"role": "Admin",
|
2021-07-04 18:53:33 +08:00
|
|
|
|
"update_by": "root",
|
|
|
|
|
"portrait": "",
|
|
|
|
|
"nickname": "\u8d85\u7ba1",
|
|
|
|
|
"id": 1
|
2021-06-29 14:55:30 +08:00
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-06-28 00:42:39 +08:00
|
|
|
|
def main():
|
|
|
|
|
payload = json.load(sys.stdin)
|
2021-06-29 14:55:30 +08:00
|
|
|
|
trigger_time = payload['event']['trigger_time']
|
|
|
|
|
event_id = payload['event']['id']
|
|
|
|
|
rule_id = payload['rule']['id']
|
|
|
|
|
notify_channels = payload['event'].get('notify_channels').strip().split(NOTIFY_CHANNELS_SPLIT_STR)
|
|
|
|
|
if len(notify_channels) == 0:
|
|
|
|
|
msg = "notify_channels_empty"
|
|
|
|
|
print(msg)
|
|
|
|
|
return
|
|
|
|
|
# 持久化到本地json文件
|
|
|
|
|
persist(payload, rule_id, event_id, trigger_time)
|
|
|
|
|
# 生成告警内容
|
2021-08-01 18:32:36 +08:00
|
|
|
|
alert_content = sms_content_gen(values_gen(payload))
|
2021-06-29 14:55:30 +08:00
|
|
|
|
for ch in notify_channels:
|
2021-08-01 18:32:36 +08:00
|
|
|
|
send_func_name = "send_{}".format(NOTIFY_CHANNEL_DICT.get(ch.strip()))
|
2021-06-29 14:55:30 +08:00
|
|
|
|
has_func = hasattr(Send, send_func_name)
|
|
|
|
|
|
|
|
|
|
if not has_func:
|
|
|
|
|
msg = "[send_func_name_err][func_not_found_in_Send_class:{}]".format(send_func_name)
|
|
|
|
|
print(msg)
|
|
|
|
|
continue
|
|
|
|
|
send_func = getattr(Send, send_func_name)
|
2021-07-04 18:53:33 +08:00
|
|
|
|
send_func(alert_content, payload)
|
2021-06-29 14:55:30 +08:00
|
|
|
|
|
2021-08-01 18:32:36 +08:00
|
|
|
|
def values_gen(payload):
|
2021-06-29 14:55:30 +08:00
|
|
|
|
event_obj = payload.get("event")
|
2021-08-01 18:32:36 +08:00
|
|
|
|
values = {
|
|
|
|
|
"IsAlert": event_obj.get("is_recovery") == 0,
|
|
|
|
|
"IsMachineDep": event_obj.get("res_classpaths") != "",
|
|
|
|
|
"Status": status_gen(event_obj.get("priority"),event_obj.get("is_recovery")),
|
|
|
|
|
"Sname": event_obj.get("rule_name"),
|
|
|
|
|
"Ident": event_obj.get("res_ident"),
|
|
|
|
|
"Classpath": event_obj.get("res_classpaths"),
|
|
|
|
|
"Metric": metric_gen(event_obj.get("history_points")),
|
|
|
|
|
"Tags": event_obj.get("tags"),
|
|
|
|
|
"Value": event_obj.get("values"),
|
|
|
|
|
"ReadableExpression": event_obj.get("readable_expression"),
|
|
|
|
|
"TriggerTime": time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(event_obj.get("trigger_time"))),
|
|
|
|
|
"Elink": "http://n9e.didiyun.com/strategy/edit/{}".format(event_obj.get("rule_id")),
|
|
|
|
|
"Slink": "http://n9e.didiyun.com/event/{}".format(event_obj.get("id"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return values
|
|
|
|
|
|
|
|
|
|
def email_content_gen(values):
|
|
|
|
|
return template('etc/script/tpl/mail.tpl', values)
|
|
|
|
|
|
|
|
|
|
def sms_content_gen(values):
|
|
|
|
|
return template('etc/script/tpl/sms.tpl', values)
|
|
|
|
|
|
|
|
|
|
def status_gen(priority,is_recovery):
|
|
|
|
|
is_recovery_str_m = {1: "恢复", 0: "告警"}
|
|
|
|
|
status = "P{} {}".format(priority, is_recovery_str_m.get(is_recovery))
|
|
|
|
|
return status
|
|
|
|
|
|
|
|
|
|
def subject_gen(priority,is_recovery,rule_name):
|
|
|
|
|
is_recovery_str_m = {1: "恢复", 0: "告警"}
|
|
|
|
|
subject = "P{} {} {}".format(priority, is_recovery_str_m.get(is_recovery), rule_name)
|
|
|
|
|
return subject
|
|
|
|
|
|
|
|
|
|
def metric_gen(history_points):
|
|
|
|
|
metrics = []
|
|
|
|
|
for item in history_points:
|
|
|
|
|
metrics.append(item.get("metric"))
|
|
|
|
|
return ",".join(metrics)
|
2021-06-29 14:55:30 +08:00
|
|
|
|
|
|
|
|
|
def persist(payload, rule_id, event_id, trigger_time):
|
|
|
|
|
if not os.path.exists(LOCAL_EVENT_FILE_DIR):
|
|
|
|
|
os.makedirs(LOCAL_EVENT_FILE_DIR)
|
|
|
|
|
|
|
|
|
|
filename = '%d_%d_%d' % (rule_id, event_id, trigger_time)
|
|
|
|
|
filepath = os.path.join(LOCAL_EVENT_FILE_DIR, filename)
|
|
|
|
|
with open(filepath, 'w') as f:
|
|
|
|
|
f.write(json.dumps(payload, indent=4))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Send(object):
|
|
|
|
|
@classmethod
|
2021-07-27 18:31:50 +08:00
|
|
|
|
def send_email(cls, alert_content, payload):
|
|
|
|
|
users = payload.get("users")
|
2021-06-29 14:55:30 +08:00
|
|
|
|
emails = [x.get("email") for x in users]
|
|
|
|
|
if not emails:
|
|
|
|
|
return
|
2021-07-27 18:31:50 +08:00
|
|
|
|
|
2021-06-29 14:55:30 +08:00
|
|
|
|
recipients = emails
|
2021-08-01 18:32:36 +08:00
|
|
|
|
mail_body = email_content_gen(values_gen(payload))
|
|
|
|
|
message = MIMEText(mail_body, 'html', 'utf-8')
|
2021-06-29 14:55:30 +08:00
|
|
|
|
message['From'] = mail_from
|
|
|
|
|
message['To'] = ", ".join(recipients)
|
2021-08-01 18:32:36 +08:00
|
|
|
|
message["Subject"] = subject_gen(payload.get("event").get("priority"),payload.get("event").get("is_recovery"),payload.get("event").get("rule_name"))
|
2021-06-29 14:55:30 +08:00
|
|
|
|
|
|
|
|
|
smtp = smtplib.SMTP_SSL(mail_host, mail_port)
|
|
|
|
|
smtp.login(mail_user, mail_pass)
|
|
|
|
|
smtp.sendmail(mail_from, recipients, message.as_string())
|
|
|
|
|
smtp.close()
|
|
|
|
|
|
|
|
|
|
print("send_mail_success")
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-07-27 15:35:37 +08:00
|
|
|
|
def send_wecom(cls, alert_content, payload):
|
|
|
|
|
users = payload.get("users")
|
|
|
|
|
|
|
|
|
|
for u in users:
|
|
|
|
|
contacts = u.get("contacts")
|
2021-08-06 18:03:36 +08:00
|
|
|
|
wecom_robot_token = contacts.get("wecom_robot_token", "")
|
2021-07-27 15:35:37 +08:00
|
|
|
|
|
|
|
|
|
if wecom_robot_token == "":
|
|
|
|
|
continue
|
|
|
|
|
|
2021-08-06 18:03:36 +08:00
|
|
|
|
wecom_api_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={}".format(wecom_robot_token)
|
2021-07-27 15:35:37 +08:00
|
|
|
|
atMobiles = [u.get("phone")]
|
|
|
|
|
headers = {'Content-Type': 'application/json;charset=utf-8'}
|
|
|
|
|
payload = {
|
|
|
|
|
"msgtype": "text",
|
|
|
|
|
"text": {
|
|
|
|
|
"content": alert_content
|
|
|
|
|
},
|
|
|
|
|
"at": {
|
|
|
|
|
"atMobiles": atMobiles,
|
|
|
|
|
"isAtAll": False
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
res = requests.post(wecom_api_url, json.dumps(payload), headers=headers)
|
|
|
|
|
print(res.status_code)
|
|
|
|
|
print(res.text)
|
|
|
|
|
print("send_wecom")
|
|
|
|
|
|
2021-06-29 14:55:30 +08:00
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-07-04 18:53:33 +08:00
|
|
|
|
def send_dingtalk(cls, alert_content, payload):
|
|
|
|
|
# 钉钉发群信息需要群的webhook机器人 token,这个信息可以在user的contacts map中
|
|
|
|
|
|
|
|
|
|
users = payload.get("users")
|
|
|
|
|
|
|
|
|
|
for u in users:
|
|
|
|
|
contacts = u.get("contacts")
|
|
|
|
|
|
2021-08-06 18:03:36 +08:00
|
|
|
|
dingtalk_robot_token = contacts.get("dingtalk_robot_token", "")
|
2021-07-04 18:53:33 +08:00
|
|
|
|
|
|
|
|
|
if dingtalk_robot_token == "":
|
|
|
|
|
print("dingtalk_robot_token_not_found")
|
|
|
|
|
continue
|
|
|
|
|
|
2021-08-06 18:03:36 +08:00
|
|
|
|
dingtalk_api_url = "https://oapi.dingtalk.com/robot/send?access_token={}".format(dingtalk_robot_token)
|
2021-07-04 18:53:33 +08:00
|
|
|
|
atMobiles = [u.get("phone")]
|
|
|
|
|
headers = {'Content-Type': 'application/json;charset=utf-8'}
|
|
|
|
|
payload = {
|
|
|
|
|
"msgtype": "text",
|
|
|
|
|
"text": {
|
|
|
|
|
"content": alert_content
|
|
|
|
|
},
|
|
|
|
|
"at": {
|
|
|
|
|
"atMobiles": atMobiles,
|
|
|
|
|
"isAtAll": False
|
|
|
|
|
}
|
2021-06-29 14:55:30 +08:00
|
|
|
|
}
|
2021-07-04 18:53:33 +08:00
|
|
|
|
res = requests.post(dingtalk_api_url, json.dumps(payload), headers=headers)
|
|
|
|
|
print(res.status_code)
|
|
|
|
|
print(res.text)
|
2021-06-29 14:55:30 +08:00
|
|
|
|
|
2021-07-04 18:53:33 +08:00
|
|
|
|
print("send_dingtalk")
|
2021-06-28 00:42:39 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def mail_test():
|
|
|
|
|
print("mail_test_todo")
|
|
|
|
|
|
|
|
|
|
recipients = ["ulricqin@qq.com", "ulric@163.com"]
|
2021-06-29 14:55:30 +08:00
|
|
|
|
|
2021-08-01 18:32:36 +08:00
|
|
|
|
payload = json.loads(json.dumps(TEST_ALERT_JSON))
|
|
|
|
|
mail_body = email_content_gen(values_gen(payload))
|
2021-06-28 00:42:39 +08:00
|
|
|
|
message = MIMEText(mail_body, 'html', 'utf-8')
|
|
|
|
|
message['From'] = mail_from
|
|
|
|
|
message['To'] = ", ".join(recipients)
|
2021-08-01 18:32:36 +08:00
|
|
|
|
message["Subject"] = subject_gen(payload.get("event").get("priority"),payload.get("event").get("is_recovery"),payload.get("event").get("rule_name"))
|
2021-06-28 00:42:39 +08:00
|
|
|
|
|
|
|
|
|
smtp = smtplib.SMTP_SSL(mail_host, mail_port)
|
|
|
|
|
smtp.login(mail_user, mail_pass)
|
|
|
|
|
smtp.sendmail(mail_from, recipients, message.as_string())
|
|
|
|
|
smtp.close()
|
|
|
|
|
|
|
|
|
|
print("mail_test_done")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
if len(sys.argv) == 1:
|
|
|
|
|
main()
|
|
|
|
|
elif sys.argv[1] == "mail":
|
|
|
|
|
mail_test()
|
|
|
|
|
else:
|
2021-06-29 14:55:30 +08:00
|
|
|
|
print("I am confused")
|
2021-08-01 18:32:36 +08:00
|
|
|
|
|