你的 AI 编程助手,这个月到底花了多少钱?

禾几海
禾几海
发布于 2026-08-08 / 4 阅读
0
0

文章中的内容和图片内容均已做脱敏处理

零依赖、零侵入、零感知,CPU 不到 0.1%,内存不到 30MB。这篇文章是 CodeBuddy Coding Monitor 的完整复盘,从动机讲到架构,再讲到踩过的坑。


如果你去问一个天天用 AI 编程的人:这个月 AI 帮你写了多少代码?花了多少钱?哪个模型最划算?

大概率一个都答不上来。不是他懒,是压根没人给他看数据。Token 消耗、模型分布、缓存命中率、AI 代码占比,全是黑盒。企业环境更狠,不让装代理、不让抓包,安全软件盯着每一个网络连接。

所以我自己写了一个:CodeBuddy Coding Monitor。不碰网络、不装代理、不写日志文件,把 AI 编程的每一笔账算得明明白白。


一、先上三个最硬的数据

0 个第三方依赖

整个后端只用 Python 标准库:sqlite3rejsonhttp.serverthreadingossubprocess。没有 pip install,没有虚拟环境,没有依赖地狱,也不用担心供应链安全。打包出来就一个 exe,双击即用。

市面上的监控全家桶(Prometheus 加 Grafana 加 node_exporter 那种),装完十几二十个组件。别人开的是舰队,我骑的是单车,而且单车还跑得更快。

CPU < 0.1%,内存 < 30MB

靠文件指针定位(seek + tell)做只读增量读取,只处理新增内容,第一次全量扫完就不再回头。空闲时资源占用低到可以忽略,跑在开发机上你完全感觉不到它。

监控工具做到这份上,基本等于隐身。

单 exe 交付

PyInstaller 打成一个 coding-monitor.exe,Windows 计划任务开机自启,pythonw 后台静默跑。部署成本约等于零:拷过去、双击、完事。


二、企业安全软件都发现不了它

市面上的 AI 用量监控,主流方案是代理中转或者 MITM 抓包,把 IDE 的网络流量拦下来数 token。听起来很美,企业环境里这就是自爆,安全软件秒告警,防火墙直接断连。

Coding Monitor 的路子完全不同。不装代理、不抓中间人、不拦网络,只以只读消费者的身份,增量读 CodeBuddy IDE / CLI 写下的本地日志。日志文件只读打开,不修改、不写入、不备份,只监听 127.0.0.1,一个端口都不对外开。

日志这玩意儿天生可审计、可追溯,比抓包稳。被监控产品发版更新?只要日志格式不变,采集链路纹丝不动。

一句话:这套方案能过安全审计,其他方案过不了。


三、杀手锏:别人只看成本,我能看产出

这是 Coding Monitor 跟市面上所有 token 统计工具拉开差距的地方。不光是记账,还帮你评估投入产出。

统计看板长这样:token 使用趋势、模型分布、缓存命中率、Skill 有效性、MCP 健康度,多维度交叉:

coding5.jpg

管理者看成本趋势、Credit 消耗,这个月 AI 花了多少钱一目了然;开发者看模型选择和缓存效率,哪个模型又贵又慢赶紧换掉;团队负责人看 AI 代码占比和 Skill 复用度,团队是真用上了还是装样子。

下面这张是 AI 代码占比看板,通过 Hook 采集工具调用指标,按提交和分支维度量化 AI 对代码库的贡献:

AI 占比看板

市面上的工具只会告诉你花了多少,这个工具能告诉你值不值。


四、AI 干了啥,回放给你看

光有统计数字不够。Coding Monitor 支持全量会话回放,IDE 和 CLI 的每一次对话都能回溯,Markdown 渲染、工具调用卡片、代码 diff 视图、SSE 实时刷新:

Coding Monitor 会话记录

AI 做了什么、怎么做的、改了哪些代码,点开回放一清二楚。复盘、审计,甚至甩锅,都有据可查。


五、整体架构

从数据源到浏览器渲染,分层架构如下:

