单进程常驻的监控告警中间件:实时采集系统指标(CPU / 内存 / 磁盘 / 负载 / 温度), 探测服务存活,增量分析关键日志,命中异常即按 Warning / Critical 分级推送到钉钉。 开箱即用、不假设任何业务,放到任意一台 Linux 机器上都能正常运行且不会误报。
| 场景 | 能力 |
|---|---|
| 系统资源异常 | 采集 CPU / 内存 / 磁盘 / 负载 / 温度,连续 N 次超阈值分级告警 |
| 服务挂了没人知道 | 按进程、端口、Unix socket 探测服务存活,DOWN 立即告警,未安装自动跳过 |
| 关键日志刷屏 | 增量轮询 Nginx / Docker 等日志,命中错误模式聚合推送,联动系统指标给出根因诊断 |
- 指标监控:CPU(psutil +
/proc双通道)、内存、磁盘、负载、温度; - 分级阈值:每个指标独立配置 Warning / Critical 两级,可经环境变量按主机覆盖;
- 内存告警:仅以总内存(RAM)使用率判定,swap 仅作参考展示、不触发告警;
- 连续判定防抖:指标/磁盘连续 N 次(默认 3 次)异常才告警,连续 N 次正常才发恢复, 偶发毛刺不再单次误报;
- 负载告警:load1 按 CPU 核数归一化(load1 / 核数)后分级告警,2 核与 8 核机器不会误报/漏报;
- 磁盘多挂载点:
DISK_PATHS可配置多个挂载点,任一超阈值告警并带上挂载点; - 服务存活监控:进程 / 二进制 / 监听端口 / Unix socket 四重探测;
- SKIP 自动跳过:配置了但主机未安装的服务自动判 SKIP,不误报 DOWN,且只通知一次;
- 开机状态播报:启动后推送一次系统指标 + 服务 UP/DOWN/SKIP 汇总(
STARTUP_NOTIFY); - 恢复通知:指标回落、服务 DOWN→UP、日志事件停止时发送“已恢复”,推送成功才落盘,失败不丢失;
- 日志语义监控:文件型(offset 增量)与命令型(定时执行)两类日志源;
- 聚合与冷却:同代码日志聚合为一条告警 + 冷却去抖,杜绝 502 / OOMKilled 刷屏;
- 可靠推送:钉钉推送失败指数退避重试(2s → 4s → 8s),仍失败则本地留痕、下轮补发;
- 多通知渠道:钉钉(加签)/ 企业微信 / 飞书 / stdout,按环境变量自动路由,无需改代码;
- 状态持久化:冷却、告警级别、服务状态落盘,进程重启不重复告警;
- 全量留痕:所有告警写入
alerts.jsonl,按大小自动轮转; - 单实例锁:PID 文件 + 存活检测,防止重复启动双份告警;
- 优雅退出:SIGINT / SIGTERM 秒级退出,线程池收尾有超时上限;
- 自检工具:
python3 main.py --selftest部署前体检配置与运行环境。
monitor-agent/
├── main.py # 主入口:事件循环、信号处理、自检
├── config.py # 全部配置:阈值、Webhook、服务/日志清单
├── collectors.py # 指标采集 + 服务存活探测
├── alerting.py # 阈值判定、冷却、钉钉推送、留痕、SKIP/开机播报通知
├── log_monitor.py # 日志语义监控(文件型 / 命令型)
├── config.example.json # 服务与日志监控配置示例(nginx + docker)
├── requirements.txt # 生产依赖(psutil)
├── deploy/
│ ├── monitor-agent.service.example # systemd 标准模板(含 __MONITOR_DIR__ / __PYTHON_BIN__ 占位符)
│ └── monitor-agent.service.legacy.example # systemd < 235(如 CentOS 7)兼容模板
└── install.sh # 安装 / 更新 / 自启 / 卸载脚本(root)
模块职责:config.py 是所有配置的唯一来源(文件 + 环境变量合并,启动时统一体检);
collectors.py 产出统一快照 MetricSnapshot;alerting.py 消费快照做判定与推送;
log_monitor.py 独立轮询日志并复用 alerting.py 的推送通道;main.py 负责编排
两个循环、信号处理与退出收尾。
进程启动后创建 asyncio 事件循环,并行运行两个协程:
┌──────────────────────────────────────────────────────────┐
│ main.py(asyncio 事件循环,单进程常驻) │
│ │
│ metrics_loop ─ 每 MONITOR_INTERVAL 秒 │
│ 采集快照(阻塞部分放线程池) │
│ ├─ 阈值判定 → Warning/Critical 告警 → 钉钉 │
│ └─ 服务存活探测 → DOWN 告警 / SKIP 一次性通知 / 开机播报 │
│ │
│ logwatch_loop ─ 每 LOG_SCAN_INTERVAL 秒 │
│ 轮询日志 watcher → 正则命中 → 聚合 → 联动诊断 → 钉钉 │
└──────────────────────────────────────────────────────────┘
关键机制:
- 采集隔离:阻塞 IO(
cpu_percent(interval=1)、磁盘、socket 探测、子进程命令) 提交到应用自有线程池(collectors.EXECUTOR),事件循环不卡顿;退出时该线程池 有界收尾(默认 5 秒上限,MONITOR_SHUTDOWN_TIMEOUT); - 推送隔离与重试:钉钉推送(含退避重试)整体在独立线程中执行,网络抖动不阻塞
监控循环;失败按
2s → 4s → 8s指数退避,仍失败则本轮放弃、下一轮重新触发; - 告警风暴抑制:同指标同级别(
metric:level)在冷却窗口内只推一次; 日志按代码(log:<code>)聚合 + 冷却; - 连续 N 次判定:指标/磁盘须连续 N 次(默认 3,
ALERT_CONSECUTIVE)超阈值才告警, 恢复同样要求连续 N 次正常;配合 10s 采集周期,持续异常约 30s 内确认并推送; - 恢复通知可靠性:指标/服务恢复时先生成恢复通知,推送成功后才把状态落盘为 “已恢复”;推送失败保持告警状态,下一轮重新生成,不会丢失;
- SKIP 判定:服务配置了,但本机既无进程、无二进制、无 socket、无监听端口, 即判定“未安装”,不产生 DOWN 告警;
- 单实例锁:PID 文件采用
O_EXCL创建 + 存活进程检测,第二个实例直接退出 (退出码 2),不会造成双份告警; - 优雅退出:SIGINT / SIGTERM 触发
_shutdown事件,推送退避等待可被打断, 主循环自然退出,超时由看门狗强制收尾。
| 文件 | 用途 |
|---|---|
monitor-agent.pid |
单实例锁(存活实例检测) |
alerts.jsonl |
全部告警留痕(自动轮转) |
monitor-agent.log |
运行日志(默认落盘,自动轮转) |
alert-state.json |
冷却 / 告警级别 / 服务状态持久化(重启续用) |
skip-notified.json |
SKIP 一次性通知标记(删除可重新触发) |
仓库内直接运行时位于 ~/.local/state/monitor-agent/(可用 MONITOR_STATE_DIR 整体搬迁);
systemd 部署下由服务单元固定到:
/run/monitor-agent/ PID 锁(运行时,重启即清)
/var/lib/monitor-agent/ alerts.jsonl、skip-notified.json、alert-state.json(持久)
/var/log/monitor-agent/ 运行日志(轮转)
| 项 | 要求 |
|---|---|
| 操作系统 | Linux 为主(含 CentOS 7 等旧版 systemd);macOS 可运行核心指标监控;Windows 仅指标与文件型日志可用 |
| Python | ≥ 3.8 |
| Python 依赖 | psutil(见 requirements.txt) |
| 网络 | 仅出站 HTTPS,用于推送钉钉 |
| 权限 | 读日志 / socket 需要 root;普通运行也能监控系统指标 |
git clone git@github.com:Bochi07/Simple-alarm-script.git
cd Simple-alarm-script
python3 -m pip install -r requirements.txt# 配置钉钉(可选;不配则告警仅本地留痕)
export DINGTALK_WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=xxxx"
export DINGTALK_SECRET="SECxxxx" # 机器人开启“加签”时必填
# 部署前体检
python3 main.py --selftest
# 前台运行(Ctrl+C 优雅退出)
python3 main.py未配置 DINGTALK_WEBHOOK 时,告警只写入本地留痕文件,不推送钉钉。
sudo bash install.sh脚本自动定位到仓库自身,默认安装到 /opt/monitor-agent,会先备份旧目录到
/opt/monitor-agent.bak-<时间戳> 再覆盖,并做语法检查。自定义位置:
sudo MONITOR_AGENT_DIR=/usr/local/monitor-agent bash install.sh验证:
python3 /opt/monitor-agent/main.py --selftestsudo bash install.sh systemd该命令在方式 B 基础上额外完成:安装 /etc/systemd/system/monitor-agent.service
(自动探测并写入 python3 路径,不依赖 /usr/bin/python3 固定位置)、生成
/etc/monitor-agent/env 密钥模板、daemon-reload + enable --now 启动。
CentOS 7 等 systemd < 235 的机器会自动改用兼容模板,无需手工处理。
常用运维命令:
systemctl status monitor-agent
systemctl restart monitor-agent
journalctl -u monitor-agent -f- 钉钉群 → 群设置 → 智能群助手 → 添加机器人 → 自定义(Webhook);
- 安全设置建议开启加签,将
SEC密钥填入DINGTALK_SECRET; - 复制 Webhook 地址填入
DINGTALK_WEBHOOK; - 若使用“自定义关键词”:普通告警标题含
告警,恢复通知标题含恢复, 建议两个关键词都加入,否则会漏掉其中一类消息; - systemd 方式把两项写入
/etc/monitor-agent/env后重启:
sudo nano /etc/monitor-agent/env
sudo systemctl restart monitor-agent不用钉钉的话,直接配置 WECOM_WEBHOOK(企业微信)或 FEISHU_WEBHOOK(飞书)即可,
调试期也可用 MONITOR_NOTIFY_STDOUT=1 把告警打印到 stdout(见 3.9 环境变量表)。
默认只监控系统指标,不会误报。要监控 nginx、docker 等业务,任选一种方式注入清单:
方式一:JSON 配置文件(推荐,多主机复用同一份)
sudo mkdir -p /etc/monitor-agent
sudo cp config.example.json /etc/monitor-agent/config.json
export MONITOR_CONFIG_FILE=/etc/monitor-agent/config.json
python3 main.py示例配置(nginx 服务 + nginx 错误日志 + docker OOM 日志):
{
"services": [
{"name": "nginx", "process_names": ["nginx"], "host": "127.0.0.1", "port": 80},
{"name": "docker", "process_names": ["dockerd", "docker"],
"unix_socket": "/var/run/docker.sock"}
],
"log_jobs": [
{"name": "nginx_error", "path": "/var/log/nginx/error.log",
"patterns": [["upstream\\s+.*?(?:connect\\(\\) failed|timed out|prematurely closed|500|502|503|504)",
"NGINX_UPSTREAM_FAIL", "Nginx 后端网关异常"]]},
{"name": "docker_oom", "command": "docker ps -a --no-trunc --format '{{.Names}} {{.Status}}'",
"patterns": [["OOMKilled|oom-kill", "DOCKER_OOM_KILL", "容器被 OOM Killer 杀死"]]}
]
}文件型日志任务的路径支持两种写法:
path:单个日志路径,最常用;paths:候选路径数组,按顺序探测、先到先用,适合非标准安装位置 (如宝塔面板的/www/server/nginx/logs/error.log、源码编译的/usr/local/nginx/logs/error.log)。
即使只配了 path 且文件不存在,也会自动按常见 nginx 安装位置回退探测同名日志
(/var/log/nginx、/www/server/nginx/logs、/usr/local/nginx/logs、
/opt/nginx/logs、/etc/nginx/logs、/www/wwwlogs),全部找不到才跳过该任务,
不影响其他监控。
systemd 方式则把 MONITOR_CONFIG_FILE=/etc/monitor-agent/config.json 写入
/etc/monitor-agent/env 后重启服务。
方式二:环境变量直接注入
export MONITOR_SERVICES='[{"name":"nginx","process_names":["nginx"],"port":80}]'
export MONITOR_LOG_JOBS='[{"name":"nginx_error","path":"/var/log/nginx/error.log","patterns":[["connect\\(\\) failed","NGINX_UPSTREAM_FAIL","Nginx 后端网关异常"]]}]'自动跳过(SKIP):配置了但主机上完全没有安装痕迹(进程 / 二进制 / socket / 监听端口)的服务判定为 SKIP,不产生 DOWN 告警,同一份配置可安全铺到一批异构主机上。
python3 main.py --selftest # 配置/环境体检
python3 main.py # 前台跑起来
tail -f ~/.local/state/monitor-agent/alerts.jsonl # 看留痕(systemd 下在 /var/lib/monitor-agent/)启动日志出现 监控告警中间件启动:... Webhook=已配置 即为正常。
| 变量 | 默认值 | 说明 |
|---|---|---|
MONITOR_CONFIG_FILE |
空 | JSON 配置文件路径(services / log_jobs) |
MONITOR_SERVICES |
空 | 服务清单 JSON 数组(文件配置优先) |
MONITOR_LOG_JOBS |
空 | 日志任务 JSON 数组(文件配置优先) |
MONITOR_DIAGNOSTICS |
内置两条 | 日志语义诊断规则 JSON 数组(默认含 Nginx 网关过载 / Docker OOM) |
DINGTALK_WEBHOOK |
空 | 钉钉机器人 Webhook,空则仅本地留痕 |
DINGTALK_SECRET |
空 | 钉钉加签密钥 |
WECOM_WEBHOOK |
空 | 企业微信机器人 Webhook(配置后优先于钉钉) |
FEISHU_WEBHOOK |
空 | 飞书机器人 Webhook(未配置钉钉/企业微信时生效) |
MONITOR_NOTIFY_STDOUT |
0 | 1 时告警仅打印到 stdout(配合 journald / 调试) |
MONITOR_INTERVAL |
10 | 指标采集周期(秒) |
LOG_SCAN_INTERVAL |
10 | 日志轮询周期(秒) |
ALERT_COOLDOWN |
300 | 同类型告警冷却(秒) |
ALERT_CONSECUTIVE |
3 | 指标/磁盘连续异常次数,达到 N 次才告警(恢复同理) |
MONITOR_COLLECT_WORKERS |
4 | 指标采集线程池 worker 数 |
PUSH_TIMEOUT |
5 | 单次 HTTP 推送超时(秒) |
PUSH_MAX_RETRIES |
3 | 推送失败重试次数(指数退避) |
PUSH_RETRY_BACKOFF |
2.0 | 首次退避基数(秒) |
MONITOR_COMMAND_SHELL |
空 | 命令型日志使用的 shell(默认自动探测 bash → sh) |
LOG_COMMAND_TIMEOUT |
15 | 命令型日志单次执行超时(秒) |
CPU_PERCENT_WARNING / CPU_PERCENT_CRITICAL |
80 / 95 | CPU 分级阈值覆盖 |
MEMORY_PERCENT_WARNING / MEMORY_PERCENT_CRITICAL |
80 / 92 | 内存分级阈值覆盖(仅按总内存 RAM 使用率判定) |
DISK_PERCENT_WARNING / DISK_PERCENT_CRITICAL |
80 / 90 | 磁盘分级阈值覆盖 |
TEMPERATURE_C_WARNING / TEMPERATURE_C_CRITICAL |
70 / 85 | 温度分级阈值覆盖 |
LOAD1_WARNING / LOAD1_CRITICAL |
1.0 / 2.0(每核) | 负载分级阈值(load1/核数 归一化后比较) |
DISK_PATHS |
/ |
磁盘监控挂载点(逗号分隔的绝对路径,任一超阈值告警) |
MONITOR_STATE_DIR |
~/.local/state/monitor-agent |
状态目录 |
ALERT_HISTORY_FILE |
状态目录/alerts.jsonl |
告警留痕(自动轮转) |
PID_FILE |
状态目录/monitor-agent.pid |
单实例锁 |
SKIP_NOTIFY_FILE |
状态目录/skip-notified.json |
SKIP 一次性通知标记 |
SKIP_NOTIFY_ONCE |
1 | 是否启用 SKIP 首次通知(0 关闭) |
STARTUP_NOTIFY |
1 | 是否发送开机状态播报(0 关闭,回退为仅 SKIP 通知) |
MONITOR_SILENCE_UNTIL |
空 | 全局静默截止时间(Unix epoch 秒或 ISO8601),期内只留痕不推送 |
MONITOR_SILENCE_SERVICES |
空 | 逗号分隔的服务名,期内这些服务的告警只留痕不推送 |
MONITOR_LOG_FILE |
状态目录/monitor-agent.log |
运行日志文件(设空串则仅 stdout,供 journald) |
MONITOR_LOG_MAX_BYTES |
5242880 | 运行日志轮转阈值(字节) |
MONITOR_LOG_BACKUPS |
2 | 运行日志备份数 |
MONITOR_SHUTDOWN_TIMEOUT |
5 | 退出时线程池收尾上限(秒) |
ALERT_STATE_FILE |
状态目录/alert-state.json |
冷却 / 级别 / 服务状态持久化文件 |
阈值、服务/日志清单、磁盘挂载点、通知渠道、静默窗口等改动,无需重启即可生效:
sudo kill -HUP "$(cat /run/monitor-agent/monitor-agent.pid)"收到 SIGHUP 后,进程会重新读取 MONITOR_CONFIG_FILE 指向的 JSON 配置与进程内环境变量,
并重建指标/日志引擎——采集周期、冷却窗口、日志样本数等调度参数也会一并生效。
重载校验失败(如非法阈值)会打印错误并保留原配置继续运行。
注意:systemd 部署下,修改 /etc/monitor-agent/env 里的环境变量不能靠 SIGHUP 生效——
systemd 只在服务启动/重启时重新读取 EnvironmentFile,因此环境变量改动仍需
sudo systemctl restart monitor-agent。
代码更新后(git pull 拉到最新提交),重跑一次安装脚本即可,配置原样保留:
cd Simple-alarm-script
git pull
sudo bash install.sh systemd # 覆盖安装 + 重建服务单元并重启旧代码会自动备份到 /opt/monitor-agent.bak-<时间戳>,/etc/monitor-agent/env
不会被动过;如需回滚,把备份目录换回原位置并重启服务即可。
只改了阈值/清单等配置时,可用 SIGHUP 热重载免重启(见 3.10);拉取到新代码仍按上面三步更新。
sudo bash install.sh systemd-remove # 只停服务,保留程序文件
sudo bash install.sh uninstall # 停止服务 + 删除程序文件(备份保留)uninstall 不会删除 /etc/monitor-agent 配置、状态文件和日志,彻底清理由手动补:
sudo rm -rf /etc/monitor-agent /var/lib/monitor-agent /var/log/monitor-agent /run/monitor-agent
rm -rf ~/.local/state/monitor-agentQ1:启动报“配置不合法,启动中止”
运行 python3 main.py --selftest 看具体 fatal 项:多为 JSON 配置缺失/解析失败、
正则非法、Webhook 是占位符、阈值越界。修正后重试。
Q2:收不到钉钉告警
- 确认
DINGTALK_WEBHOOK正确、非占位符; - 开启加签需填
DINGTALK_SECRET;自定义关键词需覆盖告警和恢复两类消息; - 看日志确认
Webhook=已配置; - 检查
alerts.jsonl:留痕有、推送失败说明网络或机器人配置问题。
Q3:服务一直 DOWN 但服务明明在跑
进程名不匹配时用 ps -eo comm 确认实际进程名,process_names 填可执行名;
TCP 探测默认走 127.0.0.1,服务只监听特定 IP 需配置 host;
Docker 用 unix_socket: /var/run/docker.sock,不要依赖 2375 端口。
Q4:机器上根本没装 nginx/docker,会不会误报?
不会。默认清单为空;即使配置了,未安装也会判 SKIP 且只通知一次。
Q5:为什么温度显示 -1?
主机没有可读温度传感器(虚拟化/部分硬件),该指标自动禁用,不影响其他监控。
Q6:重复启动会不会双份告警?
不会。单实例 PID 锁检测存活进程,第二个实例直接退出(退出码 2)。
Q7:SIGTERM 后进程迟迟不退?
线程池收尾上限默认 5 秒(MONITOR_SHUTDOWN_TIMEOUT),超时强制退出。
Q8:重启进程会不会立刻重复告警?
不会。冷却、告警级别、服务状态持久化在 alert-state.json,重启后冷却剩余时间
继续生效;状态恢复正常时会收到“已恢复”通知。
Q9:恢复通知在推送失败时会丢吗?
不会。恢复通知在推送成功后才落盘“已恢复”,失败保持告警状态、下一轮重试。
Q10:如何修改阈值?
用 CPU_PERCENT_WARNING、MEMORY_PERCENT_CRITICAL 等环境变量覆盖
(见 3.9 配置参考),systemd 方式写入 /etc/monitor-agent/env 后重启服务。
Q11:本地运行日志在哪里?
默认在状态目录 monitor-agent.log(~/.local/state/monitor-agent/,自动轮转);
systemd 部署在 /var/log/monitor-agent/monitor-agent.log。设
MONITOR_LOG_FILE= 空串可关闭文件日志,仅输出 stdout 给 journald 接管。
Q12:提示 /usr/bin/python3 不存在?
安装脚本会自动探测 python3 路径写入服务单元,正常不会出现。若仍遇到
(如安装后更换了 Python),重跑 sudo bash install.sh systemd 即可自动修正。
Q13:机器上没有 systemd(旧版 CentOS / Alpine / 容器)怎么办?
install.sh systemd 会检测并给出明确错误,不会留下无法启动的服务单元。
改用 sudo bash install.sh files 安装程序文件后手工启动
python3 /opt/monitor-agent/main.py,或用 openrc / sysvinit / supervisor /
cron 自启。CentOS 7 等 systemd < 235 的机器无需手工处理,脚本自动用兼容模板。
Q14:能在 Windows / macOS 上跑吗?
macOS 可运行完整指标监控与文件型日志;Windows 上指标与文件型日志可用,
命令型日志因缺少 POSIX shell 自动跳过;install.sh systemd 仅支持 Linux,
Windows 建议在 WSL 中运行以获得完整功能。
Q15:修改配置后必须重启吗?
MONITOR_CONFIG_FILE 指向的 JSON 配置(阈值/服务/日志清单/磁盘/渠道/静默窗口)改完发
SIGHUP 即可热重载,调度参数(周期/冷却/样本数)也会一并生效(见 3.10)。systemd 下修改
/etc/monitor-agent/env 的环境变量则必须 sudo systemctl restart monitor-agent,
因为 systemd 只在启动/重启时重新读取 EnvironmentFile。
为什么单机直推、不做汇聚?
每个主机独立采集、独立判定、直推钉钉:无中心依赖、无额外部署,主机之间互不影响,
适合个人与中小规模(几十台以内)。代价是没有全局视图与历史趋势,规模变大后再引入
Prometheus 等汇聚层做长期存储与大盘即可,本项目的告警留痕(alerts.jsonl)已按
UTC/ISO8601 落盘,可直接被外部平台消费。
为什么单进程 + 线程池,而不是多进程?
采集、推送、日志轮询都是轻量 IO,单进程内用 asyncio 编排、阻塞部分下沉到自有 线程池,足够承担单机监控负载,同时避免多进程带来的状态复制、信号处理与资源开销。 代价是单进程内必须严格遵守“阻塞不上事件循环”的纪律(命令型日志、钉钉推送均已在 线程池执行),这也是本项目最核心的实现约束。
为什么恢复通知“先推送成功,再落盘”?
若先落盘再推送,推送失败时状态已记为“已恢复”,恢复通知会永久丢失。改为推送成功 (或未配置 Webhook 仅留痕)后才迁移状态,失败则保持告警态、下一轮重试,保证 “恢复”这一关键信号不丢。代价是推送通道故障时,告警状态会多持续一轮。
为什么默认不监控任何业务?
默认零假设才能实现“放到任何机器都不误报”:不配服务清单就只报系统指标;配了但 主机未安装自动 SKIP。业务清单(服务/日志/磁盘挂载点)按主机注入,同一份代码可 安全铺到异构机器上。
命令型日志为什么按“状态快照 diff”触发?
docker ps -a 这类命令每轮返回全量状态,若每次都把命中行当新事件,容器持续
OOMKilled 会每个冷却周期重复告警且永不恢复。改为只对“本轮新出现”的命中行发事件,
持续状态只报一次,恢复按冷却窗口内无新命中判定。代价是同一行短暂消失再出现会
再次触发,属预期行为。
时间戳为什么统一存 UTC/ISO8601?
多主机跨时区时本地时间无法排序与对齐;统一以带时区的 UTC ISO8601 落盘(留痕/ 状态文件),钉钉消息展示层再转换为本地时间,兼顾机器可排序与人类可读。