feat: notify support mail and dingding

This commit is contained in:
ning1875 2021-06-29 14:55:30 +08:00 committed by GitHub
parent b03d57f40a
commit fd9d78061b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 235 additions and 17 deletions

View File

@ -4,6 +4,7 @@ import sys
import json import json
import os import os
import smtplib import smtplib
import time
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.header import Header from email.header import Header
@ -12,6 +13,24 @@ from email.header import Header
# 2. 文件路径和名字是.alerts/${timestamp}_${ruleid} # 2. 文件路径和名字是.alerts/${timestamp}_${ruleid}
# 3. 调用SMTP服务器发送告警微信、钉钉、飞书、slack、jira、短信、电话等等留给社区实现 # 3. 调用SMTP服务器发送告警微信、钉钉、飞书、slack、jira、短信、电话等等留给社区实现
# 脚本二开指南
# 1. 可以根据下面的TEST_ALERT_JSON 中的结构修改脚本发送逻辑,定制化告警格式格式如下
"""
告警类型prometheus
规则名称pull_promql
是否已恢复已触发
告警级别1
触发时间2021-06-28 11:46:35
可读表达式 go_gc_duration_seconds>0
当前值[vector={__name__="go_gc_duration_seconds", instance="172.20.70.205:9100", job="node-targets", quantile="1"}]: [value=0.022498]
标签组a=b c=d instance=172.20.70.205:9100 job=node-targets quantile=1
"""
# 2. 每个告警会以json文件的格式存储在LOCAL_EVENT_FILE_DIR 下面,文件名为 filename = '%d_%d_%d' % (rule_id, event_id, trigger_time)
# 3. 告警通道需要自行定义Send类中的send_xxx同名方法反射调用举例 event.notify_channels = [qq dingding]
# 则需要Send类中 有 send_qq send_dingding方法
import requests
mail_host = "smtp.163.com" mail_host = "smtp.163.com"
mail_port = 994 mail_port = 994
mail_user = "ulricqin" mail_user = "ulricqin"
@ -24,32 +43,231 @@ mail_body = """
<p><a href="https://www.baidu.com">baidu</a></p> <p><a href="https://www.baidu.com">baidu</a></p>
""" """
# 本地告警event json存储目录
LOCAL_EVENT_FILE_DIR = ".alerts"
NOTIFY_CHANNELS_SPLIT_STR = " "
# stdin 告警json实例
TEST_ALERT_JSON = {
"event": {
"alert_duration": 30,
"hash_id": "0c6cbe29df08bb6f9c26af518638128e",
"history_points": [
{
"metric": "go_gc_duration_seconds",
"points": [
{
"t": 1624851995,
"v": 0.000175
}
],
"tags": {
"instance": "172.20.70.205:9100",
"job": "node-targets",
"quantile": "0.25"
}
}
],
"id": 190,
"is_prome_pull": 1,
"is_recovery": 0,
"last_sent": True,
"notify_channels": "qq dingding ",
"notify_group_objs": None,
"notify_groups": "",
"notify_user_objs": None,
"notify_users": "1",
"priority": 1,
"readable_expression": " go_gc_duration_seconds>0",
"res_classpaths": "all",
"res_ident": "",
"rule_id": 10,
"rule_name": "pull_promql",
"rule_note": "note",
"runbook_url": "qq.com",
"status": 0,
"tag_map": {
"a": "b",
"c": "d",
"instance": "172.20.70.205:9100",
"job": "node-targets",
"quantile": "0.25"
},
"tags": "a=b c=d instance=172.20.70.205:9100 job=node-targets quantile=0.25",
"trigger_time": 1624851995,
"values": "[vector={__name__=\"go_gc_duration_seconds\", instance=\"172.20.70.205:9100\", job=\"node-targets\", quantile=\"0.25\"}]: [value=0.000175]"
},
"rule": {
"alert_duration": 30,
"append_tags": "a=b c=d",
"callbacks": "localhost:8881",
"create_at": 1624851512,
"create_by": "root",
"enable_days_of_week": "1 2 3 4 5 6 7",
"enable_etime": "23:59",
"enable_stime": "00:00",
"expression": {
"evaluation_interval": 3,
"promql": " go_gc_duration_seconds>0",
"resolve_timeout": 40
},
"group_id": 1,
"id": 10,
"name": "pull_promql",
"note": "note",
"notify_channels": "qq dingding ",
"notify_groups": "",
"notify_users": "1",
"priority": 1,
"recovery_notify": 0,
"runbook_url": "qq.com",
"status": 0,
"type": 1,
"update_at": 1624851512,
"update_by": "root"
},
"users": [
{
"contacts": None,
"create_at": 1624258550,
"create_by": "system",
"email": "",
"id": 1,
"nickname": "\u8d85\u7ba1",
"phone": "",
"portrait": "",
"role": "Admin",
"status": 0,
"update_at": 1624258550,
"update_by": "system",
"username": "root"
}
]
}
def main(): def main():
payload = json.load(sys.stdin) payload = json.load(sys.stdin)
persist(payload) 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)
# 生成告警内容
alert_content = content_gen(payload)
for ch in notify_channels:
send_func_name = "send_{}".format(ch.strip())
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)
send_func(alert_content)
def persist(payload): def content_gen(payload):
if not os.path.exists(".alerts"): # 生成格式化告警内容
os.makedirs(".alerts") text = ""
event_obj = payload.get("event")
filename = '%d_%d' % (payload['event']['trigger_time'], payload['rule']['id']) rule_type = event_obj.get("is_prome_pull")
filepath = os.path.join(".alerts", filename) type_str_m = {1: "prometheus", 0: "n9e"}
rule_type = type_str_m.get(rule_type)
f = open(filepath, 'w') text += "告警类型:{}\n".format(rule_type)
print(json.dumps(payload, indent=4), file=f)
f.close() rule_name = event_obj.get("rule_name")
text += "规则名称:{}\n".format(rule_name)
is_recovery = event_obj.get("is_recovery")
is_recovery_str_m = {1: "已恢复", 0: "已触发"}
is_recovery = is_recovery_str_m.get(is_recovery)
text += "是否已恢复:{}\n".format(is_recovery)
priority = event_obj.get("priority")
text += "告警级别:{}\n".format(priority)
trigger_time = event_obj.get("trigger_time")
text += "触发时间:{}\n".format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(trigger_time))))
readable_expression = event_obj.get("readable_expression")
text += "可读表达式:{}\n".format(readable_expression)
values = event_obj.get("values")
text += "当前值:{}\n".format(values)
tags = event_obj.get("tags")
text += "标签组:{}\n".format(tags)
print(text)
return text
def send_mail(payload): def persist(payload, rule_id, event_id, trigger_time):
print("send_mail") 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))
def send_wecom(payload): class Send(object):
@classmethod
def send_mail(cls, payload):
users = payload.get("event").get("users")
emails = [x.get("email") for x in users]
if not emails:
print("[emails_empty]")
return
recipients = emails
message = MIMEText(mail_body, 'html', 'utf-8')
message['From'] = mail_from
message['To'] = ", ".join(recipients)
message["Subject"] = "n9e alert"
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
def send_wecom(cls, payload):
print("send_wecom") print("send_wecom")
@classmethod
def send_dingtalk(cls, payload):
# TODO 钉钉发群信息需要群的webhook机器人 token这个信息可以写在告警策略中的附加字段中
dingtalk_api_url = "https://oapi.dingtalk.com/robot/send?access_token=xxxx"
users = payload.get("event").get("users")
atMobiles = [x.get("phone") for x in users]
headers = {'Content-Type': 'application/json;charset=utf-8'}
payload = {
"msgtype": "text",
"text": {
"content": payload
},
"at": {
"atMobiles": atMobiles,
"isAtAll": False
}
}
res = requests.post(dingtalk_api_url, json.dumps(payload), headers=headers)
print(res.status_code)
print(res.text)
def send_dingtalk(payload):
print("send_dingtalk") print("send_dingtalk")