graph TB subgraph 数据源层 IDE["IDE 日志 文本行"] CLI["CLI 会话 JSONL"] HOOK["Hook 数据 JSONL"] end subgraph 采集层 DSM["数据源管理器"] DS1["IdeLogSource"] DS2["CliSource"] DS3["HookSource"] end subgraph 解析层 PARSER["日志解析器 规则驱动"] RULE["规则加载器 远程+内置"] NORM["归一化层 UnifiedStep"] end subgraph 存储层 DB["SQLite WAL"] DATAAPI["DataAPI 查询契约"] end subgraph 服务层 API["TokenAPI 内核端点"] ROUTER["两段式路由"] BUS["事件总线"] MAINT["维护模块 三级清理"] UPDATER["自动更新"] end subgraph 配置层 CFG["配置管理 热加载"] end subgraph 插件平台 STATS["stats 统计看板"] MONITOR["monitor 实时监控"] CODERATIO["coderatio AI代码占比"] TELEMETRY["telemetry 工具遥测"] CONVERSATIONS["conversations 会话记录"] SYSTEM["system 系统管理"] end subgraph 前端层 SHELL["Dashboard 壳 导航+亮暗"] DASHBOARDS["插件 Dashboard"] end subgraph 远程服务 RULESERVER["远程规则服务器"] UPDATESERVER["更新清单服务器"] end IDE --> DS1 CLI --> DS2 HOOK --> DS3 DS1 --> DSM DS2 --> DSM DS3 --> DSM DSM --> PARSER PARSER --> NORM RULE --> PARSER RULESERVER -.-> RULE NORM --> DB NORM --> BUS DB --> DATAAPI DATAAPI --> API BUS --> STATS BUS --> MONITOR BUS --> CODERATIO BUS --> TELEMETRY BUS --> SYSTEM ROUTER --> STATS ROUTER --> MONITOR ROUTER --> CODERATIO ROUTER --> TELEMETRY ROUTER --> CONVERSATIONS ROUTER --> SYSTEM STATS --> DATAAPI SYSTEM --> DATAAPI CFG -.-> DSM SHELL --> DASHBOARDS API --> SHELL ROUTER --> DASHBOARDS UPDATESERVER -.-> UPDATER UPDATER --> SYSTEM MAINT -.-> IDE MAINT -.-> DB

整个系统干的事就四件:读日志、归一化、存 SQLite、起一个本地 HTTP 服务给浏览器看。听起来朴素,能不能一直稳,全看工程细节。

主 Dashboard 总览页:总步数、总 Token、Credit 消耗、Token 分布、缓存命中率、每日趋势:

CodeBuddy Coding Monitor 主 Dashboard 总览

端到端数据流水线:

graph LR A["IDE/CLI 写入日志"] --> B["Watcher 增量读取"] B --> C["Parser 规则解析"] C --> D["Normalizer 归一化"] D --> E["SQLite WAL"] D --> F["EventBus 事件广播"] F --> G["插件 聚合/统计/推送"] E --> H["DataAPI 查询"] H --> I["TokenAPI HTTP响应"] G --> I I --> J["Dashboard 前端渲染"]

六、内核 + 插件平台:对标 VS Code 的插件化设计

系统拆成两块:稳定内核负责采集、解析、存储、HTTP 服务这些基础设施,插件平台承接所有扩展能力。新插件放进目录就生效,自动检测、自动加载,不用重启服务。

graph TB subgraph 插件平台 PM["PluginManager 加载/生命周期/热加载"] HR["PluginHotReloader 目录监听"] PC["PluginContext 隔离环境"] end subgraph Context注入 BUS2["EventBus 订阅"] CFG2["插件私有配置"] RT["路由注册"] SP["后台任务"] DS["数据源管理器"] RSP["响应入口"] SH["shutdown 回调"] end subgraph 插件生命周期 LOAD["on_load 初始化"] EVENT["on_event 事件处理"] REG["register_routes 路由注册"] UNLOAD["on_unload 卸载清理"] end subgraph 内置插件 S1["stats 统计看板"] S2["system 系统管理"] S3["monitor 实时监控"] S4["coderatio AI代码占比"] S5["telemetry 工具遥测"] S6["conversations 会话记录"] end subgraph 自定义插件 CX["your-plugin 放入即生效"] end PM --> HR PM --> PC PC --> Context注入 PM --> 插件生命周期 PM --> 内置插件 PM --> 自定义插件

