refactor: get notify content by tpl

This commit is contained in:
710leo 2021-08-01 18:32:36 +08:00
parent 62cbe4a833
commit e14d3eac4d
3 changed files with 289 additions and 62 deletions

View File

@ -8,6 +8,7 @@ import time
import requests
from email.mime.text import MIMEText
from email.header import Header
from bottle import template
reload(sys) # reload 才能调用 setdefaultencoding 方法
sys.setdefaultencoding('utf-8') # 设置 'utf-8'
@ -18,19 +19,9 @@ sys.setdefaultencoding('utf-8') # 设置 'utf-8'
# 3. 调用SMTP服务器发送告警微信、钉钉、飞书、slack、jira、短信、电话等等留给社区实现
# 脚本二开指南
# 1. 可以根据下面的TEST_ALERT_JSON 中的结构修改脚本发送逻辑,定制化告警格式格式如下
"""
[告警类型prometheus]
[规则名称a]
[是否已恢复已触发]
[告警级别1]
[触发时间2021-07-02 16:05:14]
[可读表达式go_goroutines>0]
[当前值[vector={__name__="go_goroutines", instance="localhost:9090", job="prometheus"}]: [value=33.000000]]
[标签组instance=localhost:9090 job=prometheus]
"""
# 1. 可以根据下面的 TEST_ALERT_JSON 中的结构修改脚本发送逻辑定制化告警格式见tpl下的模块文件
# 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方法
# 3. 告警通道需要自行定义Send类中的send_xxx的方法反射调用举例 event.notify_channels = [邮件] 则需要在NOTIFY_CHANNEL_DICT配置 邮件=email在Send类中实现 send_email 方法
# 4. im发群信息比如钉钉发群信息需要群的webhook机器人 token这个信息可以在user的contacts map中各个send_方法处理即可
# 5. 用户创建一个虚拟的用户保存上述im群 的机器人token信息 user的contacts map中
@ -40,12 +31,6 @@ mail_user = "ulricqin"
mail_pass = "password"
mail_from = "ulricqin@163.com"
# just for test
mail_body = """
<p>邮件发送测试</p>
<p><a href="https://www.baidu.com">baidu</a></p>
"""
# 本地告警event json存储目录
LOCAL_EVENT_FILE_DIR = ".alerts"
NOTIFY_CHANNELS_SPLIT_STR = " "
@ -57,12 +42,20 @@ DINGTALK_API = "https://oapi.dingtalk.com/robot/send"
WECOM_ROBOT_TOKEN_NAME = "wecom_robot_token"
WECOM_API = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send"
NOTIFY_CHANNEL_DICT = {
"email":"email",
"sms":"sms",
"voice":"voice",
"dingtalk":"dingtalk",
"wecom":"wecom"
}
# stdin 告警json实例
TEST_ALERT_JSON = {
"event": {
"alert_duration": 10,
"notify_channels": "dingtalk",
"res_classpaths": "",
"res_classpaths": "all",
"id": 4,
"notify_group_objs": None,
"rule_note": "",
@ -91,8 +84,8 @@ TEST_ALERT_JSON = {
"status": 0,
"tags": "instance=localhost:9090 job=prometheus",
"trigger_time": 1625213114,
"res_ident": "",
"rule_name": "a",
"res_ident": "ident1",
"rule_name": "alert_test",
"is_prome_pull": 1,
"notify_users": "1",
"notify_groups": "",
@ -100,7 +93,7 @@ TEST_ALERT_JSON = {
"values": "[vector={__name__=\"go_goroutines\", instance=\"localhost:9090\", job=\"prometheus\"}]: [value=33.000000]",
"readable_expression": "go_goroutines>0",
"notify_user_objs": None,
"is_recovery": 0,
"is_recovery": 1,
"rule_id": 1
},
"rule": {
@ -166,10 +159,9 @@ def main():
# 持久化到本地json文件
persist(payload, rule_id, event_id, trigger_time)
# 生成告警内容
alert_content = content_gen(payload)
alert_content = sms_content_gen(values_gen(payload))
for ch in notify_channels:
send_func_name = "send_{}".format(ch.strip())
send_func_name = "send_{}".format(NOTIFY_CHANNEL_DICT.get(ch.strip()))
has_func = hasattr(Send, send_func_name)
if not has_func:
@ -179,44 +171,47 @@ def main():
send_func = getattr(Send, send_func_name)
send_func(alert_content, payload)
def content_gen(payload):
# 生成格式化告警内容
text = ""
def values_gen(payload):
event_obj = payload.get("event")
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"))
}
rule_type = event_obj.get("is_prome_pull")
type_str_m = {1: "prometheus", 0: "n9e"}
rule_type = type_str_m.get(rule_type)
return values
text += "[告警类型:{}]\n".format(rule_type)
def email_content_gen(values):
return template('etc/script/tpl/mail.tpl', values)
rule_name = event_obj.get("rule_name")
text += "[规则名称:{}]\n".format(rule_name)
def sms_content_gen(values):
return template('etc/script/tpl/sms.tpl', values)
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)
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
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 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)
def persist(payload, rule_id, event_id, trigger_time):
if not os.path.exists(LOCAL_EVENT_FILE_DIR):
@ -237,11 +232,11 @@ class Send(object):
return
recipients = emails
message = MIMEText(alert_content, 'html', 'utf-8')
mail_body = email_content_gen(values_gen(payload))
message = MIMEText(mail_body, 'html', 'utf-8')
message['From'] = mail_from
message['To'] = ", ".join(recipients)
message["Subject"] = "n9e alert"
message["Subject"] = subject_gen(payload.get("event").get("priority"),payload.get("event").get("is_recovery"),payload.get("event").get("rule_name"))
smtp = smtplib.SMTP_SSL(mail_host, mail_port)
smtp.login(mail_user, mail_pass)
@ -320,10 +315,12 @@ def mail_test():
recipients = ["ulricqin@qq.com", "ulric@163.com"]
payload = json.loads(json.dumps(TEST_ALERT_JSON))
mail_body = email_content_gen(values_gen(payload))
message = MIMEText(mail_body, 'html', 'utf-8')
message['From'] = mail_from
message['To'] = ", ".join(recipients)
message["Subject"] = "n9e alert"
message["Subject"] = subject_gen(payload.get("event").get("priority"),payload.get("event").get("is_recovery"),payload.get("event").get("rule_name"))
smtp = smtplib.SMTP_SSL(mail_host, mail_port)
smtp.login(mail_user, mail_pass)
@ -332,7 +329,6 @@ def mail_test():
print("mail_test_done")
if __name__ == "__main__":
if len(sys.argv) == 1:
main()
@ -340,3 +336,4 @@ if __name__ == "__main__":
mail_test()
else:
print("I am confused")

216
etc/script/tpl/mail.tpl Normal file
View File

@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>夜莺告警通知</title>
<style type="text/css">
.wrapper {
background-color: #f8f8f8;
padding: 15px;
height: 100%;
}
.main {
width: 600px;
padding: 30px;
margin: 0 auto;
background-color: #fff;
font-size: 12px;
font-family: verdana,'Microsoft YaHei',Consolas,'Deja Vu Sans Mono','Bitstream Vera Sans Mono';
}
header {
border-radius: 2px 2px 0 0;
}
header .title {
font-size: 16px;
color: #333333;
margin: 0;
}
header .sub-desc {
color: #333;
font-size: 14px;
margin-top: 6px;
margin-bottom: 0;
}
hr {
margin: 20px 0;
height: 0;
border: none;
border-top: 1px solid #e5e5e5;
}
em {
font-weight: 600;
}
table {
margin: 20px 0;
width: 100%;
}
table tbody tr{
font-weight: 200;
font-size: 12px;
color: #666;
height: 32px;
}
.succ {
background-color: green;
color: white;
}
.fail {
background-color: red;
color: white;
}
table tbody tr th {
width: 80px;
text-align: right;
}
.text-right {
text-align: right;
}
.body {
margin-top: 24px;
}
.body-text {
color: #666666;
-webkit-font-smoothing: antialiased;
}
.body-extra {
-webkit-font-smoothing: antialiased;
}
.body-extra.text-right a {
text-decoration: none;
color: #333;
}
.body-extra.text-right a:hover {
color: #666;
}
.button {
width: 200px;
height: 50px;
margin-top: 20px;
text-align: center;
border-radius: 2px;
background: #2D77EE;
line-height: 50px;
font-size: 20px;
color: #FFFFFF;
cursor: pointer;
}
.button:hover {
background: rgb(25, 115, 255);
border-color: rgb(25, 115, 255);
color: #fff;
}
footer {
margin-top: 10px;
text-align: right;
}
.footer-logo {
text-align: right;
}
.footer-logo-image {
width: 108px;
height: 27px;
margin-right: 10px;
}
.copyright {
margin-top: 10px;
font-size: 12px;
text-align: right;
color: #999;
-webkit-font-smoothing: antialiased;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="main">
<header>
<h3 class="title">{{Sname}}</h3>
<p class="sub-desc"></p>
</header>
<hr>
<div class="body">
<table cellspacing="0" cellpadding="0" border="0">
<tbody>
% if IsAlert:
<tr class="fail">
<th>级别状态:</th>
<td>{{Status}}</td>
</tr>
% else:
<tr class="succ">
<th>级别状态:</th>
<td>{{Status}}</td>
</tr>
% end
% if IsMachineDep:
<tr>
<th>告警设备:</th>
<td>{{Ident}}</td>
</tr>
<tr>
<th>所属分组:</th>
<td>
{{Classpath}}<br />
</td>
</tr>
% end
<tr>
<th>监控指标:</th>
<td>{{Metric}}</td>
</tr>
<tr>
<th>tags</th>
<td>{{Tags}}</td>
</tr>
<tr>
<th>当前值:</th>
<td>{{Value}}</td>
</tr>
<tr>
<th>报警说明:</th>
<td>
{{ReadableExpression}}
</td>
</tr>
<tr>
<th>触发时间:</th>
<td>
{{TriggerTime}}
</td>
</tr>
<tr>
<th>报警详情:</th>
<td>{{Elink}}</td>
</tr>
<tr>
<th>报警策略:</th>
<td>{{Slink}}</td>
</tr>
</tbody>
</table>
<hr>
<footer>
<div class="footer-logo">
<a href="https://n9e.didiyun.com">
<img src="https://s3-gz01.didistatic.com/n9e-pub/image/n9e-logo-bg-white.png" class="footer-logo-image" alt="">
</a>
</div>
<div class="copyright" style="font-style: italic">
我们希望与您一起,将监控这个事情,做到极致!
</div>
</footer>
</div>
</div>
</div>
</body>
</html>

14
etc/script/tpl/sms.tpl Normal file
View File

@ -0,0 +1,14 @@
级别状态:{{Status}}
策略名称:{{Sname}}
% if IsMachineDep:
告警设备:{{Ident}}
挂载节点:{{Classpath}}
% end
监控指标:{{Metric}}
指标标签:{{Tags}}
当前值:{{!Value}}
报警说明:{{!ReadableExpression}}
触发时间:{{TriggerTime}}
报警详情:{{Elink}}
报警策略:{{Slink}}