内容
# 部署后监控
MCP 服务器,包含两个用于比较服务两个部署版本之间错误模式的工具。两个工具都通过 SSH 在远程主机上搜索日志文件,将匹配的行标准化为稳定的签名,并将每个不同的错误分类为 **新增**, **回归**, 或 **预先存在**。
## 两个工具
### `diagnose_deployment` — 存档主机,版本对比
比较 `service-v1.43` 和 `service-v1.42` 在长期日志存档上的文件。适用于:
- 您想比较两个特定的过去版本。
- 您正在调查的部署已经过去一天以上。
### `diagnose_deployment_live` — 今日主机,时间窗口
比较部署时间戳之后和之前在同一组文件上的实时 rsyslog 主机。适用于:
- 您刚刚部署完毕,希望实时检查回归问题。
- 两个版本混合在同一日志文件中。
## 设置
1. 安装 [uv](https://docs.astral.sh/uv/)。
2. 从当前目录运行:`uv sync`。
3. 将 `config.example.toml` 复制到 `config.toml` 并填写您的配置值,或者设置环境变量 `SSH_HOST_ALIAS`, `LOG_ROOT`, 和(可选)`LIVE_SSH_HOST_ALIAS`, `LIVE_LOG_ROOT`。
4. 在 `~/.ssh/config` 中添加匹配的 `Host` 块 — 每个目标主机一个。例如:
```
Host log-host
HostName logbackup.example.com
User dev
IdentityFile ~/.ssh/your-key
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 60s
Host live-host
HostName rsyslog.example.com
User dev
IdentityFile ~/.ssh/your-key
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 60s
```
## 接入 GitHub Copilot
在您的 Copilot MCP 配置中添加:
```json
{
"mcpServers": {
"post-deploy-monitor": {
"command": "uv",
"args": ["run", "--project", "<absolute-path-to-this-dir>", "post-deploy-monitor"],
"env": {
"SSH_HOST_ALIAS": "log-host",
"LOG_ROOT": "/var/log/myapp",
"LIVE_SSH_HOST_ALIAS": "live-host",
"LIVE_LOG_ROOT": "/rsyslog"
}
}
}
}
```
实时主机环境变量(`LIVE_SSH_HOST_ALIAS`, `LIVE_LOG_ROOT`)是可选的。如果省略,则仅 `diagnose_deployment` 可用;调用 `diagnose_deployment_live` 将返回一个结构化的 `live_host_not_configured` 错误。
## 单次调用主机覆盖
两个工具都接受一个可选的 `ssh_host_alias` 参数,该参数覆盖了针对该调用的配置值。在不重启服务器的情况下,针对非默认集群的日志主机(例如,暂存与生产)非常有用。覆盖值将针对 `[A-Za-z0-9._-]+` 进行验证。
## `diagnose_deployment` 工作原理(存档主机)
每次调用,服务器都会通过 SSH 登录到存档主机,并对当前版本的日志文件运行 `zgrep`,然后对前一版本的文件运行 `zgrep`。匹配的行被标准化为稳定的签名(时间戳,UUID,请求 ID,行号被剥离)。在当前版本中看到的每个签名被分类为:
- **新增** — 在以前版本中未曾出现(可能由您的更改引起)
- **回归** — 在两个版本中都存在,但在当前版本中频率超过 3 倍
- **预先存在** — 在两个版本中以相似的速率存在
必需的路径布局(`log_locator.py`):
```
<LOG_ROOT>/<YYYY>/<MM>/<DD>/<vm_host>/<service>-<version_underscored>/<app_instance>/<file>.gz
```
## `diagnose_deployment_live` 工作原理(rsyslog 主机)
每次调用,服务器都会通过 SSH 登录到 rsyslog 主机,并对服务的所有匹配文件运行一次 `zgrep`。每个匹配行的时间戳被提取(优先使用消息中的括号 `[YYYY-MM-DD HH:MM:SS]` 格式;回落到 syslog 样式的 `MMM DD HH:MM:SS` 前缀锚定在 `deploy_start`)。行被分成三个窗口:
- **之前**:时间戳在 `[deploy_start − previous_window, deploy_start)`
- **部署**(滚动):时间戳在 `[deploy_start, deploy_end]` — **丢弃**。此间隔包含来自不同时间重启实例的混合版本日志;分类它们会污染差异。
- **当前**:时间戳在 `(deploy_end, now]`
然后在两个方面(之前与当前)运行相同的 **新增** / **回归** / **预先存在** 分类。丢弃的部署窗口中的行数被报告为 `lines_discarded_in_deploy_window`,以便读者可以检查滚动间隔。
### 输入
开发人员传递:
- `service`:例如 `"pvcore"`。
- `current_version`, `previous_version`:仅元数据(版本存在于日志内容中,而不是此主机上的路径)。
- `deploy_start`, `deploy_end`:滚动开始和结束时间。每个接受:
- `"HH:MM"` 或 `"HH:MM:SS"` — 今日,在服务器的本地时区。如果结果时间相对于当前时间是未来,则回滚到昨天(处理"I deployed yesterday at 23:50")。
- 完整的 ISO-8601 偏移量,例如 `"2026-05-07T14:30:00+05:30"`。
- `previous_window` (必需):前一侧回溯的持续时间字符串,例如 `"2h"`,`"30m"`,`"1h15m"`。前一侧跨度为 `[deploy_start − previous_window, deploy_start)`。
必需的路径布局(`live_locator.py`):
```
<LIVE_LOG_ROOT>/<service>/*.log
<LIVE_LOG_ROOT>/<service>/*.log.<n>.gz
```
## 测试
```bash
uv run pytest
```