这套设计的妙处:

  • 内核迭代不破坏插件,插件开发不污染内核,两边各干各的;
  • 进程内事件总线:数据写入后自动广播,插件按需订阅,单个插件挂了也不影响内核和其他插件;
  • 两段式 HTTP 路由:插件以自己的名字为前缀注册独立路由,互不打架,还白嫖内核的 CORS、错误处理这些基础设施;
  • 免重启热加载:改完插件代码刷新页面就生效,开发体验是真的好。

一句话:内核稳定,边缘灵活。教科书里的解耦长啥样,这就是。


七、数据采集:异构数据源,一个归一化层全吃掉

CodeBuddy IDE 日志是文本行,CLI 会话是 JSONL,字段、结构、语义都不一样。Coding Monitor 用一个归一化层把差异消化在内核内部,插件开发者只消费标准数据:

graph TB subgraph 异构数据源 S1["IDE 日志 文本行"] S2["CLI 会话 JSONL"] S3["Hook 数据 JSONL"] S4["未来数据源 Web API"] end subgraph 数据源抽象层 IF["DataSource 抽象接口"] MGR["DataSourceManager 生命周期管理"] end subgraph 归一化层 NORM["Normalizer UnifiedStep"] SID["source_id 全链路传递"] end subgraph 上层消费者 P["插件 stats/monitor/..."] D["Dashboard 前端筛选"] end S1 --> IF S2 --> IF S3 --> IF S4 -.-> IF IF --> MGR MGR --> NORM NORM --> SID SID --> P SID --> D

多智能体(subagent)归因、步骤号序列化、角色派生,这些复杂逻辑全部集中在归一化层,不用在每个插件里重复实现。以后要接新数据源(Web API、别的 IDE),扩展一下归一化映射就行,上层零改动。


八、运维:一个工具把运维做成了产品

监控工具最怕装上就忘、坏了没人修。Coding Monitor 把运维门槛压到最低:

  • 配置热加载:改完配置约 30 秒自动生效,支持动态启停数据源,服务不用重启;
  • 远程解析规则:解析规则像杀毒软件的病毒库一样远程下发,sha256 校验加本地缓存加周期热更新,还有内置规则兜底。CodeBuddy 日志格式变了?不用重新打包发版,下发一条规则全搞定;
  • 自动更新:版本检查、下载、备份、替换、重启,全流程后台完成,更新失败可回滚。更新脚本不被 Windows job object 锁死,父进程退出后依然能活下来完成替换;
  • 三级日志清理:归档文件、旧目录、空目录逐级清理,活跃目录保护(5 分钟内有写入不清理),磁盘自动回收,不会越跑越臃肿。

部署架构:

graph TB subgraph 用户机器 TASK["Windows 计划任务 开机自启"] EXE["coding-monitor.exe"] RUNTIME["_internal 运行时依赖"] DATA["data SQLite+缓存规则"] LOGDIR["log 日志文件"] CFGF["config.json 配置文件"] end subgraph 被监控产品 CB["CodeBuddy IDE"] CBCLI["CodeBuddy CLI"] end subgraph 远程服务2 RS["规则服务器"] US["更新服务器"] end TASK --> EXE EXE --> RUNTIME EXE --> DATA EXE --> LOGDIR EXE --> CFGF EXE -->|"127.0.0.1"| BROWSER["浏览器 Dashboard"] CB -->|"写入日志"| LOGL["IDE 日志目录"] CBCLI -->|"写入日志"| LOGC["CLI 会话目录"] LOGL -->|"只读增量读取"| EXE LOGC -->|"只读增量读取"| EXE RS -.->|"规则热更新"| EXE US -.->|"版本检查+下载"| EXE

技术栈一览:

