├── .idea ├── dingding.iml ├── encodings.xml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── README.md ├── conf ├── BLog.py ├── BLog.pyc ├── INIFILES.py ├── INIFILES.pyc ├── __init__.py ├── __init__.pyc └── file_util.py ├── config.ini ├── dingding.py ├── log └── 2017-01-06.log ├── requests ├── requests-2.12.4-py2.py3-none-any.whl └── requests-2.12.4.tar.gz ├── 恢复.jpg └── 报警.jpg /.idea/dingding.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 32 | 33 | 34 | 35 | true 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 104 | 105 | 124 | 125 | 126 | 127 | 128 | 141 | 142 | 155 | 156 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 202 | 203 | 222 | 223 | 244 | 245 | 267 | 268 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 1483618058798 301 | 304 | 305 | 1483720371347 306 | 310 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 343 | 344 | 345 | 347 | 348 | 349 | 350 | 351 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ZABBIX可以实现短信、邮件、微信等各种报警,这三种基本大家都很熟悉,但是多多少少受到一些限制,最近受人启发,说可以实现钉钉报警,然后网上找了找,有GO语言大神写得,无奈自己只会三P,所以就用PY交易来实现,苍老师说过:Life is short,you need python! 2 | 3 | [TOC] 4 | 5 | ### 1 钉钉配置 6 | 钉钉官网:https://oa.dingtalk.com/ 7 | 我们主要获取四个参数:部门名称,AgentID和CorpID和CorpSecret 8 | #### 1.1 注册安装 9 | 注册钉钉企业号,安装手机钉钉略过 10 | ##### 1.1.1 部门设置 11 | 在通信录管理里面设置部门,如下图,我们这里设置的**运维部**,这个名称要记住,在ZABBIX里面要配置这个名称,然后把你需要发送告警的人员添加到这个部门里面 12 | ![](leanote://file/getImage?fileId=58708444d31d9c3103000000) 13 | ##### 1.1.2 应用设置 14 | 在企业应用里面有各种应用,如图,发送告警信息可以给下面的各种应用,如钉邮、钉盘 15 | ![](leanote://file/getImage?fileId=587084ffd31d9c3103000001) 16 | 这里我自己定义了一个应用,服务器报警,记住图中的AgentID,用钉邮、钉盘的AgentID也可以 17 | ![](leanote://file/getImage?fileId=58708580d31d9c3103000002) 18 | ##### 1.1.3 微应用设置 19 | 找到微应用设置,企业应用,CorpID和CorpSecret,点击获取 20 | ![](leanote://file/getImage?fileId=58708661d31d9c3103000003) 21 | ### 2 程序配置 22 | 代码托管到github:https://github.com/bluetom520/dingding 23 | ``` 24 | git clone https://github.com/bluetom520/dingding.git 25 | pip install requests/requests-2.12.4-py2.py3-none-any.whl 26 | cp dingding/* /usr/lib/zabbix/alertscripts/ 27 | chown -R zabbix:zabbix /usr/lib/zabbix/alertscripts/dingding.py 28 | chmod +x /usr/lib/zabbix/alertscripts/dingding.py 29 | chmod a+w /usr/lib/zabbix/alertscripts/config.ini 30 | 31 | ``` 32 | 修改config.ini,把上节获得的三个参数填入,toparty不用填,程序第一次运行会自动获取,web是点击报警信息后跳转的页面,大家用自己,不要老给我发。 33 | ``` 34 | [ding] 35 | corpid = ding31b4af980259953235c2f4657eb6378f 36 | corpsecret = 5tjFK9oKWptDnh473_2hX3Z_DzZoK2uxhQTqzo6tXf7gd5W6zcOdg8yP-FyjnjfJ 37 | agentid = 66029515 38 | toparty = 39 | web = http://192.168.1.199/zabbix/ 40 | ``` 41 | ### 3 ZABBIX配置 42 | #### 3.1 报警媒介类型 43 | 到管理-》报警媒介类型配置我们的钉钉 44 | ![](leanote://file/getImage?fileId=58708903d31d9c3103000004) 45 | #### 3.2 配置用户 46 | 到管理-》用户-》报警媒介-》添加,注意填写收件人为我们之前设置的运维部 47 | ![](leanote://file/getImage?fileId=58708952d31d9c3103000005) 48 | #### 3.3 动作设置 49 | 到配置-》动作-》创建动作(触发器) 50 | - 动作 51 | ![](leanote://file/getImage?fileId=587089ffd31d9c3103000006) 52 | - 条件 53 | ![](leanote://file/getImage?fileId=58708a1dd31d9c3103000007) 54 | ``` 55 | 服务器:{HOST.NAME}: {TRIGGER.NAME}已恢复! 56 | 57 | { 58 | "告警主机":"{HOST.NAME}", 59 | "告警地址":"{HOST.IP}", 60 | "告警时间":"{EVENT.DATE} {EVENT.TIME}", 61 | "恢复时间":"{EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME}", 62 | "告警等级":"{TRIGGER.SEVERITY}", 63 | "告警信息":"{TRIGGER.NAME}", 64 | "监控项目":"{ITEM.NAME}", 65 | "当前状态":"{TRIGGER.STATUS}", 66 | "持续时间":"{EVENT.AGE}", 67 | "事件ID":"{EVENT.ID}", 68 | "监控取值":"{ITEM.LASTVALUE}" 69 | } 70 | 71 | 服务器:{HOST.NAME}发生: {TRIGGER.NAME}故障! 72 | 73 | { 74 | "告警主机":"{HOST.NAME}", 75 | "告警地址":"{HOST.IP}", 76 | "告警时间":"{EVENT.DATE} {EVENT.TIME}", 77 | "告警等级":"{TRIGGER.SEVERITY}", 78 | "告警信息":"{TRIGGER.NAME}", 79 | "监控项目":"{ITEM.NAME}", 80 | "当前状态":"{TRIGGER.STATUS}", 81 | "持续时间":"{EVENT.AGE}", 82 | "事件ID":"{EVENT.ID}", 83 | "监控取值":"{ITEM.LASTVALUE}" 84 | } 85 | ``` 86 | - 操作 87 | ![](leanote://file/getImage?fileId=58708a3ad31d9c3103000008) 88 | 89 | ### 4 效果展现 90 | 故障图 91 | ![](leanote://file/getImage?fileId=58708d18d31d9c3103000009) 92 | 恢复图 93 | ![](leanote://file/getImage?fileId=58708d3ad31d9c310300000b) 94 | 95 | ### 5 docker环境修改 96 | ``` 97 | tar zxvf requests-2.12.4.tar.gz 98 | docker cp requests-2.12.4 zabbix:/usr/local/share/zabbix/alertscripts 99 | docker cp dingding zabbix:/usr/local/share/zabbix/alertscripts 100 | docker exec -it zabbix /bin/bash 101 | cd /usr/local/share/zabbix/alertscripts/requests-2.12.4 102 | rm -rf requests-2.12.4 103 | python setup.py install 104 | cd .. 105 | mv dingding/* . 106 | vi config.ini 107 | exit 108 | docker restart zabbix 109 | 110 | ``` 111 | 112 | ## 具体内容参考:https://note.gitcloud.cc/blog/post/bluetom520/%E9%92%89%E9%92%89%E6%8A%A5%E8%AD%A6%E6%A8%A1%E6%9D%BF 113 | 114 | -------------------------------------------------------------------------------- /conf/BLog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # __author__ = 'bluetom' 4 | 5 | import sys 6 | import logging 7 | from logging.handlers import RotatingFileHandler 8 | import os 9 | 10 | #{{{class ColoredFormatter 11 | class ColoredFormatter(logging.Formatter): 12 | '''A colorful formatter.''' 13 | 14 | def __init__(self, fmt = None, datefmt = None): 15 | logging.Formatter.__init__(self, fmt, datefmt) 16 | # Color escape string 17 | COLOR_RED='\033[1;31m' 18 | COLOR_GREEN='\033[1;32m' 19 | COLOR_YELLOW='\033[1;33m' 20 | COLOR_BLUE='\033[1;34m' 21 | COLOR_PURPLE='\033[1;35m' 22 | COLOR_CYAN='\033[1;36m' 23 | COLOR_GRAY='\033[1;37m' 24 | COLOR_WHITE='\033[1;38m' 25 | COLOR_RESET='\033[1;0m' 26 | 27 | # Define log color 28 | self.LOG_COLORS = { 29 | 'DEBUG': '%s', 30 | 'INFO': COLOR_GREEN + '%s' + COLOR_RESET, 31 | 'WARNING': COLOR_YELLOW + '%s' + COLOR_RESET, 32 | 'ERROR': COLOR_RED + '%s' + COLOR_RESET, 33 | 'CRITICAL': COLOR_RED + '%s' + COLOR_RESET, 34 | 'EXCEPTION': COLOR_RED + '%s' + COLOR_RESET, 35 | } 36 | 37 | def format(self, record): 38 | level_name = record.levelname 39 | msg = logging.Formatter.format(self, record) 40 | 41 | return self.LOG_COLORS.get(level_name, '%s') % msg 42 | #}}} 43 | #{{{class Log 44 | class Log(object): 45 | 46 | ''' 47 | log 48 | ''' 49 | def __init__(self, filename, level="debug", logid="qiueer", mbs=20, count=10, is_console=True): 50 | ''' 51 | mbs: how many MB 52 | count: the count of remain 53 | ''' 54 | try: 55 | self._level = level 56 | #print "init,level:",level,"\t","get_map_level:",self._level 57 | self._filename = filename 58 | self._logid = logid 59 | 60 | self._logger = logging.getLogger(self._logid) 61 | file_path = os.path.split(self._filename)[0] 62 | if not os.path.exists(file_path): 63 | os.makedirs(file_path) 64 | 65 | if not len(self._logger.handlers): 66 | self._logger.setLevel(self.get_map_level(self._level)) 67 | 68 | fmt = '[%(asctime)s] %(levelname)s\n%(message)s' 69 | datefmt = '%Y-%m-%d %H:%M:%S' 70 | formatter = logging.Formatter(fmt, datefmt) 71 | 72 | maxBytes = int(mbs) * 1024 * 1024 73 | file_handler = RotatingFileHandler(self._filename, mode='a',maxBytes=maxBytes,backupCount=count) 74 | self._logger.setLevel(self.get_map_level(self._level)) 75 | file_handler.setFormatter(formatter) 76 | self._logger.addHandler(file_handler) 77 | 78 | if is_console == True: 79 | stream_handler = logging.StreamHandler(sys.stderr) 80 | console_formatter = ColoredFormatter(fmt, datefmt) 81 | stream_handler.setFormatter(console_formatter) 82 | self._logger.addHandler(stream_handler) 83 | 84 | except Exception as expt: 85 | print expt 86 | 87 | def tolog(self, msg, level=None): 88 | try: 89 | level = level if level else self._level 90 | level = str(level).lower() 91 | level = self.get_map_level(level) 92 | if level == logging.DEBUG: 93 | self._logger.debug(msg) 94 | if level == logging.INFO: 95 | self._logger.info(msg) 96 | if level == logging.WARN: 97 | self._logger.warn(msg) 98 | if level == logging.ERROR: 99 | self._logger.error(msg) 100 | if level == logging.CRITICAL: 101 | self._logger.critical(msg) 102 | except Exception as expt: 103 | print expt 104 | 105 | def debug(self,msg): 106 | self.tolog(msg, level="debug") 107 | 108 | def info(self,msg): 109 | self.tolog(msg, level="info") 110 | 111 | def warn(self,msg): 112 | self.tolog(msg, level="warn") 113 | 114 | def error(self,msg): 115 | self.tolog(msg, level="error") 116 | 117 | def critical(self,msg): 118 | self.tolog(msg, level="critical") 119 | 120 | def get_map_level(self,level="debug"): 121 | level = str(level).lower() 122 | #print "get_map_level:",level 123 | if level == "debug": 124 | return logging.DEBUG 125 | if level == "info": 126 | return logging.INFO 127 | if level == "warn": 128 | return logging.WARN 129 | if level == "error": 130 | return logging.ERROR 131 | if level == "critical": 132 | return logging.CRITICAL 133 | #}}} 134 | if __name__ == "__main__": 135 | from BLog import Log 136 | debug=False 137 | logpath = "/tmp/test.log" 138 | logger = Log(logpath,level="warn",is_console=True, mbs=5, count=5) 139 | 140 | 141 | logstr="helloworld" 142 | logger.error(logstr) 143 | logger.info(logstr) 144 | logger.warn(logstr) 145 | -------------------------------------------------------------------------------- /conf/BLog.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/conf/BLog.pyc -------------------------------------------------------------------------------- /conf/INIFILES.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # __author__ = 'TT' 4 | import sys,os,time 5 | import ConfigParser 6 | 7 | 8 | class Config: 9 | def __init__(self, path): 10 | self.path = path 11 | self.cf = ConfigParser.ConfigParser() 12 | self.cf.read(self.path) 13 | 14 | def get(self, field, key): 15 | result = "" 16 | try: 17 | result = self.cf.get(field, key) 18 | except: 19 | result = "" 20 | return result 21 | 22 | def set(self, field, key, value): 23 | try: 24 | self.cf.set(field, key, value) 25 | self.cf.write(open(self.path,'w')) 26 | except: 27 | return False 28 | return True 29 | 30 | 31 | def read_config(config_file_path, field, key): 32 | cf = ConfigParser.ConfigParser() 33 | try: 34 | cf.read(config_file_path) 35 | result = cf.get(field, key) 36 | except: 37 | sys.exit(1) 38 | return result 39 | 40 | 41 | def write_config(config_file_path, field, key, value): 42 | cf = ConfigParser.ConfigParser() 43 | try: 44 | cf.read(config_file_path) 45 | cf.set(field, key, value) 46 | cf.write(open(config_file_path,'w')) 47 | except: 48 | sys.exit(1) 49 | return True 50 | 51 | if __name__ == "__main__": 52 | config_file_path = 'config.ini' 53 | print read_config(config_file_path, 'baseconf', "host") 54 | 55 | write_config(config_file_path, 'baseconf', "host", '192.168.0.101') 56 | 57 | print read_config(config_file_path, 'baseconf', "host") -------------------------------------------------------------------------------- /conf/INIFILES.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/conf/INIFILES.pyc -------------------------------------------------------------------------------- /conf/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # __author__ = 'bluetom' -------------------------------------------------------------------------------- /conf/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/conf/__init__.pyc -------------------------------------------------------------------------------- /conf/file_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # __author__ = 'bluetom' 4 | 5 | """Package for operations. 6 | """ 7 | 8 | import os 9 | if __name__ == '__main__': 10 | import sys 11 | root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 12 | sys.path.insert(0, root_path) 13 | 14 | import re 15 | import shlex 16 | 17 | import subprocess 18 | import sys 19 | import linecache 20 | reload(sys) 21 | sys.setdefaultencoding('utf8') 22 | 23 | # 需要修改,配置文件位置 24 | CONFIG_CFG = '/etc/zabbix/zabbix_agentd.conf' 25 | 26 | #{{{loadconfig 27 | 28 | 29 | def loadconfig(cfgfile=None, detail=False): 30 | """Read config file and parse config item to dict. 31 | """ 32 | if not cfgfile: 33 | cfgfile = CONFIG_CFG 34 | 35 | settings = {} 36 | with open(cfgfile) as f: 37 | for line_i, line in enumerate(f): 38 | # line_i[行号],line[每行内容] 39 | 40 | # 删除空白符(包括'\n', '\r', '\t', ' ') 41 | line = line.strip() 42 | 43 | # 跳过空行和注释('# '开头的) 44 | if not line or line.startswith('# '): 45 | continue 46 | 47 | # detect if it's commented 48 | if line.startswith('#'): 49 | line = line.strip('#') 50 | commented = True 51 | if not detail: 52 | continue 53 | else: 54 | commented = False 55 | 56 | # 将行以第一个'='分割 57 | ######################################### 58 | fs = re.split('=', line, 1) 59 | if len(fs) != 2: 60 | continue 61 | 62 | item = fs[0].strip() 63 | value = fs[1].strip() 64 | 65 | if settings.has_key(item): 66 | if detail: 67 | count = settings[item]['count'] + 1 68 | if not commented: 69 | settings[item] = detail and { 70 | 'file': cfgfile, 71 | 'line': line_i, 72 | 'value': value, 73 | 'commented': commented, 74 | } or value 75 | else: 76 | count = 1 77 | settings[item] = detail and { 78 | 'file': cfgfile, 79 | 'line': line_i, 80 | 'value': fs[1].strip(), 81 | 'commented': commented, 82 | } or value 83 | if detail: 84 | settings[item]['count'] = count 85 | 86 | return settings 87 | 88 | #}}} 89 | #{{{cfg_get 90 | 91 | 92 | def cfg_get(item, detail=False, config=None): 93 | """Get value of a config item. 94 | """ 95 | if not config: 96 | config = loadconfig(detail=detail) 97 | if config.has_key(item): 98 | return config[item] 99 | else: 100 | return None 101 | #}}} 102 | #{{{cfg_set 103 | 104 | 105 | def cfg_set(item, value, commented=False, config=None): 106 | """Set value of a config item. 107 | 如果可以获取到key,则对key后的item进行修改 108 | 如果获取不到key,则直接在配置文件后进行追加一行 109 | """ 110 | cfgfile = CONFIG_CFG 111 | v = cfg_get(item, detail=True, config=config) 112 | # print v 113 | 114 | if v: 115 | # detect if value change 116 | if v['commented'] == commented and v['value'] == value: 117 | return True 118 | 119 | # empty value should be commented 120 | # 如果有key,但是传的value值为空,会将此行进行注释 121 | if value == '': 122 | commented = True 123 | 124 | # replace item in line 125 | lines = [] 126 | with open(v['file']) as f: 127 | for line_i, line in enumerate(f): 128 | if line_i == v['line']: 129 | # 对没注释的行进行操作 130 | if not v['commented']: 131 | # 检测是否需要注释 132 | if commented: 133 | if v['count'] > 1: 134 | # delete this line, just ignore it 135 | pass 136 | else: 137 | # comment this line 138 | ######################################### 139 | # lines.append('#%s=%s\n' % (item, value)) 140 | lines.append('#%s\n' % (line,)) 141 | else: 142 | ######################################### 143 | lines.append('%s=%s\n' % (item, value)) 144 | else: 145 | if commented: 146 | # do not allow change comment value 147 | lines.append(line) 148 | pass 149 | else: 150 | # append a new line after comment line 151 | # lines.append(line) 152 | ######################################### 153 | lines.append('%s=%s\n' % (item, value)) 154 | else: 155 | lines.append(line) 156 | with open(v['file'], 'w') as f: 157 | f.write(''.join(lines)) 158 | else: 159 | # append to the end of file 160 | with open(v['file'], 'a') as f: 161 | f.write('\n%s%s = %s\n' % (commented and '#' or '', item, value)) 162 | 163 | return True 164 | #}}} 165 | if __name__ == '__main__': 166 | # import pprint 167 | # pp = pprint.PrettyPrinter(indent=4) 168 | # loadconfig() 169 | # pp.pprint(loadconfig()) 170 | print cfg_get('Hostname') #有#号的不显示 171 | # print cfg_get('Subsystem', detail=True) #detail显示细节 172 | # print cfg_set('Subsystem','') #直接注释 173 | # print cfg_set('Subsystem', '44444333') #设置值 174 | # # print cfg_set('Subsystem', 'sftp\t/usr/libexec/openssh/sftp-server',commented=False) 175 | # print cfg_get('Subsystem', detail=True) #detail显示细节 176 | 177 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [ding] 2 | corpid = ding31b4af980259953235c2f4657eb6378f 3 | corpsecret = 5tjFK9oKWptDnh473_2hX3Z_DzZoK2uxhQTqzo6tXf7gd5W6zcOdg8yP-FyjnjfJ 4 | agentid = 66029515 5 | toparty = 6 | web = http://192.168.1.199/zabbix/ 7 | 8 | -------------------------------------------------------------------------------- /dingding.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | # __author__ = '懒懒的天空' 4 | 5 | import requests 6 | import sys 7 | import json 8 | from conf.INIFILES import read_config, write_config 9 | import os 10 | import datetime 11 | from conf.BLog import Log 12 | reload(sys) 13 | sys.setdefaultencoding('utf-8') 14 | 15 | 16 | class Dingtalk(object): 17 | def __init__(self, corpid, corpsecret): # 初始化的时候需要获取corpid和corpsecret,需要从管理后台获取 18 | self.__params = { 19 | 'corpid': corpid, 20 | 'corpsecret': corpsecret 21 | } 22 | 23 | self.url_get_token = 'https://oapi.dingtalk.com/gettoken' 24 | self.url_get_dept_list = 'https://oapi.dingtalk.com/department/list' 25 | self.url_get_dept_detail = 'https://oapi.dingtalk.com/department/get' 26 | self.url_create_dept = 'https://oapi.dingtalk.com/department/create' 27 | self.url_delete_dept = 'https://oapi.dingtalk.com/department/delete' 28 | self.url_get_user_id_by_unionid = 'https://oapi.dingtalk.com/user/getUseridByUnionid' 29 | self.url_get_user_detail = 'https://oapi.dingtalk.com/user/get' 30 | self.url_send_message = 'https://oapi.dingtalk.com/message/send_to_conversation' 31 | self.url_send = 'https://oapi.dingtalk.com/message/send' 32 | self.url_get_user_count = 'https://oapi.dingtalk.com/user/get_org_user_count' 33 | self.__token = self.__get_token() 34 | self.__token_params = { 35 | 'access_token': self.__token 36 | } 37 | 38 | def __raise_error(self, res): 39 | raise Exception('error code: %s,error message: %s' % (res.json()['errcode'], res.json()['errmsg'])) 40 | global senderr 41 | sendstatus = False 42 | senderr = 'error code: %s,error message: %s' % (res.json()['errcode'], res.json()['errmsg']) 43 | 44 | def __get_token(self): 45 | # print self.url_get_token 46 | headers = {'content-type': 'application/json'} 47 | res = requests.get(self.url_get_token, headers=headers, params=self.__params) 48 | 49 | try: 50 | return res.json()['access_token'] 51 | except: 52 | self.__raise_error(res.content) 53 | 54 | def get_dept_list(self): 55 | # print self.url_get_dept_list 56 | res = requests.get(self.url_get_dept_list, params=self.__token_params) 57 | try: 58 | return res.json()['department'] 59 | except: 60 | self.__raise_error(res.content) 61 | 62 | def get_dept_detail(self, dept_id): 63 | params = self.__token_params 64 | params.update({'id': dept_id}) 65 | res = requests.get(self.url_get_dept_detail, params=params) 66 | try: 67 | return res.json() 68 | except: 69 | self.__raise_error(res) 70 | 71 | def create_dept(self, name, parentid, orderid, createdeptgroup=False): 72 | payload = self.__token_params 73 | payload.update({ 74 | 'name': name, 75 | 'parentid': parentid, 76 | 'orderid': orderid, 77 | 'createDeptGroup': createdeptgroup, 78 | }) 79 | res = requests.post(self.url_create_dept, data=payload) 80 | try: 81 | return res.json()['id'] 82 | except: 83 | self.__raise_error(res) 84 | 85 | def delete_dept(self, dept_id): 86 | params = self.__token_params 87 | params.update({'id': dept_id}) 88 | res = requests.get(self.url_delete_dept, params=params) 89 | try: 90 | return res.json()['errcode'] 91 | except: 92 | self.__raise_error(res) 93 | 94 | def get_userid_by_unionid(self, unionid): 95 | params = self.__token_params 96 | params.update({'unionid': unionid}) 97 | res = requests.get(self.url_get_user_id_by_unionid, params=params) 98 | try: 99 | return res.json()['userid'] 100 | except: 101 | self.__raise_error(res) 102 | 103 | def get_user_detail(self, userid): 104 | params = self.__token_params 105 | params.update({'userid': userid}) 106 | res = requests.get(self.url_get_user_detail, params=params) 107 | try: 108 | return res.json() 109 | except: 110 | self.__raise_error(res) 111 | 112 | def send_message(self, agentid, messages, userid='', toparty=''): 113 | payload = { 114 | 'touser': userid, 115 | 'toparty': toparty, 116 | 'agentid': agentid, 117 | 118 | "msgtype": "oa", 119 | "oa": messages 120 | } 121 | headers = {'content-type': 'application/json'} 122 | params = self.__token_params 123 | res = requests.post(self.url_send, headers=headers, params=params, data=json.dumps(payload)) 124 | try: 125 | return res.json() 126 | except: 127 | self.__raise_error(res) 128 | 129 | def get_user_count(self, only_active=0): 130 | params = self.__token_params 131 | params.update({'onlyActive': only_active}) 132 | res = requests.get(self.url_get_user_count, params=params) 133 | try: 134 | return res.json()['count'] 135 | except: 136 | self.__raise_error(res) 137 | 138 | 139 | def main(send_to, subject, content): 140 | try: 141 | global sendstatus 142 | global senderr 143 | data = '' 144 | messages = {} 145 | body = {} 146 | config_file_path = get_path() 147 | CorpID = read_config(config_file_path, 'ding', "CorpID") 148 | CorpSecret = read_config(config_file_path, 'ding', "CorpSecret") 149 | agentid = read_config(config_file_path, 'ding', "agentid") 150 | web = read_config(config_file_path, 'ding', "web") 151 | toparty = read_config(config_file_path, 'ding', "toparty") 152 | content = json.loads(content) 153 | messages["message_url"] = web 154 | form = [] 155 | 156 | body["content"] = subject 157 | body["rich"] = {"num": content[u'监控取值']} 158 | 159 | if content[u'当前状态'] == 'PROBLEM': 160 | messages["head"] = { 161 | "bgcolor": "DBE97659", #前两位表示透明度 162 | "text": "服务器故障" 163 | } 164 | body["title"] = "服务器故障" 165 | form.append({'key': '告警等级:', 'value': content[u'告警等级']}) 166 | form.append({'key': '告警时间:', 'value': content[u'告警时间']}) 167 | form.append({'key': '告警地址:', 'value': content[u'告警地址']}) 168 | form.append({'key': '持续时间:', 'value': content[u'持续时间']}) 169 | form.append({'key': '监控项目:', 'value': content[u'监控项目']}) 170 | body['form'] = form 171 | body["author"] = content[u'告警主机'] + '故障(' + content[u'事件ID']+ ')' 172 | else: 173 | messages["head"] = { 174 | "bgcolor": "DB00AA00", #前两位表示透明度 175 | "text": "服务器恢复" 176 | } 177 | body["title"] = "服务器恢复" 178 | form.append({'key': '告警等级:', 'value': content[u'告警等级']}) 179 | form.append({'key': '恢复时间:', 'value': content[u'恢复时间']}) 180 | form.append({'key': '告警地址:', 'value': content[u'告警地址']}) 181 | form.append({'key': '持续时间:', 'value': content[u'持续时间']}) 182 | form.append({'key': '监控项目:', 'value': content[u'监控项目']}) 183 | body['form'] = form 184 | body["author"] = content[u'告警主机'] + '恢复(' + content[u'事件ID']+ ')' 185 | messages['body'] = body 186 | ding = Dingtalk(CorpID, CorpSecret) 187 | if toparty == '': 188 | res = ding.get_dept_list() 189 | # topartname = read_config(config_file_path, 'ding', "topartname") 190 | for item in res: 191 | if item['name'] == send_to: 192 | toparty = item['id'] 193 | write_config(config_file_path, 'ding', "toparty", toparty) 194 | # print ding.get_dept_list()[1] #65875499钉邮 66029515 自定义 65875504钉盘 195 | data = ding.send_message(toparty=toparty, agentid=agentid, messages=messages) 196 | sendstatus = True 197 | except Exception, e: 198 | senderr = str(e) 199 | sendstatus = False 200 | 201 | logwrite(sendstatus, data) 202 | 203 | 204 | def get_path(): 205 | path = os.path.dirname(os.path.abspath(sys.argv[0])) 206 | config_path = path + '/config.ini' 207 | return config_path 208 | 209 | 210 | def logwrite(sendstatus, content): 211 | logpath = '/var/log/zabbix/dingding' 212 | if not sendstatus: 213 | content = senderr 214 | t = datetime.datetime.now() 215 | daytime = t.strftime('%Y-%m-%d') 216 | daylogfile = logpath+'/'+str(daytime)+'.log' 217 | logger = Log(daylogfile, level="info", is_console=False, mbs=5, count=5) 218 | os.system('chown zabbix.zabbix {0}'.format(daylogfile)) 219 | logger.info(content) 220 | 221 | if __name__ == "__main__": 222 | if len(sys.argv) > 1: 223 | send_to = sys.argv[1] 224 | subject = sys.argv[2] 225 | content = sys.argv[3] 226 | logwrite(True, content) 227 | main(send_to, subject, content) 228 | 229 | -------------------------------------------------------------------------------- /log/2017-01-06.log: -------------------------------------------------------------------------------- 1 | INFO:root:********************************************************************************************************************************** 2 | DEBUG:root:{u'invaliduser': u'', u'invalidparty': u'', u'errcode': 0, u'errmsg': u'ok', u'messageId': u'44004de5b8683acab9b1700f56bfb880'} 3 | INFO:root:********************************************************************************************************************************** 4 | DEBUG:root:{u'forbiddenUserId': u'manager1522|07523835553556498', u'invalidparty': u'', u'errcode': 0, u'invaliduser': u'', u'messageId': u'b7277e1ee317357389a5290358aaf0ec', u'errmsg': u'ok'} 5 | INFO:root:********************************************************************************************************************************** 6 | DEBUG:root:{u'forbiddenUserId': u'manager1522|07523835553556498', u'invalidparty': u'', u'errcode': 0, u'invaliduser': u'', u'messageId': u'51b94e481a493951848ce6956bf1fb44', u'errmsg': u'ok'} 7 | INFO:root:********************************************************************************************************************************** 8 | DEBUG:root:{u'invaliduser': u'', u'invalidparty': u'', u'errcode': 0, u'errmsg': u'ok', u'messageId': u'd3fc2bcb9486345fa0684a6e63da0988'} 9 | -------------------------------------------------------------------------------- /requests/requests-2.12.4-py2.py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/requests/requests-2.12.4-py2.py3-none-any.whl -------------------------------------------------------------------------------- /requests/requests-2.12.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/requests/requests-2.12.4.tar.gz -------------------------------------------------------------------------------- /恢复.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/恢复.jpg -------------------------------------------------------------------------------- /报警.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bluetom520/dingding/a04de8967326594b1b0e8bb9ca6c4b2a629a4130/报警.jpg --------------------------------------------------------------------------------