news 2026/10/17 1:18:40

PRTG告警升级:企业微信机器人图文混排推送实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PRTG告警升级:企业微信机器人图文混排推送实战

1. 从一条“挤成一坨”的告警说起:为什么要折腾图文混排

很多做运维的朋友都有过这种体验:凌晨两点,手机突然连续震动,打开一看,PRTG 发来的告警邮件里全是密密麻麻的纯文本——设备名、传感器 ID、状态码、时间戳挤成一团,关键信息得靠肉眼一行行扒。更麻烦的是,邮件里没有图表,你根本不知道这个端口流量是突然飙到 900Mbps 还是已经持续高位运行了半小时。等你远程连上去查,故障可能已经自愈,或者更糟——已经扩散了。

这就是我当初决定改造告警推送的直接原因。PRTG 本身是个很扎实的监控系统,传感器类型丰富、阈值策略灵活,但它的原生通知渠道在“信息密度”和“可读性”上确实偏保守。纯文本邮件适合归档,不适合在手机上快速决策。而企业微信作为日常办公入口,几乎人人都在用,如果能把它变成告警终端,并且推送的是图文混排的消息——左边是状态摘要,右边是趋势图,底部带一键跳转链接——那运维响应效率会有质的提升。

这篇内容就是围绕这个目标展开的:让 PRTG 通过企业微信机器人,推送带图表、带颜色标识、带跳转链接的图文混排告警消息。适合已经部署了 PRTG、对企业微信机器人接口有基本了解、想进一步提升告警可读性的运维人员。哪怕你之前没写过一行 PRTG 自定义脚本,跟着思路走也能落地。

需要先明确一个边界:企业微信机器人支持的消息类型里,markdown类型是支持图片渲染的,但图片必须是公网可访问的 URL,不能直接上传本地文件。这一点决定了整个方案的架构走向,后面会详细拆解。

2. 企业微信机器人消息类型的能力边界与选型逻辑

2.1 为什么不用文本消息和图文卡片

企业微信机器人目前支持text、markdown、image、news、file等几种消息类型。很多人第一反应是用news类型,因为它天生就是图文卡片。但实测下来,news有几个硬伤:一是卡片布局固定,标题、描述、图片的位置不能自由调整;二是每次只能推一条主卡片,多条告警会变成轮播,反而增加点击成本;三是news的图片同样需要公网 URL,且对图片尺寸比例有隐性要求,稍不注意就拉伸变形。

text类型就更不用说了,纯文本,连颜色都标不了。markdown类型则灵活得多——它支持标题、加粗、字体颜色(通过<font color="info/warning/comment">标签)、链接、引用块,最关键的是支持![alt](url)图片语法。这意味着我们可以把告警的严重程度用颜色区分,把趋势图直接嵌在消息体里,把设备详情页做成可点击链接。一条消息就能承载“状态判断 + 趋势感知 + 快速跳转”三个动作。

所以选型结论很明确:用markdown消息类型作为载体。它的表达能力足够强,又不像news那样受模板限制。

2.2 图片公网化是整个方案的命门

markdown消息里的图片,企业微信服务器会去主动拉取。如果你的图片地址是http://192.168.1.100/chart.png这种内网地址,企业微信的服务器根本访问不到,消息里只会显示一个裂图占位符。这是整个方案里最容易翻车的地方,没有之一。

那怎么解决?常见的有三条路:

方案原理优点缺点
图床服务把 PRTG 生成的图表上传到公网图床,拿到 URL 再推送实现简单,不依赖本地网络依赖第三方服务,有隐私顾虑,免费图床可能限流
自建公网 Web 服务在内网出口做端口映射,让 PRTG 的图表 URL 可被公网访问数据不出内网,可控性强需要公网 IP 或域名,有安全暴露面
反向代理 + 对象存储图表先存到对象存储,通过 CDN 或存储桶公网地址访问稳定、可扩展架构复杂度上升,成本增加

