notify.py 10.4 KB
Newer Older
Q
qinyening 已提交
1 2
#!/usr/bin/python
# -*- coding: UTF-8 -*-
U
UlricQin 已提交
3 4 5 6
#
# n9e-server把告警事件通过stdin的方式传入notify.py,notify.py从事件中解析出接收人信息、拼出通知内容,发送通知
# 脚本的灵活性高,要接入短信、电话、jira、飞书等,都非常容易,只要有接口,notify.py去调用即可
#
Q
qinyening 已提交
7 8 9 10
import sys
import json
import os
import smtplib
11
import time
U
UlricQin 已提交
12
import requests
Q
qinyening 已提交
13 14
from email.mime.text import MIMEText
from email.header import Header
7
710leo 已提交
15
from bottle import template
Q
qinyening 已提交
16

17 18 19
reload(sys)                      # reload 才能调用 setdefaultencoding 方法
sys.setdefaultencoding('utf-8')  # 设置 'utf-8'

U
UlricQin 已提交
20 21 22
################################
##    邮件告警,修改下面的配置    ##
################################
Q
qinyening 已提交
23 24 25 26 27 28
mail_host = "smtp.163.com"
mail_port = 994
mail_user = "ulricqin"
mail_pass = "password"
mail_from = "ulricqin@163.com"

29 30 31 32
# 本地告警event json存储目录
LOCAL_EVENT_FILE_DIR = ".alerts"
NOTIFY_CHANNELS_SPLIT_STR = " "

7
710leo 已提交
33 34 35 36 37 38 39 40
NOTIFY_CHANNEL_DICT = {
  "email":"email",
  "sms":"sms",
  "voice":"voice",
  "dingtalk":"dingtalk",
  "wecom":"wecom"
}

41 42 43
# stdin 告警json实例
TEST_ALERT_JSON = {
    "event": {
ning1875's avatar
ning1875 已提交
44 45
        "alert_duration": 10,
        "notify_channels": "dingtalk",
7
710leo 已提交
46
        "res_classpaths": "all",
ning1875's avatar
ning1875 已提交
47 48 49
        "id": 4,
        "notify_group_objs": None,
        "rule_note": "",
50 51
        "history_points": [
            {
ning1875's avatar
ning1875 已提交
52
                "metric": "go_goroutines",
53 54
                "points": [
                    {
ning1875's avatar
ning1875 已提交
55 56
                        "t": 1625213114,
                        "v": 33.0
57 58 59
                    }
                ],
                "tags": {
ning1875's avatar
ning1875 已提交
60 61
                    "instance": "localhost:9090",
                    "job": "prometheus"
62 63 64 65
                }
            }
        ],
        "priority": 1,
ning1875's avatar
ning1875 已提交
66
        "last_sent": True,
67
        "tag_map": {
ning1875's avatar
ning1875 已提交
68 69
            "instance": "localhost:9090",
            "job": "prometheus"
70
        },
ning1875's avatar
ning1875 已提交
71 72 73 74
        "hash_id": "ecb258d2ca03454ee390a352913c461b",
        "status": 0,
        "tags": "instance=localhost:9090 job=prometheus",
        "trigger_time": 1625213114,
7
710leo 已提交
75 76
        "res_ident": "ident1",
        "rule_name": "alert_test",
ning1875's avatar
ning1875 已提交
77 78 79 80 81 82 83
        "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,
7
710leo 已提交
84
        "is_recovery": 1,
ning1875's avatar
ning1875 已提交
85
        "rule_id": 1
86 87
    },
    "rule": {
ning1875's avatar
ning1875 已提交
88 89 90 91 92
        "alert_duration": 10,
        "notify_channels": "dingtalk",
        "enable_stime": "00:00",
        "id": 1,
        "note": "",
93
        "create_by": "root",
ning1875's avatar
ning1875 已提交
94 95 96 97 98 99
        "append_tags": "",
        "priority": 1,
        "update_by": "root",
        "type": 1,
        "status": 0,
        "recovery_notify": 0,
100
        "enable_days_of_week": "1 2 3 4 5 6 7",
ning1875's avatar
ning1875 已提交
101 102 103 104 105 106 107
        "callbacks": "localhost:10000",
        "notify_users": "1",
        "notify_groups": "",
        "runbook_url": "",
        "name": "a",
        "update_at": 1625211576,
        "create_at": 1625211576,
108 109
        "enable_etime": "23:59",
        "group_id": 1,
ning1875's avatar
ning1875 已提交
110 111 112 113
        "expression": {
            "evaluation_interval": 4,
            "promql": "go_goroutines>0"
        }
114 115 116
    },
    "users": [
        {
ning1875's avatar
ning1875 已提交
117 118 119 120 121
            "username": "root",
            "status": 0,
            "contacts": {
                "dingtalk_robot_token": "xxxxxx"
            },
122
            "create_by": "system",
ning1875's avatar
ning1875 已提交
123 124
            "update_at": 1625211432,
            "create_at": 1624871926,
125 126 127
            "email": "",
            "phone": "",
            "role": "Admin",
ning1875's avatar
ning1875 已提交
128 129 130 131
            "update_by": "root",
            "portrait": "",
            "nickname": "\u8d85\u7ba1",
            "id": 1
132 133 134 135 136
        }
    ]
}


