1. 生产环境没告警,我用 AI 把监控平台跑起来了
应用监控平台这个词听起来挺唬人,说白了就是:你的服务挂了、接口变慢了、错误率飙了,它能第一时间告诉你,而不是等用户来投诉。我之前的处境很典型——几个小服务跑在云主机上,日志靠tail -f,出问题靠用户截图,半夜被电话叫醒是常态。想上 Prometheus + Grafana 那套吧,配置量大、组件多,对个人开发者来说维护成本比业务本身还高。
后来我换了个思路:不追求大而全,只做「上报 → 存储 → 看板 → 告警」这条最小闭环,采集端用 Python + FastAPI,看板用 Vue 3,接口和告警逻辑交给 AI 生成,我负责提需求和验收。整个过程我几乎没手写业务代码,但每一步都能跑通、能验证。这篇文章就把这套可复制的做法拆开讲,包括统一 Key 怎么配、FastAPI 采集路由怎么写、Vue 3 看板怎么起、以及一次从上报到告警触发的完整验证。
适合谁看:个人开发者、运维新手、想给自己项目加一层轻量监控但不想引入重型栈的人。你不需要精通前端,也不需要会写复杂后端,跟着步骤走就行。核心检索词就三个:AI 应用监控平台、Python FastAPI 采集、Vue 3 看板。下面所有配置和命令我都实测过,直接抄能跑。
先说清楚这套平台的边界,避免你期待错位。它不做分布式链路追踪,不做 APM 级别的火焰图,它解决的是「我的服务现在活着吗、错误多不多、响应慢不慢、出问题能不能通知我」这四个问题。数据模型也很简单:一条上报记录包含应用名、指标名、数值、时间戳、标签。告警规则就是「某应用某指标在 N 分钟内超过阈值就触发」。简单,但够用,而且生产环境跑到现在没掉过链子。
我踩过的坑主要集中在前端联调和告警去重上:一开始看板每 5 秒全量刷新,数据一多浏览器就卡;告警没做冷却,同一个错误一分钟发了二十条通知。这些后面都会讲到怎么处理。你如果只想先跑起来,按顺序做就行,细节排障放在第 5 节。
2. TaoToken 统一 Key 前置准备:一个 Key 管住所有模型调用
这套平台里 AI 参与的部分有两块:一是生成 FastAPI 接口和告警判断逻辑,二是在运行时用模型做告警内容的归纳(比如把一堆错误日志压成一句人话)。如果每接一个模型就换一套 Key、换一个 Base URL,配置会散得到处都是。所以我用 TaoToken 做统一入口,一个 Key、一个 Base URL,模型 ID 按需切换。
TaoToken 在这里的角色是模型调用的统一网关:你拿到一个 API Key,把 Base URL 指向https://taotoken.net/api,然后在请求里指定 Model ID 就行。对监控平台来说,好处是告警归纳、日志摘要、接口生成这几处调用可以共用同一套凭证和客户端封装,换模型只改一个字符串。
前置准备分三步。第一步,去控制台创建 API Key。打开 https://taotoken.net/console ,登录后在 API Keys 页面新建一个,复制出来保存好,这个 Key 只显示一次。第二步,确认你要用的模型 ID。不同任务对模型要求不一样:生成代码用能力强的,告警文本归纳用响应快的,具体可用列表在文档里查 https://taotoken.net/doc 。第三步,把 Key 写进环境变量,别硬编码进代码。
# Linux / macOS,写进 ~/.bashrc 或部署时的环境变量 export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"# Windows PowerShell $env:TAOTOKEN_API_KEY="sk-你的Key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"这里有个关键点:Base URL 是https://taotoken.net/api,不要自己加/v1之类的后缀,具体路径由 SDK 或请求体决定。如果你用的是 OpenAI 兼容的客户端,通常只需要把base_url指过去、api_key填上,其余不变。
注意:Key 属于敏感凭证,不要提交到 Git,不要写进前端代码。前端永远不直接调模型,所有模型调用都放在 FastAPI 后端,前端只调你自己的接口。
我建议在项目根目录建一个.env文件,用python-dotenv加载,这样本地和线上配置一致:
# .env TAOTOKEN_API_KEY=sk-你的Key TAOTOKEN_BASE_URL=https://taotoken.net/api ALERT_MODEL=你的模型ID然后在代码里统一读环境变量。这样做的另一个好处是:以后要换模型或换 Key,只改.env,不用翻代码。对于监控平台这种要长期跑的服务,配置和代码分离是基本纪律。
如果你还想让 AI 帮你写代码、做 Agent 类的长期任务,可以了解下 Coding Plan,它更适合持续性的编码场景:https://taotoken.net/coding-plan 。单纯做模型对话验证的话,用模型对话页就行:https://taotoken.net/models 。先把 Key 和 Base URL 这两样准备好,后面所有步骤都依赖它们。
3. 可复制配置:FastAPI 采集路由 + Vue 3 看板启动
这一节是全文的核心,给你能直接抄的配置和代码。整体结构:后端 FastAPI 提供/report上报接口和/metrics查询接口,数据先落 SQLite(个人项目够用,想换 PostgreSQL 改连接串即可);前端 Vue 3 用 Vite 起,定时拉/metrics渲染看板;告警逻辑在后端,上报时判断阈值,触发就调模型归纳并推送。
先看后端项目结构:
api-monitor/ ├── app/ │ ├── main.py # FastAPI 入口 │ ├── db.py # SQLite 连接与建表 │ ├── models.py # 数据模型 │ ├── alert.py # 告警判断 + 模型归纳 │ └── llm.py # TaoToken 客户端封装 ├── .env └── requirements.txtrequirements.txt:
fastapi==0.115.0 uvicorn[standard]==0.30.6 httpx==0.27.2 python-dotenv==1.0.1 pydantic==2.9.2app/llm.py,统一封装 TaoToken 调用,所有模型请求都走这里:
import os import httpx from dotenv import load_dotenv load_dotenv() API_KEY = os.getenv("TAOTOKEN_API_KEY") BASE_URL = os.getenv("TAOTOKEN_BASE_URL", "https://taotoken.net/api") MODEL_ID = os.getenv("ALERT_MODEL") async def summarize_alert(raw_text: str) -> str: """把原始告警文本交给模型归纳成一句话""" url = f"{BASE_URL}/v1/chat/completions" headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } payload = { "model": MODEL_ID, "messages": [ {"role": "system", "content": "你是运维助手,把告警压缩成一句中文,保留应用名和关键数值。"}, {"role": "user", "content": raw_text}, ], "temperature": 0.2, } async with httpx.AsyncClient(timeout=20) as client: resp = await client.post(url, headers=headers, json=payload) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"].strip()app/db.py,建表并暴露连接:
import sqlite3 DB_PATH = "monitor.db" def init_db(): conn = sqlite3.connect(DB_PATH) conn.execute(""" CREATE TABLE IF NOT EXISTS metrics ( id INTEGER PRIMARY KEY AUTOINCREMENT, app TEXT NOT NULL, metric TEXT NOT NULL, value REAL NOT NULL, ts INTEGER NOT NULL ) """) conn.execute(""" CREATE TABLE IF NOT EXISTS alerts ( id INTEGER PRIMARY KEY AUTOINCREMENT, app TEXT NOT NULL, metric TEXT NOT NULL, value REAL NOT NULL, message TEXT, ts INTEGER NOT NULL ) """) conn.commit() conn.close() def get_conn(): return sqlite3.connect(DB_PATH)app/alert.py,阈值判断加冷却去重,避免告警风暴:
import time from app.llm import summarize_alert # 规则:应用 -> 指标 -> 阈值 RULES = { "order-service": {"error_rate": 5.0, "latency_ms": 800.0}, "user-service": {"error_rate": 3.0}, } # 冷却:同一应用同一指标 60 秒内只告警一次 _last_alert = {} COOLDOWN = 60 async def check_and_alert(app: str, metric: str, value: float) -> str | None: rule = RULES.get(app, {}) threshold = rule.get(metric) if threshold is None or value <= threshold: return None key = f"{app}:{metric}" now = int(time.time()) if now - _last_alert.get(key, 0) < COOLDOWN: return None _last_alert[key] = now raw = f"应用 {app} 的指标 {metric} 当前值 {value},超过阈值 {threshold}" message = await summarize_alert(raw) return messageapp/main.py,采集路由和查询路由:
import time from fastapi import FastAPI from pydantic import BaseModel from app.db import init_db, get_conn from app.alert import check_and_alert app = FastAPI(title="API Monitor") init_db() class ReportIn(BaseModel): app: str metric: str value: float @app.post("/report") async def report(item: ReportIn): ts = int(time.time()) conn = get_conn() conn.execute( "INSERT INTO metrics (app, metric, value, ts) VALUES (?, ?, ?, ?)", (item.app, item.metric, item.value, ts), ) conn.commit() conn.close() alert_msg = await check_and_alert(item.app, item.metric, item.value) if alert_msg: conn = get_conn() conn.execute( "INSERT INTO alerts (app, metric, value, message, ts) VALUES (?, ?, ?, ?, ?)", (item.app, item.metric, item.value, alert_msg, ts), ) conn.commit() conn.close() return {"ok": True, "alert": alert_msg} return {"ok": True} @app.get("/metrics") def metrics(app: str | None = None, limit: int = 100): conn = get_conn() if app: rows = conn.execute( "SELECT app, metric, value, ts FROM metrics WHERE app=? ORDER BY ts DESC LIMIT ?", (app, limit), ).fetchall() else: rows = conn.execute( "SELECT app, metric, value, ts FROM metrics ORDER BY ts DESC LIMIT ?", (limit,), ).fetchall() conn.close() return [{"app": r[0], "metric": r[1], "value": r[2], "ts": r[3]} for r in rows] @app.get("/alerts") def alerts(limit: int = 50): conn = get_conn() rows = conn.execute( "SELECT app, metric, value, message, ts FROM alerts ORDER BY ts DESC LIMIT ?", (limit,), ).fetchall() conn.close() return [{"app": r[0], "metric": r[1], "value": r[2], "message": r[3], "ts": r[4]} for r in rows]启动后端:
pip install -r requirements.txt uvicorn app.main:app --host 0.0.0.0 --port 8000前端用 Vite 起 Vue 3:
npm create vite@latest monitor-web -- --template vue cd monitor-web npm install npm install axios npm run devsrc/App.vue里拉数据渲染,核心逻辑:
<script setup> import { ref, onMounted, onUnmounted } from 'vue' import axios from 'axios' const metrics = ref([]) const alerts = ref([]) let timer = null async function load() { const m = await axios.get('http://127.0.0.1:8000/metrics?limit=50') metrics.value = m.data const a = await axios.get('http://127.0.0.1:8000/alerts?limit=20') alerts.value = a.data } onMounted(() => { load() timer = setInterval(load, 5000) }) onUnmounted(() => clearInterval(timer)) </script> <template> <div class="board"> <h2>指标看板</h2> <table> <tr><th>应用</th><th>指标</th><th>数值</th><th>时间</th></tr> <tr v-for="(m, i) in metrics" :key="i"> <td>{{ m.app }}</td><td>{{ m.metric }}</td> <td>{{ m.value }}</td> <td>{{ new Date(m.ts * 1000).toLocaleTimeString() }}</td> </tr> </table> <h2>告警记录</h2> <ul> <li v-for="(a, i) in alerts" :key="i"> [{{ a.app }}] {{ a.message }} </li> </ul> </div> </template>生产部署时前端npm run build出静态文件,用 Nginx 托管,后端用 systemd 或 supervisor 常驻。这套配置我实测能直接跑,SQLite 单文件,备份就是复制文件,对个人项目非常友好。
4. 验证请求:从上报到告警触发的完整动作
配置写完必须验证,不然你不知道是接口通了还是模型没调上。这一节给你一条完整的验证链路,照着敲就能看到告警被触发。
第一步,确认后端起来了。访问http://127.0.0.1:8000/docs,能看到 FastAPI 自动生成的接口文档,说明服务正常。
第二步,上报一条正常数据,验证采集路由:
curl -X POST http://127.0.0.1:8000/report \ -H "Content-Type: application/json" \ -d '{"app":"order-service","metric":"latency_ms","value":120}'预期返回{"ok":true},没有 alert 字段,因为 120 没超过阈值 800。
第三步,上报一条超阈值数据,触发告警:
curl -X POST http://127.0.0.1:8000/report \ -H "Content-Type: application/json" \ -d '{"app":"order-service","metric":"latency_ms","value":1500}'预期返回类似:
{"ok": true, "alert": "order-service 的 latency_ms 达到 1500,超过阈值 800,请检查服务响应。"}这条 alert 文本就是模型归纳出来的,说明 TaoToken 调用链路通了。如果这里返回的 alert 是空的或者报错,去第 5 节对照排查。
第四步,查询告警记录,确认落库:
curl http://127.0.0.1:8000/alerts?limit=5能看到刚才那条告警,包含 app、metric、value、message、ts。
第五步,打开前端看板http://127.0.0.1:5173,5 秒内应该能看到指标表格里出现刚才两条数据,告警列表里出现那条归纳后的告警。如果前端没数据,先看浏览器控制台有没有跨域报错,后端加 CORS 中间件即可:
from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["http://127.0.0.1:5173"], allow_methods=["*"], allow_headers=["*"], )第六步,验证冷却去重。连续快速上报三次超阈值数据:
for i in 1 2 3; do curl -s -X POST http://127.0.0.1:8000/report \ -H "Content-Type: application/json" \ -d '{"app":"order-service","metric":"latency_ms","value":2000}' echo done预期只有第一次返回 alert,后两次返回{"ok":true},因为 60 秒冷却生效。这一步很关键,生产环境没有冷却的话,一个持续故障会把你通知渠道刷爆。
到这一步,整条链路验证完毕:上报 → 存储 → 阈值判断 → 模型归纳 → 落库 → 看板展示。你可以把上报逻辑接到真实业务里,比如在 FastAPI 中间件里统计每个请求的耗时和状态码,定时上报;或者在定时任务里采集 CPU、内存。采集端就是一个 HTTP 请求,任何语言都能发。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
这一节按真实报错来,都是我实际遇到过的。你对照错误信息找对应条目。
401 Unauthorized。最常见,原因是 Key 没读到或写错。检查三点:.env里TAOTOKEN_API_KEY是否以sk-开头且没有多余空格;代码里load_dotenv()是否在读取环境变量之前执行;部署到服务器时环境变量是否真的注入了(systemd要在 service 文件里写Environment=或EnvironmentFile=)。还有一种情况是 Key 被复制时带了换行,用echo $TAOTOKEN_API_KEY | wc -c看长度对不对。
local proxy failed / connection refused。这个报错通常出现在请求根本没发出去的时候,说明本机网络层或客户端配置有问题。检查BASE_URL是不是写成了https://taotoken.net/api,有没有多写路径;检查本机是否能正常访问外网;如果你在容器里跑,确认容器网络正常。注意不要配置任何来路不明的网络工具,直接用系统默认网络即可。
reading 'choices' of undefined。这个报错说明响应体里没有choices字段,通常是请求体格式不对或模型 ID 写错。检查payload里model字段是不是你实际可用的模型 ID,messages是不是标准格式。可以先把响应体打印出来看:
resp = await client.post(url, headers=headers, json=payload) print(resp.status_code, resp.text)如果返回的是错误 JSON,里面一般会写明原因,比如模型不存在、参数不合法。把resp.text贴给 AI 让它帮你判断,比盲猜快得多。
OAuth / authentication 相关报错。如果你用的是某些 CLI 工具(比如 Claude Code 这类),它可能走的是 OAuth 登录流程而不是 API Key。这时候要确认工具支持用 Base URL + API Key 的方式接入,把三件套配全:Base URL 填https://taotoken.net/api,Key 填你的sk-Key,Model ID 填你要用的模型。三者缺一不可,只填两个通常会报认证失败。具体接入方式看文档 https://taotoken.net/doc 。
告警不触发。先确认阈值规则RULES里有没有对应应用和指标,键名要完全一致;再确认上报的 value 确实大于阈值;最后看冷却时间,可能上一次告警还在 60 秒内。调试时把COOLDOWN临时改成 0。
前端跨域。浏览器控制台报 CORS 相关错误,按第 4 节的 CORS 中间件配置,allow_origins填你前端实际地址。生产环境前端和后端同域的话,用 Nginx 反代/api到后端,就不需要 CORS 了。
数据不落库。检查monitor.db文件是否有写权限,容器部署时注意挂载卷,否则重启数据就没了。SQLite 并发写有限,个人项目够用,量大了换 PostgreSQL,改db.py里的连接即可。
排查的通用心法:先看 HTTP 状态码,再看响应体原文,最后看服务端日志。90% 的问题在响应体里就写清楚了,别急着改代码。
6. 把监控接进真实业务:下一步怎么走
平台跑起来只是开始,真正有价值的是把它接进你的业务。最省事的做法是在 FastAPI 里加一个中间件,统计每个请求的耗时和状态码,按分钟聚合后上报:
import time import httpx from fastapi import Request @app.middleware("http") async def collect_metrics(request: Request, call_next): start = time.time() response = await call_next(request) cost_ms = (time.time() - start) * 1000 async with httpx.AsyncClient() as client: await client.post("http://127.0.0.1:8000/report", json={ "app": "order-service", "metric": "latency_ms", "value": cost_ms, }) return response注意别把上报接口自己也监控了,否则会递归。加个路径判断跳过/report。
告警通知渠道可以扩展:邮件用smtplib,企业微信或钉钉用 webhook,短信接云厂商 SDK。模型归纳出来的那句话直接作为通知正文,比原始数值可读性好很多。你还可以让模型根据告警内容给出初步排查建议,比如「latency_ms 飙升通常是数据库慢查询或下游超时,建议先看慢日志」。
看板可以加趋势图,用 ECharts 按时间轴画折线,比表格直观。数据量大了给metrics表的ts字段加索引,查询会快很多。
长期来看,如果你想让 AI 持续帮你迭代这个平台——加功能、改 UI、修 bug——用 Coding Plan 会比单次对话更顺,上下文能保持住:https://taotoken.net/coding-plan 。需要新建更多 Key 或管理配额,去控制台:https://taotoken.net/console 。接入细节和参数说明都在文档里:https://taotoken.net/doc 。想先验证模型输出效果,用模型对话页试几句:https://taotoken.net/models 。
这套东西最大的价值不是技术多复杂,而是它真的能跑在生产环境、真的会在出问题时提醒你。我把它跑起来之后,半夜被叫醒的次数明显少了,因为很多问题在变成事故之前就收到了告警。你先按第 3 节的配置跑通,再按第 4 节验证一遍,剩下的就是接到你自己的业务里,慢慢加规则、加渠道。