我最终选的是自建公网 Web 服务这条路,因为告警图表里可能包含设备名称、IP 等敏感信息,走第三方图床心里不踏实。具体做法是在 PRTG 服务器上跑一个轻量级的 Web 服务,专门用来输出图表图片,然后通过网关把特定路径映射到公网。这样企业微信拉取图片时走的是我自己的域名,数据全程可控。

注意:如果你的环境没有公网出口,或者公司安全策略不允许端口映射,那图床方案是唯一选择。但务必选支持私有存储桶的对象存储服务,不要用公开图床。

2.3 PRTG 的告警触发机制怎么和脚本对接

PRTG 的通知机制是这样的:当传感器状态变化触发通知时,PRTG 会执行一个“通知模板”里配置的动作。这些动作可以是发邮件、发短信、执行 HTTP 请求、或者执行一个外部程序/脚本。我们要做的就是让 PRTG 在告警时调用一个脚本,把传感器 ID、状态、消息等参数传进去,脚本再去拉取图表、组装 markdown、调用企业微信机器人接口。

PRTG 传递参数的方式有两种:一是通过命令行参数,二是通过环境变量。命令行参数更直观,但 PRTG 的占位符语法需要记牢,比如%sensorid、%status、%message这些。环境变量则适合传递大量数据,但调试起来稍微麻烦。我建议用命令行参数,因为脚本里一眼就能看出哪个参数对应哪个字段。

这里有个细节:PRTG 执行脚本时的工作目录默认是 PRTG 的安装目录,不是脚本所在目录。所以脚本里所有文件路径都要用绝对路径,否则读写临时文件时会找不到位置。这个坑我踩过,脚本手动跑没问题,一放到 PRTG 里就报“文件不存在”,排查了半天才发现是工作目录的问题。

3. 从零搭建:脚本、图表服务与机器人接口的串联

3.1 环境准备与依赖清单

先列一下我这边实际用到的环境,你可以根据自己情况调整:

  • PRTG 版本:21.x 以上(低版本的通知脚本接口略有差异,但核心逻辑一致)
  • 脚本语言:Python 3.8+(也可以用 PowerShell,但 Python 的 requests 库处理 HTTP 请求更顺手)
  • Web 服务:Flask 或 FastAPI 起一个轻量服务,专门输出图表
  • 企业微信:一个群机器人 Webhook 地址
  • 公网映射:域名 + 反向代理(Nginx 或 Caddy 均可)

Python 依赖只有两个核心库:requests用于调用企业微信接口和 PRTG API,Pillow用于在图表上叠加状态水印(可选,但强烈建议加,后面会讲为什么)。安装命令很简单:

pip install requests Pillow flask

企业微信机器人的 Webhook 地址格式是这样的:

https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=你的机器人key

这个 key 在群机器人设置里生成,注意保管好,泄露了别人就能往你群里发消息。

3.2 图表服务的实现:让 PRTG 的图表能被公网拉取

PRTG 本身提供了图表接口,格式大致是:

https://prtg你的地址/api/chart.png?type=live&id=传感器ID&width=800&height=400

这个接口返回的是 PNG 图片,但需要认证。如果你直接用这个 URL 给企业微信,企业微信服务器访问时会因为认证失败拿不到图。所以我的做法是:在本地 Web 服务里代理这个请求,由服务端完成认证,再把图片流返回给公网。

Flask 端的核心代码大概长这样:

