| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 |
- # -*- coding:utf-8 -*-
- """
- 企业微信告警发送。
- 职责:
- - 读 conf/alerter.ini 拿 base_url + 按通道名取机器人 key
- - send_markdown 发企微 markdown 消息
- 失败一律抛异常,不静默:告警发不出去必须让调度看见,否则问题被双重掩盖。
- 覆盖四种情况——配置文件缺失、通道未注册、key 还是占位符、企微返回 errcode != 0。
- (ConfigParser.read 对缺失文件静默返回,故先显式校验存在性。)
- 消息拼装由调用方负责:不同 monitor 的标题和字段各不相同,塞进来只会变成参数堆。
- """
- import json
- import os
- import urllib.request
- from configparser import ConfigParser
- _PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- _DEFAULT_CONF_PATH = os.path.join(_PROJECT_ROOT, 'conf', 'alerter.ini')
- _TIMEOUT_SEC = 20
- class Alerter:
- def __init__(self, channel='default', conf_path=None):
- # type: (str, str) -> None
- path = conf_path or _DEFAULT_CONF_PATH
- if not os.path.isfile(path):
- raise RuntimeError('告警配置文件不存在:' + path)
- cp = ConfigParser()
- cp.read(path, encoding='utf-8')
- if not cp.has_option('channels', channel):
- raise RuntimeError(
- "告警通道未注册:'{0}';{1} [channels] 段加 '{0} = <key>'".format(channel, path))
- key = cp.get('channels', channel).strip()
- if key.startswith('<'):
- raise RuntimeError(
- "告警通道 '{0}' 的 key 仍是占位符,填真实机器人 key:{1}".format(channel, path))
- self.webhook = cp.get('wechat_work', 'base_url').strip() + key
- def send_markdown(self, content):
- # type: (str) -> None
- """发 markdown 消息。企微 markdown 单条上限 4096 字节,超长由调用方自行截断。"""
- payload = json.dumps({'msgtype': 'markdown', 'markdown': {'content': content}},
- ensure_ascii=False).encode('utf-8')
- req = urllib.request.Request(
- self.webhook, data=payload, headers={'Content-Type': 'application/json'})
- resp = urllib.request.urlopen(req, timeout=_TIMEOUT_SEC).read().decode('utf-8')
- if json.loads(resp).get('errcode') != 0:
- raise RuntimeError('企微发送失败:' + resp)
|