Q
qinyening 已提交
137 138
def main():
    payload = json.load(sys.stdin)
139 140 141 142 143 144 145 146 147 148 149
    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)
    # 生成告警内容
7
710leo 已提交
150
    alert_content = sms_content_gen(values_gen(payload))
151
    for ch in notify_channels:
7
710leo 已提交
152
        send_func_name = "send_{}".format(NOTIFY_CHANNEL_DICT.get(ch.strip()))
153 154 155 156 157 158 159
        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)
ning1875's avatar
ning1875 已提交
160
        send_func(alert_content, payload)
161

7
710leo 已提交
162
def values_gen(payload):
163
    event_obj = payload.get("event")
7
710leo 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
    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)
203 204 205 206 207 208 209 210 211 212 213 214 215

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
U
UlricQin 已提交
216 217
    def send_email(cls, alert_content, payload):
        users = payload.get("users")
218 219 220
        emails = [x.get("email") for x in users]
        if not emails:
            return
U
UlricQin 已提交
221

222
        recipients = emails
7
710leo 已提交
223 224
        mail_body = email_content_gen(values_gen(payload))
        message = MIMEText(mail_body, 'html', 'utf-8')
225 226
        message['From'] = mail_from
        message['To'] = ", ".join(recipients)
7
710leo 已提交
227
        message["Subject"] = subject_gen(payload.get("event").get("priority"),payload.get("event").get("is_recovery"),payload.get("event").get("rule_name"))
228 229 230 231 232 233 234 235 236

        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
李伟强 已提交
237 238 239 240 241
    def send_wecom(cls, alert_content, payload):
        users = payload.get("users")

        for u in users:
            contacts = u.get("contacts")
U
UlricQin 已提交
242
            wecom_robot_token = contacts.get("wecom_robot_token", "")
李伟强 已提交
243 244 245 246

            if wecom_robot_token == "":
                continue

U
UlricQin 已提交
247
            wecom_api_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={}".format(wecom_robot_token)
李伟强 已提交
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
            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")

265 266

    @classmethod
ning1875's avatar
ning1875 已提交
267 268 269 270 271 272 273 274
    def send_dingtalk(cls, alert_content, payload):
        # 钉钉发群信息需要群的webhook机器人 token,这个信息可以在user的contacts map中

        users = payload.get("users")

        for u in users:
            contacts = u.get("contacts")

U
UlricQin 已提交
275
            dingtalk_robot_token = contacts.get("dingtalk_robot_token", "")
ning1875's avatar
ning1875 已提交
276 277 278 279 280

            if dingtalk_robot_token == "":
                print("dingtalk_robot_token_not_found")
                continue

U
UlricQin 已提交
281
            dingtalk_api_url = "https://oapi.dingtalk.com/robot/send?access_token={}".format(dingtalk_robot_token)
ning1875's avatar
ning1875 已提交
282 283 284 285 286 287 288 289 290 291 292
            atMobiles = [u.get("phone")]
            headers = {'Content-Type': 'application/json;charset=utf-8'}
            payload = {
                "msgtype": "text",
                "text": {
                    "content": alert_content
                },
                "at": {
                    "atMobiles": atMobiles,
                    "isAtAll": False
                }
293
            }
ning1875's avatar
ning1875 已提交
294 295 296
            res = requests.post(dingtalk_api_url, json.dumps(payload), headers=headers)
            print(res.status_code)
            print(res.text)
297

ning1875's avatar
ning1875 已提交
298
            print("send_dingtalk")
Q
qinyening 已提交
299 300 301 302 303 304


def mail_test():
    print("mail_test_todo")

    recipients = ["ulricqin@qq.com", "ulric@163.com"]
305

7
710leo 已提交
306 307
    payload =  json.loads(json.dumps(TEST_ALERT_JSON))
    mail_body = email_content_gen(values_gen(payload))
Q
qinyening 已提交
308 309 310
    message = MIMEText(mail_body, 'html', 'utf-8')
    message['From'] = mail_from
    message['To'] = ", ".join(recipients)
7
710leo 已提交
311
    message["Subject"] = subject_gen(payload.get("event").get("priority"),payload.get("event").get("is_recovery"),payload.get("event").get("rule_name"))
Q
qinyening 已提交
312 313 314 315 316 317 318 319 320 321 322 323 324 325

    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:
326
        print("I am confused")
7
710leo 已提交
327