层面 技术
后端 Python 3.10+(仅标准库:sqlite3 / re / json / http.server / threading / os / subprocess)
存储 SQLite(WAL 模式)
前端 单 HTML 文件 + ECharts(CDN + 本地化双模式)
打包 PyInstaller → 单 exe(coding-monitor.exe)
部署 Windows 计划任务开机自启,pythonw 后台运行

九、六个隐蔽 bug,全部追到根因

这部分是我觉得最值钱的。踩过的坑,每一个都不报错、悄悄出错,最考验底层功底。

坑 1:时区双重转换

前一天 16:00 后的新数据被错误归入当天。库里存的是本地时间,查询又套了 SQLite 的 localtime 修饰符,多偏了 8 小时。把 localtime 去掉就好了。

这种 bug 不会报错,只会让统计悄悄偏掉,特别隐蔽。写 SQLite 时间函数前,先搞清楚你存的是 UTC 还是本地时间。

坑 2:多模型会话显示错误

一个会话用多个模型时,显示的模型名对不上。原因是聚合查询用了 MAX() 取模型名,字符串上的 MAX() 是字母序比较,不是"最近一条",模型 ID 和名称还可能来自不同步骤。改成子查询取最后一步模型,再用 GROUP_CONCAT(DISTINCT) 展示全部模型。

SQL 聚合的语义陷阱,多值展示一定要用 GROUP_CONCAT 或者子查询取最新,别拿 MAX 糊弄。

坑 3:热加载读到过期缓存

改了插件代码,行为没变,查了半天发现 Python 的 source_file_loader 按文件路径复用了旧的 pyc 字节码缓存。每次加载用唯一模块名,清掉 __pycache__,临时关闭字节码写回,解决。

热加载场景必须主动打破"同路径同名"的缓存假设,不然"改了没生效"能让你怀疑人生。

坑 4:并发请求 socket 句柄错乱

多线程处理 HTTP 请求时触发 WinError 10038。插件管理器的请求状态放在实例属性上被多线程共享,socket 句柄互相覆盖。改成线程局部存储(threading.local())完事。

HTTP 服务天然多线程,跟"当前请求"相关的状态千万别放共享实例属性上。

坑 5:subprocess 编码崩溃

中文 Windows 下,子进程输出里带 UTF-8 中文直接 UnicodeDecodeError,整条链路崩掉。原因是 subprocess 默认用系统编码 GBK 解码。所有调用显式指定 encoding='utf-8', errors='replace',再对 None 返回值做防御。

跨平台 Python 工具,编码必须显式指定,别赌系统默认值,这是本地化环境最常见的大坑。

坑 6:日志系统白名单失配

大量日志被静默丢弃,一个报错都没有。白名单写的是模块名 data_source,实际 logger 名是带包前缀的 core.data_sourceimportlib 动态加载的命名空间跟静态导入也不一样。白名单改成实际 logger 名,动态插件按完整模块名前缀匹配。

日志过滤的前缀必须跟 logging.getLogger("") 的实际名称精确匹配。这类问题只会在你想排查的时候冒出来折磨你。


十、安全设计

监控工具天生容易被当成"内鬼",所以安全设计前置:

  • 只监听 127.0.0.1,不对外暴露;
  • 不记录任何密钥和用户凭证;
  • 日志文件只读打开,不修改、不写入、不备份;
  • 解析异常静默处理,不往响应体里泄露任何敏感信息。

十一、最后

这套系统最大的收获,说不上是哪个具体功能,倒是验证了一个道理:轻量级工具不需要重度框架,也能做到工业级质量。

架构上:数据源抽象加归一化层消化多端差异;内核插件分离把变化隔离在边缘;事件总线加 SSE 实现零轮询实时;配置、规则、更新全部支持远程热更新。

工程上:纯标准库没有依赖负担;增量读取、只读打开、异常静默,对被监控产品零干扰;线程局部存储、显式编码、唯一模块名,每个并发、编码、缓存陷阱都被显式处理掉。

最后想说:工具型产品的最高境界,是用户忘记它的存在。这套系统一直在后台默默跑着,开发者唯一想起它的时刻,是看到 Dashboard 上某个数据突然飙高想查个究竟的时候。而这,恰恰是它存在的意义。


评论