钉钉告警推送@多人脚本

原创作者:运维工程师 谢晋

钉钉告警推送@多人脚本

  1. 钉钉脚本编写

    vi /usr/lib/zabbix/alertscripts/dingding.py

    #!/usr/bin/python3

    -- coding: utf-8 --

    import requests
    import json
    import sys
    import time
    import hmac
    import hashlib
    import base64
    import urllib.parse
    import logging
    import os
    from datetime import datetime

    配置日志系统

    def setup_logging():
    log_file = '/var/log/zabbix/dingding.log'

    复制代码
     # 创建日志目录(如果不存在)
     log_dir = os.path.dirname(log_file)
     if not os.path.exists(log_dir):
         try:
             os.makedirs(log_dir, mode=0o755, exist_ok=True)
             logging.info(f"创建日志目录: {log_dir}")
         except Exception as e:
             print(f"无法创建日志目录 {log_dir}: {e}")
             sys.exit(1)
    
     # 配置日志格式和级别
     logging.basicConfig(
         level=logging.INFO,
         format='%(asctime)s - %(levelname)s - %(message)s',
         handlers=[
             logging.FileHandler(log_file),
             logging.StreamHandler(sys.stdout)  # 同时输出到控制台
         ]
     )
    
     # 设置日志文件权限(如果文件已存在)
     try:
         if os.path.exists(log_file):
             os.chmod(log_file, 0o644)
     except Exception as e:
         print(f"无法设置日志文件权限: {e}")

    def msg(content, at_mobiles=None, is_at_all=False):
    # 记录发送的消息
    logging.info(f"准备发送消息: {content}")
    logging.info(f"@人员: {at_mobiles}, @所有人: {is_at_all}")

    复制代码
     # 钉钉机器人地址
     dingding_url = "https://oapi.dingtalk.com/robot/send?access_token=9c1cbf04a1f37bd44ce736664b6f2f5f2a488fff3c8fbb65538278987aaad8c2"
     # 钉钉的加签
     secret = 'SECf654831ae1d4d4ab32cbd93e7d11e491113c96c986f2110beb29e2e32dacd324'
    
     try:
         # 加签算法
         timestamp = str(round(time.time() * 1000))
         secret_enc = secret.encode('utf-8')
         string_to_sign = '{}\n{}'.format(timestamp, secret)
         string_to_sign_enc = string_to_sign.encode('utf-8')
         hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
         sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
    
         headers = {'Content-Type': 'application/json;charset=utf-8'}
         api_url = dingding_url + "&timestamp={}&sign={}".format(timestamp, sign)
    
         # 构建@人信息
         at_info = {}
         if at_mobiles:
             at_info["atMobiles"] = at_mobiles
         if is_at_all:
             at_info["isAtAll"] = True
         
         # 在消息内容中添加@信息
         formatted_content = content
         if at_mobiles:
             for mobile in at_mobiles:
                 # 在消息内容中添加 @手机号
                 formatted_content += f" @{mobile}"
         
         json_text = {
             "msgtype": "markdown",
             "markdown": {
                 "title": "zabbix告警",
                 "text": formatted_content  # 使用包含@信息的格式化内容
             },
             "at": at_info
         }
    
         # 记录请求详情(隐藏敏感信息)
         logging.info(f"请求URL: {dingding_url.split('?')[0]}...")
         logging.info(f"请求数据: {json.dumps(json_text, ensure_ascii=False)}")
    
         # 发送请求
         start_time = time.time()
         response = requests.post(api_url, json.dumps(json_text), headers=headers, timeout=30)
         response_time = round((time.time() - start_time) * 1000, 2)
    
         # 记录响应
         logging.info(f"响应状态码: {response.status_code}")
         logging.info(f"响应时间: {response_time}ms")
         logging.info(f"响应内容: {response.text}")
    
         # 检查响应
         if response.status_code == 200:
             result = response.json()
             if result.get('errcode') == 0:
                 logging.info("消息发送成功")
                 return True
             else:
                 error_msg = result.get('errmsg', '未知错误')
                 logging.error(f"消息发送失败: {error_msg}")
                 return False
         else:
             logging.error(f"HTTP请求失败: {response.status_code}")
             return False
    
     except requests.exceptions.Timeout:
         logging.error("请求超时:30秒内未收到响应")
         return False
     except requests.exceptions.ConnectionError:
         logging.error("网络连接错误:无法连接到钉钉服务器")
         return False
     except requests.exceptions.RequestException as e:
         logging.error(f"网络请求异常: {str(e)}")
         return False
     except Exception as e:
         logging.error(f"发送消息时发生未知异常: {str(e)}")
         return False

    def main():
    # 设置日志
    setup_logging()

    复制代码
     # 记录脚本启动
     logging.info("=" * 60)
     logging.info("钉钉通知脚本启动")
     logging.info(f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
     logging.info(f"命令行参数数量: {len(sys.argv)}")
    
     # 解析参数
     at_mobiles = None
     is_at_all = False
     message_content = ""
     
     # 解析参数
     i = 1
     while i < len(sys.argv):
         if sys.argv[i] == "--at" and i + 1 < len(sys.argv):
             at_mobiles = sys.argv[i + 1].split(',')
             i += 2
         elif sys.argv[i] == "--at-all":
             is_at_all = True
             i += 1
         else:
             # 处理带空格的消息内容
             if message_content:
                 message_content += " " + sys.argv[i]
             else:
                 message_content = sys.argv[i]
             i += 1
    
     # 默认@多个人(这里设置多个手机号)
     if at_mobiles is None:
         at_mobiles = ["13800138000", "13900139000", "15000150000", "15100151000"]  # 替换为实际的多个手机号
         logging.info(f"使用默认@多人: {at_mobiles}")
    
     try:
         if not message_content.strip():
             logging.error("错误: 消息内容为空")
             print("错误: 消息内容为空")
             sys.exit(1)
    
         # 发送消息
         success = msg(message_content, at_mobiles, is_at_all)
    
         if success:
             logging.info("钉钉通知脚本执行成功")
             print("消息发送成功")
             sys.exit(0)
         else:
             logging.error("钉钉通知脚本执行失败")
             print("消息发送失败")
             sys.exit(1)
    
     except KeyboardInterrupt:
         logging.warning("脚本被用户中断")
         print("\n操作已取消")
         sys.exit(1)
     except Exception as e:
         logging.exception("脚本执行过程中发生未预期的异常")
         print(f"错误: {str(e)}")
         sys.exit(1)

    if name == 'main':
    main()

相关推荐
这个DBA有点耶3 小时前
DELETE没删多少数据,表却越来越大——InnoDB删除空洞与页合并失效的根因
数据库·mysql·代码规范
卷无止境4 小时前
一人公司的全栈选型指南:让 AI Agent 写得快,也让生意跑得稳
后端·python
ly76896 小时前
PostgreSQL 备份恢复策略:pg_basebackup、WAL 归档与 PITR 在企业 Java 系统中的落地
java·数据库·postgresql·pitr·pg_basebackup·wal 归档
滴啦嘟啦哒6 小时前
【具身笔记】【π0.5 微调与仿真】五、在MuJoCo中实现可视化仿真
笔记·python·vla·具身
全栈练习生6 小时前
AI安全与红队测试
python·ai
小羊没烦恼!7 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
费曼学习法7 小时前
把「发送成功」当「送达」?我排查 IM 长消息被静默吞掉的完整过程
python·ai编程·自动化运维
鸽鸽程序猿7 小时前
【项目】【在线判题系统】b端题目增删改
开发语言·python
注塑机数据采集联网7 小时前
如何解决注塑机数据采集联网对接MES系统数据WiFi传输数据不稳问题
大数据·数据库·物联网·数据挖掘·数据分析
黄俊懿7 小时前
【架构师从入门到进阶】第六章:服务集群优化——第八节:线程数公式
java·服务器·数据库·分布式·性能优化·架构·系统架构