PRTG告警推送企业微信:从纯文本升级为图文卡片的完整方案
凌晨两点手机开始连着振我睡眼惺忪地摸过来一看群消息已经刷了几十条。不是恶意骚扰是PRTG在报警。可点开消息一看清一色纯文本一行设备名、一行传感器名、一行“警报CPU负载率超过阈值”数据是多少、持续了多久、影响范围有多大全靠自己往上翻聊天记录去拼。这不是我一个人遇到的尴尬。PRTG接企业微信做告警很多团队第一步都是找群机器人Webhook直发纯文本图省事。但真等告警风暴砸下来纯文本几乎等于没有告警——信息都在却什么都找不到。关键状态、当前值、恢复情况全部混成一团值班的人光分辨这些消息就要花大把时间。所以我今天把这套完整方案聊透怎么把PRTG的告警消息通过企业微信群机器人或自建应用做成带层级、带视觉重点、带跳转链接的图文混排告警卡片。这篇东西适合正在用PRTG、被夜里纯文本告警折磨的监控运维也适合刚准备搭监控通知体系、想把可读性一步做到位的人。1. 纯文本告警为什么救不了夜班一个真实的告警风暴现场1.1 先复盘那个凌晨某次在客户机房现场一台精密空调故障温度缓慢上升。按说PRTG一个传感器就能发现问题但当时那个环境里300多个传感器同时在线空调异常后温度、湿度、设备负载、网络延迟这些传感器先后越阈值告警像瀑布一样往下刷。我盯着聊天窗口看到的是一堆结构几乎一样的文本消息设备名 传感器名 状态 一行说明。没有颜色、没有标题层次更没有“哪条影响最大”的提示。等值班同事从消息流里翻出最原始的那条空调告警已经过去了十几分钟。冷却系统过温这个告警优先级显然比某个内网交换机延迟超标高得多但在纯文本列表里它们长得一模一样。1.2 纯文本告警的三个硬伤第一个硬伤没有视觉层级。所有消息一个模子印出来高优低优全凭肉眼猜。值班人收到一屏告警首先得做“分诊”而纯文本把分诊成本全部推给了人。第二个硬伤缺少上下文。纯文本一般只给“当前状态”和一句固定描述。持续多久当前实测值是多少这已经是第几次触发这些字段散落在不同消息里想回答“严重不严重”得自己拼图。第三个硬伤告警和恢复难区分甚至自动恢复消息和告警消息挤在一起如果群消息顺序乱一下很容易让处理人以为问题已经解决导致误判。用一段大白话总结纯文本告警适合喝杯咖啡慢慢对着屏幕分析但不适合凌晨被消息震醒之后那三十秒的快速决策。1.3 什么叫图文混排不是插图是信息分层其实“图文混排”不是非得往消息里塞张图片。在企业微信的消息体系里真正的图文混排指的是把一段富文本信息按标题、正文、字段、跳转按钮的方式重新编排让人类视觉系统能快速抓重点。常见形态有三种消息类型形态适合场景text纯文本应急测试、内部调试markdown富文本支持加粗、引用、链接、分级标题日常告警推送主力信息密度高image_text图文卡片上方可带图下面配标题和正文关键告警、需要吸引注意力、需要跳转详情news图文列表多条可折叠批量告警聚合、通报类消息一图胜千言但更重要的是“分层”标题点明发生了什么正文用加粗强调当前值链接负责把人引导到PRTG后台。值班人扫一眼标题就能决定“要不要起来处理”。1.4 为什么选企业微信当监控告警通道原因很现实同事手机上已经装了企业微信不需要额外装任何客户端。相比短信的低成本、高触达相比邮箱的看完需要登录企业微信消息出现在聊天列表里接收率天然更高。而且企业微信提供了Webhook和自建应用两套接口前者适合快速接入后者适合按组织架构定向通知。再加上群聊的历史消息天然可搜索复盘告警只需要搜设备名就行。这套路线的性价比在中小规模监控体系里几乎没有对手。2. 企业微信通道二选一群机器人Webhook还是自建应用推送2.1 群机器人三分钟就能跑通的方案先给只想验证效果的读者一条最快路径。建一个企业微信群点群设置添加群机器人复制Webhook地址。Webhook地址长这样https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx拿到地址之后直接用命令行测试连通性不需要写任何代码curl -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyREPLACE_KEY \ -H Content-Type: application/json \ -d { msgtype: text, text: { content: PRTG联动测试 } }返回内容里如果出现errcode:0说明通道已经通了。群机器人的特点是每个群独立管理、互不影响把不同业务系统的告警推到不同群天然隔离。2.2 自建应用面向成员的定向推送如果你的告警不想发到群里而是希望按组织架构定向发给某个人或某个部门就得用自建应用。操作步骤也不复杂登录企业微信管理后台在应用管理里创建自建应用拿到三个关键参数企业IDCorpID、应用IDAgentId、应用密钥Secret。有了这三个参数先换access_token再发应用消息。# 拿 access_token curl -G https://qyapi.weixin.qq.com/cgi-bin/gettoken \ --data-urlencode corpidYOUR_CORP_ID \ --data-urlencode corpsecretYOUR_SECRET# 发应用消息 curl -X POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_tokenREPLACE_TOKEN \ -H Content-Type: application/json \ -d { touser: user1|user2, msgtype: text, agentid: 1000002, text: { content: PRTG联动测试 } }自建应用的“可见范围”一定要认真设置给开发人员和运维人员最小授权。因为应用消息是可以发给企业内所有成员的配置不当容易变成全员告警广播。2.3 两类通道怎么取舍对比项群机器人Webhook自建应用接收对象群成员按组织架构指定的成员配置门槛低建群即可中需要管理后台权限消息类型text、markdown、image_text等text、markdown、image_text等定向通知不能指定某个人可以适用场景运维值班群、自动化发布群需要通知到具体负责人的场景实际项目里我的默认选择是先上群机器人因为告警处理本来就是一个团队协作动作消息发到值班群里谁看了谁接手信息是共享的。等到告警链路成熟、需要把“某台设备的专属告警发给专属负责人”时再叠加自建应用不迟。2.4 两类接口的限流与素材有效期这个坑必须提前知道企业微信的Webhook发送频率是有限制的短时间大量消息会直接返回错误码。告警风暴场景下几百条消息并发很容易触发峰值限制。另外如果你要发送图片类素材通过接口上传临时素材拿到的media_id有效期只有三天。PRTG这种长期运行的监控系统如果脚本里硬编码了一个media_id三天后图片必然失效。正确做法是每次发送前实时上传图片或者把图片放在内网HTTP服务上用URL代替media_id。这些细节到第5章排错部分会再展开。3. PRTG侧改造用Execute Program把%变量变成结构化卡片3.1 为什么放弃PRTG内置通知通道改用脚本PRTG的通知机制分三层传感器触发通知模板模板里可以选邮件、HTTP请求、执行程序等不同通道。能把消息推到企业微信的方式不止一种但我的实践结论是用“Execute Program”执行脚本是最可控的。邮件通知要配SMTP绕一圈不说消息形态还是纯文本。HTTP通知虽然可以不写脚本直接发POST请求但PRTG的%变量在URL编码和模板转义上限制很大想拼出规范的JSON请求体会被特殊字符、引号、百分号折磨到怀疑人生。脚本则不同PRTG把变量作为参数传给脚本脚本内部随便处理、随便拼JSON所有逻辑都在PowerShell里调试起来透明得多。3.2 PRTG变量到卡片字段的映射在配置脚本之前先搞清楚PRTG能给你什么。常用变量如下PRTG变量含义落到卡片上的位置%device设备名称卡片标题主体%sensor传感器名称卡片标题主体%status传感器状态如Down、Warning状态前缀%message传感器当前消息文案正文摘要%lastvalue传感器最新读取到当前值当前值字段%up传感器正常运行时长持续时长字段%time通知触发时间时间字段PRTG在通知配置里可以把这些变量当作参数传给脚本。配置方式是在传感器的通知设置里选择“Execute Program”然后在参数栏位上写类似这样的内容powershell.exe -ExecutionPolicy Bypass -File C:\prtg-scripts\wechat_push.ps1 %device %sensor %status %message %lastvalue %time这里有一个关键操作所有%变量必须用双引号包起来。因为%message和%lastvalue里可能包含空格不加引号PRTG在传参时会把一句话拆成好几个参数脚本收到的就是一堆碎片。3.3 PowerShell脚本完整示例markdown版告警卡片下面的脚本对应企业微信群机器人的markdown消息类型。PRTG把这些变量传进来脚本在内部拼装markdown格式再POST到Webhookparam( [string]$DeviceName, [string]$SensorName, [string]$Status, [string]$Message, [string]$LastValue, [string]$Time ) $WebhookUrl https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyREPLACE_KEY $Content ## 告警通知 **设备**$DeviceName **传感器**$SensorName **状态**$Status **当前值**$LastValue **发生时间**$Time $Message [打开PRTG后台](https://prtg.example.com) $Body { msgtype markdown markdown { content $Content } } | ConvertTo-Json -Depth 5 $Utf8Bytes [System.Text.Encoding]::UTF8.GetBytes($Body) Invoke-RestMethod -Uri $WebhookUrl -Method Post -Body $Utf8Bytes -ContentType application/json; charsetutf-8注意脚本里最后一行把JSON字符串转成UTF8字节数组再发送而不是直接传字符串。这是中文不乱码的关键之一。3.4 升级为image_text图文卡片先传图再发卡片markdown在文字排版上很舒服但如果希望消息看起来像一张“卡片”带封面图、标题栏和描述区就要用image_text消息类型。两条请求先上传图片拿到media_id再发送image_text。上传图片用这种方式在Windows PowerShell 5.1里推荐直接用curl.exe因为老版本PowerShell的Invoke-RestMethod没有封装multipart表单curl.exe -F mediaC:\prtg-scripts\banner_warning.png https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?keyREPLACE_KEYtypefile返回的JSON里有media_id。发送image_text消息的结构如下{ msgtype: image_text, image_text: { media_id: MEDIA_ID_FROM_UPLOAD, title: 告警 - 机房A - 精密空调温度, text: 当前值: 27.3度, 阈值: 25度\n持续时长: 8分钟\n点击下方链接查看详情, url: https://prtg.example.com } }这里最需要注意的一点是企业微信的image_text支持不全的版本差异偶尔会遇到发送报错。稳妥做法是脚本里同时写好markdown兜底逻辑一旦image_text发送失败自动退回markdown普通告警。监控通道可以晚10秒到但不能不到。3.5 PRTG运行脚本的三个隐藏前提第一PRTG核心服务运行在Windows服务账号下这个账号必须有权限访问powershell.exe并且能主动访问外网。经常有人在测试环境中用管理员账号手工跑通了脚本但PRTG服务一执行就失败排查半天才发现是服务账号出网权限不够。第二PowerShell执行策略。建议在命令行里显式加-ExecutionPolicy Bypass别指望目标机器的默认策略放行。第三脚本路径和脚本文件名不要带空格不要放在OneDrive同步目录里否则PRTG调用时会因为路径解析问题静默失败。4. 图文卡片的信息设计一张告警卡片上应该放什么4.1 标题规范一秒判断要不要起来处理值班人看告警的顺序永远是先看标题再看正文。所以标题一定要在最短字数内回答四个问题什么状态、哪台设备、哪个传感器、是否紧急。我的标题模板长这样告警|机房A|精密空调温度|超过阈值恢复消息则改成恢复|机房A|精密空调温度|已回到正常范围多花几个字符写清楚“恢复”比只改状态值强太多。因为凌晨值班心理是“告警处理完看到恢复消息才真正松口气”。如果恢复消息只写“UP”简直是在折磨人。4.2 正文七要素缺一不可参考下面的markdown模板**状态**警告 **设备**机房A-精密空调 **传感器**温度传感器 **当前值**27.3 度 **阈值**25.0 度 **持续时长**8 分钟 **发生时间**02:33 **链接**[打开PRTG查看详情](https://prtg.example.com)七要素里最容易被忽略的是持续时长。持续时长决定了这个告警是新发还是重复抖动。一个设备持续告警8分钟和一个刚触发10秒的告警紧急程度完全不同。把持续时间放进去值班人就能判断要不要立刻叫醒同事。4.3 图片怎么用不是越多越好如果没条件把PRTG的实时图表导出成图片就不要硬塞图。我见过很多告警卡片塞一张模糊截图不仅没提供信息还拉低了加载速度。推荐的做法是准备一套按严重级别区分的banner图警告用黄色错误用红色恢复用绿色每条告警选择对应banner上传获取media_id。这样图片起到的是“视觉报警灯”作用真正高密度的数据靠文字和表格承载。如果确实需要展示传感器历史曲线可以配置PRTG的Graph API定期把图表导出成PNG放到内网HTTP服务下。卡片里用picurl指向这张图而不是每次都上传到企业微信。这样既能展示走势图又没有media_id过期问题。4.4 恢复通知也要图文混排但内容结构要变恢复消息别简单把“告警”改成“恢复”最佳实践是把这次事件的复盘要素放进去持续时间、峰值、最终恢复值、处理建议。**状态**已恢复 **设备**机房A-精密空调 **传感器**温度传感器 **峰值**27.3 度14:02 **持续时间**23 分钟 **当前温度**24.1 度 **链接**[打开PRTG查看历史](https://prtg.example.com)这种恢复卡片在事后复盘时特别好用直接把聊天记录导出就是一个简版事件报告。值班人员不用再翻监控后台拉数据。5. 联调排错实录让卡片稳定到达的五道关卡5.1 关卡一PRTG到底有没有把脚本执行起来排错第一步永远是确认执行链路的上一环通不通。PRTG里可以查看传感器的通知历史也可以直接在PRTG的“系统日志”里看脚本执行记录。但最直接的办法是让脚本自己写日志$LogDir C:\prtg-scripts\logs if (-not (Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir -Force } $LogFile Join-Path $LogDir (alert_ (Get-Date -Format yyyyMMdd_HHmmss) .log) Device$DeviceName Sensor$SensorName Status$Status Time$Time | Out-File -FilePath $LogFile -Encoding utf8先在脚本开头写日志再跑一遍告警看日志文件有没有生成。如果日志都生成了企业微信里没消息那就是网络或参数问题方向就很明确。我在现场排错时几乎每次都用这个手段先划定问题边界。5.2 关卡二中文全是问号或者乱码PowerShell 5.1会把无BOM的UTF-8脚本文件读取为ANSI编码中文直接乱掉。所以脚本文件必须保存为UTF-8 with BOM。另一点发送请求时如果Body直接传字符串某些企业微信版本解析时会把JSON里的中文按默认字符集处理结果就是星星点点。正确做法是像前面脚本里写的用[System.Text.Encoding]::UTF8.GetBytes()转成字节数组。如果乱码依然存在先手工curl验证curl -X POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyREPLACE_KEY \ -H Content-Type: application/json; charsetutf-8 \ -d {msgtype:text,text:{content:中文测试}}curl不乱码而脚本乱码问题必然在PowerShell的文本编码处理上。5.3 关卡三消息被截断或者markdown渲染失效企业微信的markdown消息对内容大小有限制大体在4096字节以内。PRTG的%message一旦包含长文本很容易超限。脚本里要主动截断if ($Message.Length -gt 200) { $Message $Message.Substring(0, 200) ...(已截断请点击链接查看原文) }另一个细节是markdown里出现特殊符号会被解析成格式符。比如告警内容里如果包含尖括号markdown渲染会出问题。保险做法是把会话消息里的换行统一为\n把容易冲突的字符做简单清洗。注意企业微信的markdown消息格式和常用markdown引擎不完全一致加粗符号**和引用通常可用但表格、行内代码不一定可靠。所以卡片正文宁可用列表和加粗不要依赖复杂markdown语法。5.4 关卡四图片卡片发不出来image_text图片发不出来的场景我大致归纳成三类media_id失效。临时素材有效期只有3天脚本里如果上传一次后缓存media_id超过3天必失败。每次发送前重新上传或者使用URL模式。picurl不可达。如果企业微信服务器拉取不到picurl对应图片卡片会显示空白。注意不能只在PRTG服务器本机能访问图片企业微信服务器也要能访问。内网环境酌情考虑。图片超限。企业微信临时素材接口对图片大小有上限现场踩过2M以上PNG被拒的情况。控制banner图片体积。现象排查方向media_id无效是否超过3天有效期卡片空白只有标题picurl是否外网/企业微信可达上传接口报错图片大小、字段名是否为media发送报错不识别消息类型或参数名拼写错误5.5 关卡五告警风暴打爆webhook限流大批设备同时掉线几十上百条告警在一个时间点冲过来很容易触发企业微信的频率限制。这时消息发送返回错误码直接丢弃。我的处理方案是双重保险先是在脚本里做轻量重试发送失败后间隔几秒再试最多重试三次。这能解决瞬时抖动。真正管用的是在PRTG侧和脚本侧做聚合同一个设备下的传感器告警不要逐条发而是汇总成一条image_text用列表把多个传感器状态展示出来。聚合后的卡片既避开了限流分工也更清晰。5.6 额外提醒Webhook key泄露等于群聊被接管最后说一句安全常识群机器人的Webhook地址是一个免鉴权的推送入口任何拿到URL的人都能往群里发消息。不要把它提交到Git仓库不要把带key的脚本随手拷到共享目录。如果发现泄露直接到群机器人设置里删除重建别留隐患。我在实际项目中踩过的最深的坑是跳过了curl自测直接上脚本结果PRTG通知触发后一片寂静排查了半小时才发现是服务账号没有出网权限。现在我的习惯是第一步用curl敲通道连通性第二步用固定参数跑脚本第三步再让PRTG真实触一发告警。把链路一层层割开验证问题永远出现在你最没想到的那一环但一定逃不出这几个关卡。