from flask import Flask, Response import requests app = Flask(__name__) PRTG_BASE = "https://你的prtg地址" PRTG_USER = "你的用户名" PRTG_PASS = "你的密码" @app.route("/chart/<int:sensor_id>") def chart(sensor_id): url = f"{PRTG_BASE}/api/chart.png" params = { "type": "live", "id": sensor_id, "width": 800, "height": 400 } resp = requests.get( url, params=params, auth=(PRTG_USER, PRTG_PASS), verify=False # 如果 PRTG 用的是自签证书 ) return Response(resp.content, mimetype="image/png")

这样企业微信只需要访问https://你的域名/chart/1234就能拿到传感器 1234 的实时图表。注意verify=False那行,如果 PRTG 用的是自签名证书,不加这个会报 SSL 错误。生产环境建议把 PRTG 的 CA 证书导入信任链,而不是简单粗暴地关掉验证。

提示:图表尺寸建议用 800x400,这个比例在企业微信里显示最舒服。太宽会被压缩,太高会占满屏幕。

3.3 告警脚本的核心逻辑拆解

脚本要做的事情按顺序是:接收 PRTG 传来的参数 → 判断状态 → 拉取图表 → 组装 markdown → 推送企业微信。我把它拆成几个函数,方便调试。

第一步:解析参数。PRTG 调用脚本时传进来的参数类似这样:

python alert.py --sensorid 1234 --status Down --message "端口流量异常" --device "核心交换机"

用argparse解析即可。这里要注意,PRTG 的%message里可能包含空格和特殊字符,所以脚本里要对参数做转义处理,否则 markdown 渲染会乱。

第二步:状态映射颜色。企业微信 markdown 支持三种颜色标签:info(绿色)、warning(橙色)、comment(灰色)。我把 PRTG 的状态映射成:

  • Down/Error→<font color="warning">橙色,表示需要立即处理
  • Warning→<font color="comment">灰色,表示需要关注
  • Up→<font color="info">绿色,表示恢复正常

这个映射逻辑看似简单,但实际用起来很关键。橙色消息在手机通知栏里一眼就能认出来,比纯文本的“Down”直观得多。

第三步:组装 markdown 消息体。这是整个脚本的核心。我用的模板大致是这样的:

markdown = f""" ### <font color="{color}">{status_icon} {device_name} 告警</font> **传感器:** {sensor_name} **状态:** <font color="{color}">{status}</font> **消息:** {message} **时间:** {timestamp} ![趋势图](https://你的域名/chart/{sensor_id}) [点击查看详情](https://你的prtg地址/sensor.htm?id={sensor_id}) """

这里有几个细节值得展开说。status_icon我用的是文字符号而不是 emoji,因为企业微信 markdown 对 emoji 的支持不稳定,有时候会显示成乱码。趋势图的 URL 就是前面 Flask 服务暴露的地址。最后的详情链接直接指向 PRTG 的传感器页面,点一下就能跳转。

第四步:调用企业微信接口。这一步最简单,一个 POST 请求搞定:

def send_wechat(webhook, markdown): payload = { "msgtype": "markdown", "markdown": {"content": markdown} } requests.post(webhook, json=payload)

但要注意,企业微信对消息体大小有限制,markdown 内容不能超过 4096 字节。如果告警消息特别长(比如包含大量堆栈信息),需要做截断处理。我的做法是只保留前 500 个字符,后面加省略号,完整信息让用户点详情链接去看。

3.4 在 PRTG 里配置通知模板

脚本写好后,要在 PRTG 里建一个通知模板。路径是:Setup → Notification Templates → Add New Notification Template。

关键配置项:

  • Notification Type:选 “Execute Program”
  • Program File:填 Python 解释器的绝对路径,比如C:\Python38\python.exe
  • Parameters:填脚本路径和参数占位符,比如C:\scripts\alert.py --sensorid %sensorid --status %status --message "%message" --device "%device"
  • Working Directory:填脚本所在目录,避免前面说的路径问题

这里有个容易忽略的点:PRTG 执行外部程序时,默认是以 PRTG 服务的运行账户身份执行的。如果 PRTG 服务用的是 Local System 账户,而你的脚本需要访问网络或读写特定目录,可能会权限不足。建议单独建一个服务账户,给它脚本目录的读写权限和网络访问权限。

配置好通知模板后,还要在传感器的通知触发里勾选这个模板。可以针对单个传感器配置,也可以在分组或根级别配置,让所有传感器继承。我建议先在测试传感器上验证,跑通了再批量应用。

4. 实测中遇到的五个坑与排查链路

4.1 图片裂图:企业微信拉不到图的第一反应

第一次推送时,消息文字部分正常显示,但图片位置是一个灰色的裂图图标。排查思路是这样的:

先确认图片 URL 在公网能不能访问。用手机 4G 网络(不连公司 WiFi)打开https://你的域名/chart/1234,如果打不开,说明公网映射有问题。检查 Nginx 配置、防火墙规则、域名解析。如果公网能打开但企业微信里还是裂图,那可能是企业微信服务器拉取超时。企业微信对图片拉取有超时限制,如果 PRTG 图表生成太慢(比如查询大量历史数据),企业微信会放弃等待。

解决办法是给图表服务加缓存。第一次请求后把图片缓存 30 秒,后续请求直接返回缓存,这样企业微信拉取时几乎是瞬时的。Flask 里可以用flask-caching或者简单地用一个字典做内存缓存。

4.2 颜色标签不生效:markdown 语法的一个隐藏规则

企业微信 markdown 的颜色标签写法是<font color="info">文字</font>,但实测发现,如果这个标签嵌套在标题###里面,有时候不生效。比如:

### <font color="warning">告警</font>

这种写法在部分企业微信版本里会直接显示原始标签文本。解决办法是把颜色标签放在标题外面,或者用加粗代替标题:

**<font color="warning">告警</font>**

我后来统一改成加粗 + 颜色标签的组合,兼容性最好。

4.3 消息重复推送:PRTG 的通知抑制机制没配好

测试阶段发现,一个传感器状态抖动时,企业微信里会连续收到好几条告警。这是因为 PRTG 默认在状态每次变化时都触发通知,而网络抖动会导致状态在 Up 和 Warning 之间反复横跳。

PRTG 本身有“通知抑制”功能,在通知模板里可以设置Send Notification Only If Sensor Is In This Condition For At Least X Minutes。我一般设成 5 分钟,这样短暂抖动不会触发告警,只有持续 5 分钟以上的异常才推送。这个值根据业务重要性调整,核心设备可以设短一点,边缘设备设长一点。

另外,脚本里也可以加一个简单的去重逻辑:记录上次推送的传感器 ID 和状态,如果 5 分钟内相同传感器相同状态已经推过,就跳过。这个逻辑用 Redis 或者本地文件都能实现,看你的环境。

4.4 中文乱码:编码问题比想象中顽固

PRTG 传递的%message参数里如果有中文,脚本接收时可能出现乱码。这是因为 Windows 环境下 PRTG 默认用 GBK 编码传递参数,而 Python 3 默认用 UTF-8 解码。

解决办法是在脚本开头强制指定编码:

import sys import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

同时在 PRTG 的通知模板里,把参数用双引号包起来,比如"%message",这样 PRTG 会按原始字节传递,减少编码转换环节。

4.5 脚本执行超时:PRTG 的等待耐心有限

PRTG 执行外部程序时有超时限制,默认好像是 60 秒。如果你的脚本里要拉取多个图表、调用多次接口,可能会超时。超时后 PRTG 会认为通知失败,可能会重试,导致重复推送。

优化思路是:脚本里所有网络请求都设短超时(比如 5 秒),失败就跳过,不要阻塞主流程。图表拉取和消息推送可以并行处理,用concurrent.futures开线程池。实测下来,优化后单次告警处理时间从 8 秒降到了 2 秒以内。

5. 让告警消息更好用的几个进阶技巧

5.1 在图表上叠加状态水印

PRTG 原生图表只有曲线,没有状态标识。我后来用 Pillow 在图片右下角叠加了一个半透明的状态文字,比如“DOWN”或“WARNING”。这样即使消息文字被折叠,光看图片也能知道严重程度。

实现方式是在 Flask 的图表接口里,拿到 PRTG 返回的 PNG 后,用 Pillow 打开,画一个带背景色的文字框,再返回给企业微信。代码不复杂,但效果很直观。

5.2 多传感器聚合推送

如果一个设备有多个传感器同时告警,逐条推送会很烦。我写了一个聚合逻辑:脚本先查 PRTG API,找出同一设备下所有异常传感器,然后合并成一条消息推送。消息体里用列表展示每个传感器的状态,只附一张汇总图表。

这样一条消息就能覆盖整个设备的健康状况,减少打扰。聚合的时间窗口设为 30 秒,即 30 秒内的告警合并推送。

5.3 恢复通知的差异化处理

告警恢复时,我用的 markdown 模板和告警时不一样:颜色用绿色,标题加“已恢复”前缀,并且不附图表(因为恢复时图表意义不大)。这样在消息列表里,绿色消息一眼就能识别为“问题已解决”,不需要点开看详情。

另外,恢复通知我设置了更长的抑制时间(15 分钟),避免网络抖动导致“告警-恢复-告警”的循环推送。

5.4 机器人接口的限流应对

企业微信机器人有频率限制,好像是每分钟 20 条。如果告警风暴来袭,比如机房断电导致上百个传感器同时 Down,脚本会疯狂调用接口,触发限流后消息就发不出去了。

我的应对策略是:脚本里加一个令牌桶限流器,每秒最多发 1 条,超出的消息进入队列,按优先级排序(Down 优先于 Warning),依次发送。如果队列超过 100 条,就合并成一条汇总消息,只列出前 20 个异常设备,其余用“等 N 个设备”概括。

这个逻辑稍微复杂一点,但关键时刻能保证重要告警不被限流吞掉。

5.5 消息里的链接怎么跳转最顺手

PRTG 的传感器详情页 URL 是https://你的prtg地址/sensor.htm?id=传感器ID。但这个页面在手机上打开体验一般,需要登录,而且布局不是响应式的。

我后来做了一个简化版的详情页,用 Flask 渲染,只展示关键信息:传感器名称、当前状态、最近 24 小时图表、最近 10 条告警记录。这个页面不需要登录(用一次性 token 鉴权),手机打开很快。链接放在 markdown 消息底部,点击直接跳转。

注意:这个简化页面如果暴露在公网,一定要加鉴权。我用的是 HMAC 签名,链接里带过期时间戳,过期后自动失效。

6. 个人实操体会:告警系统的价值在于“被信任”

这套方案跑了大半年,最大的感受不是技术上的成就感,而是团队对告警的信任度明显提升了。以前纯文本邮件时代,大家看到告警第一反应是“先放着,等会儿再看”,因为信息太少,判断不了严重程度。现在图文混排消息推过来,状态颜色、趋势图、设备名一目了然,该立刻处理的立刻处理,该观察的观察,误报率也降下来了。

如果让我给正在考虑改造告警系统的朋友一个建议,那就是:先把消息的可读性做上去,再考虑智能化。很多人一上来就想搞 AI 根因分析、自动修复,但基础告警消息都让人看不懂,后面的都是空中楼阁。图文混排是一个投入产出比很高的起点,脚本不复杂,效果立竿见影。

另外,脚本的健壮性比功能丰富更重要。我见过太多告警脚本因为一个未捕获的异常就静默失败,结果故障期间一条通知都没发出去。所以脚本里一定要加 try-except,任何一步失败都要记录日志,并且最好有一个“心跳”机制——如果脚本连续 N 次执行失败,通过备用渠道(比如邮件)发一条元告警,提醒运维人员检查告警系统本身。

最后分享一个小技巧:企业微信机器人的 markdown 消息里,可以用>引用块来展示原始告警文本,这样既保留了 PRTG 的原始信息,又不会干扰主要内容的阅读。引用块在手机上有缩进和灰色背景,视觉上很舒服。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 1:17:14

手写MACD计算与matplotlib可视化:构建可验证的交易策略系统

1. 项目概述&#xff1a;为什么一张MACD图能成为交易决策的“眼睛”在某次和几位做量化策略的朋友吃饭时&#xff0c;有人掏出手机给我看一张图——不是K线&#xff0c;也不是成交量柱状图&#xff0c;而是一张用Python画出来的、带三根曲线和彩色柱状的MACD图。他指着那根快线…

作者头像 李华
网站建设 2026/10/17 1:16:55

x86汇编宏指令与条件汇编实战:构建工业级模块化代码

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:16:55

Nginx 配置 HSTS 实战:告别 301 明文跳转,彻底强制 HTTPS

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:16:24

决策树算法通俗指南:熵、基尼系数与调参实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 1:15:42

SCENRED场景缩减实战:GAMS把10000个随机场景降到300个

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华