bheus/guiltyspark

GitHub: bheus/guiltyspark

guiltyspark 是一个 AI 驱动的 Loki 日志监控 agent,能够自动检测异常、诊断根因,并借助 Codex 生成修复补丁和 GitHub PR,实现从日志告警到代码修复的闭环自动化。

Stars: 0 | Forks: 1

# guiltyspark `guiltyspark` 是一个可部署的 agent,用于监控 Loki 日志、发现未知问题,并将有用的发现转化为可执行的报告。它旨在运行于任何常驻在线的 Docker 主机上。 其核心循环设计得非常简单: 1. 查询 Loki 获取最近的日志。 2. 将嘈杂的日志行分组归类为事件。 3. 调用 `codex exec` 来解释可能的 bug、配置错误和改进机会。 4. 将发现结果存储在本地,避免重复的噪音导致无休止的报警。 5. 将事件与其配置的 GitHub 仓库关联。 6. 在一个隔离的克隆环境中,让 Codex 准备最小修复和回归测试。 7. 强制执行补丁策略,运行验证,并选择性开启 PR。 ## 它的功能 - 查看所有发送日志到 Loki 的应用。 - 搜索错误、重试、认证失败、服务降级、可疑的重启、缓慢的请求和反复出现的警告。 - 汇总证据,而不是直接转发原始的日志垃圾信息。 - 维护 SQLite 状态,用于游标记录和去重。 - 输出 JSONL 格式的发现结果,支持实时追踪、转发或接入通知系统。 - 使用通过 ChatGPT 认证的 Codex 会话,因此可以使用 ChatGPT Pro 额度来覆盖使用量,而无需单独计费的 OpenAI API key。 - 可在带有 Docker Compose 的树莓派上运行。 ## 尚未实现的功能 - 除非在 `draft-pr` 或 `pr` 模式中明确配置了目标,否则它绝不会推送代码。 - 它不假定此 Codex 聊天能控制该树莓派。 - 除非你启用了修复/PR 工作流,否则它不需要本地仓库检入。 ## 快速开始 ``` cp .env.example .env # 在 .env 或 Portainer 中设置 GUILTYSPARK_TARGETS_JSON。 docker compose pull docker compose run --rm guiltyspark codex login --device-auth docker compose up ``` 要在本地构建容器而不是拉取 GHCR: ``` docker build -t guiltyspark:local . GUILTYSPARK_IMAGE=guiltyspark:local docker compose up ``` 进行本地试运行: ``` uv sync --dev --no-editable cp .env.local.example .env.local uv run --no-editable --reinstall-package guiltyspark guiltyspark once ``` 在本地运行测试: ``` uv run --no-editable pytest ``` ### 仪表板前端 仪表板 UI 是一个位于 `frontend/` 目录下的 React + TypeScript 应用。其构建 输出会存放在 `src/guiltyspark/web/` 中,Python 后端通过 `importlib.resources` 来提供这些文件。该目录是 **被 git 忽略的构建产物** —— Docker 镜像会在 Node 阶段构建它,并且 wheel 包会强制包含它。 ``` # 日常 UI 工作:Vite dev server(将 /api 代理到运行在 :8343 上的 dashboard) guiltyspark dashboard & # or run the daemon/compose service npm --prefix frontend install npm --prefix frontend run dev # open the printed http://localhost:5173 # 生成由 Python server 直接提供的 bundle npm --prefix frontend run build ``` 因为 `src/guiltyspark/web/` 没有被提交到仓库,在全新检出后直接运行原生的 `guiltyspark dashboard` 将无法提供任何内容,除非你运行过一次构建(或使用开发 服务器)。Docker 构建会自动处理此过程。 ## 配置 所有设置均通过环境变量进行。最重要的变量如下: | 变量 | 用途 | | --- | --- | | `LOKI_URL` | Loki 的 Base URL,例如 `http://loki:3100`。 | | `LOKI_QUERY` | 要监控的 LogQL 查询语句,例如 `'{job=~".+"}'`。 | | `LOKI_LIMIT` | 每次查询范围获取的最大日志行数。默认为 `5000`。 | | `LOKI_BEARER_TOKEN` | 用于经过认证的 Loki 实例的可选 bearer token。 | | `LOKI_BASIC_AUTH` | 用于 Loki 的可选 `user:password` 基础认证凭证。 | | `CODEX_HOME` | 持久化的 Codex 认证/配置目录。在 Docker 中默认为 `/data/codex`。 | | `GUILTYSPARK_INTERVAL_SECONDS` | daemon 模式下的轮询间隔。默认为 `300`。 | | `GUILTYSPARK_LOOKBACK_SECONDS` | 如果不存在游标,初始的回溯时间。默认为 `900`。 | | `GUILTYSPARK_MIN_EVENTS` | 一组日志行被算作一个事件所需的最小事件数。默认为 `2`。 | | `GUILTYSPARK_MAX_INCIDENTS_PER_RUN` | 单个轮询周期内分析的事件数量上限。默认为 `8`。 | | `GUILTYSPARK_STATE_PATH` | SQLite 状态路径。 | | `GUILTYSPARK_FINDINGS_PATH` | JSONL 发现结果输出路径。 | | `GUILTYSPARK_RUNBOOK_PATH` | agent 在分析前读取的 Markdown 运行手册。 | | `GUILTYSPARK_NOTIFY_WEBHOOK_URL` | 用于推送新发现结果的可选通用 JSON webhook。 | | `RESEND_API_KEY` | Resend API key。当 guiltyspark 开启一个 PR 时会发送一封电子邮件(仅对它自己开启的 PR 触发,绝不会干预你的手动 PR)。 | | `GUILTYSPARK_NOTIFY_EMAIL_FROM` | 用于 PR 开启邮件且经过验证的 Resend 发件人地址。 | | `GUILTYSPARK_NOTIFY_EMAIL_TO` | PR 开启邮件的收件人地址。 | | `GUILTYSPARK_MODEL` | 传递给 `codex exec --model` 的模型。未设置则使用 Codex CLI 默认值。 | | `GUILTYSPARK_CODEX_PATH` | Codex CLI 二进制文件路径。默认为 `codex`。 | | `GUILTYSPARK_CODEX_WORKDIR` | Codex 可以检查的本地仓库/配置检出路径。 | | `GUILTYSPARK_CODEX_TIMEOUT_SECONDS` | 单次 Codex 调用的超时时间。默认为 `600`。 | | `GUILTYSPARK_PR_MODE` | `off`、`plan` 或 `branch`。脚手架默认为 `off`。 | | `GUILTYSPARK_TARGETS_PATH` | 可选的 TOML 文件,用于将 Loki 查询映射到 GitHub 仓库。仅在首次运行时播种目标存储;此后以数据库为准。 | | `GUILTYSPARK_TARGETS_JSON` | JSON 目标列表,用于 Portainer stack 配置。仅在首次运行时播种目标存储;之后可从仪表板编辑目标。 | | `GUILTYSPARK_REMEDIATION_ROOT` | 用于存放短暂存在的隔离克隆的父目录。 | | `GUILTYSPARK_GITHUB_TOKEN_ENV` | 包含 GitHub token 的环境变量名。 | | `GUILTYSPARK_GITHUB_API_URL` | GitHub API base URL,用于 GitHub Enterprise。默认为 `https://api.github.com`。 | | `GUILTYSPARK_DASHBOARD_HOST` | `guiltyspark dashboard` 的绑定地址。默认为 `0.0.0.0`。 | | `GUILTYSPARK_DASHBOARD_PORT` | Web 仪表板端口。默认为 `8343`。 | | `GUILTYSPARK_SITE_IMAGE` | 营销网站镜像。默认为 `ghcr.io/bheus/guiltyspark-site:latest`。 | | `GUILTYSPARK_SITE_PORT` | nginx 营销网站容器的主机端口。默认为 `8080`。 | | `GUILTYSPARK_DASHBOARD_GROUPING` | 启用后,仪表板会要求 Codex 将相关且未分配的异常聚类为一个单一的语义组,以便操作员可以一次性屏蔽整类异常,并提议一个**屏蔽模式**(一种服务作用域内的 regex),用于抑制当前*及未来*的变体。模式在生效前始终由操作员审查 —— UI 会显示该提议及其实时影响范围;绝不会自动应用。当出现新的异常类时会产生一次 Codex 调用开销(聚类基于未分配的指纹集进行缓存;仅数量变化会复用缓存),每次模式提议也会产生一次调用。需要 `codex` 二进制文件。如果发生任何 Codex 错误,则回退到平面列表。默认为 `false`。 | | `GUILTYSPARK_DASHBOARD_FILTER_LABEL` | 仪表板的容器选择器过滤所依据的 Loki label。选择器会列出当前窗口该 label 的值,并在服务端缩小异常审查范围,从而使 `LOKI_LIMIT` 预算仅用于选定的容器。默认为 `container`。 | | `GUILTYSPARK_DEDUP_ISSUES` | 启用时(默认),修复操作将基于*逻辑问题*而不是确切的指纹进行去重:Codex 将近乎重复的异常聚类为一个持久化的问题,每个问题每次运行仅修复一个代表性异常,并且如果某个问题的 PR 仍处于开启状态 —— 或者其上一个 PR 在冷却期内被合并/关闭 —— 则不会被重新提交。只有在出现真正的新指纹时才会产生 Codex 调用开销(已知指纹会直接从存储中进行短路处理);发生任何 Codex 错误时将回退到基于指纹的去重。需要 `codex` 二进制文件。默认为 `true`。 | | `GUILTYSPARK_ISSUE_COOLDOWN_SECONDS` | 某个问题的上一个 PR(已合并或已关闭)之后,多长时间内抑制对其重新提交。无论此值如何,处于开启状态的 PR 始终会被抑制。默认为 `604800`(7 天)。 | | `GUILTYSPARK_ISSUE_ACTIVE_WINDOW_SECONDS` | 在将新异常与现有问题匹配时,向后追溯已知问题的时长(用于界定聚类 prompt 的范围)。默认为 `1209600`(14 天)。 | | `GUILTYSPARK_EXPECTED_LOGS_CACHE_SECONDS` | 每个目标获取的 `expected_logs_path` 文档在重新从 GitHub 获取前的缓存时间。默认为 `300`(5 分钟)。 | | `GITHUB_APP_ID` | GitHub App ID。优先于个人 token 认证。 | | `GITHUB_APP_INSTALLATION_ID` | 包含目标仓库的账户的 Installation ID。 | | `GITHUB_APP_PRIVATE_KEY` | 作为字面量或 `\n` 转义 PEM 格式的 App 私钥。 | | `GITHUB_APP_PRIVATE_KEY_FILE` | 挂载的 App 私钥 PEM 的替代路径。 | ## 仓库目标 Fleet 模式使用一个包含一个或多个 Loki 到仓库映射的 TOML 文件。从 [`targets.example.toml`](targets.example.toml) 开始: ``` [[targets]] id = "inventory-service" loki_url = "http://loki:3100" loki_query = '''{container=~"inventory-(api|worker)"}''' github_repo = "example-org/inventory-service" base_branch = "main" mode = "observe" test_commands = ["pytest -q"] allowed_paths = ["src", "tests"] max_changed_files = 8 expected_logs_path = "docs/EXPECTED_LOGS.md" ``` `expected_logs_path`(可选)是相对于仓库的路径,指向一个文档,该文档列出了服务发出的 *预期*或良性的日志行 —— 故意的警告、启动 chatter、重试通知。Monitor 会从目标仓库(包括私有 仓库,使用相同的 GitHub 认证)获取该文件,并将其作为上下文传递给 Codex,从而避免将这些 模式误判为异常。如果文件丢失或无法访问,将被直接 忽略 —— 分析将在没有此额外上下文的情况下继续进行。获取结果会按 `GUILTYSPARK_EXPECTED_LOGS_CACHE_SECONDS` 进行缓存。 目标模式是逐步递进的: - `observe`:仅检测和诊断。 - `fix`:克隆、编辑、强制执行策略并进行验证;绝不推送。 - `draft-pr`:执行相同的检查,然后推送一个 GuiltySpark 分支并开启一个 draft PR。 - `pr`:执行相同的检查,然后推送一个 GuiltySpark 分支并开启一个可进行审查的 PR。 生产环境可以通过 `GUILTYSPARK_TARGETS_JSON` 以 JSON 列表的形式提供相同的结构;这是首选的 Portainer 配置 方式,并且优先于本地的 TOML 文件。 目标存储在 SQLite 状态数据库中,并且可以在仪表板中进行编辑 (见下文)。`GUILTYSPARK_TARGETS_JSON` / `GUILTYSPARK_TARGETS_PATH` 仅在首次运行且 存储中不包含任何目标时**播种**该 存储;此后,数据库具有绝对权威,并且仪表板上的编辑将在重启后持久化保存。稍后更改环境变量不会 产生任何影响,除非存储为空,并且从 仪表板删除已播种的目标也不会因重启而恢复。daemon 会在 每个轮询周期开始时从存储中重新读取目标,因此编辑会在一个间隔内生效,而无需 重启。 对于私有仓库和 PR 模式 模式,推荐使用仅安装在配置的仓库上的 GitHub App。设置 `GITHUB_APP_ID`、`GITHUB_APP_INSTALLATION_ID` 以及 `GITHUB_APP_PRIVATE_KEY` 或 `GITHUB_APP_PRIVATE_KEY_FILE`。GuiltySpark 会生成并缓存短期的 installation token,用于控制器拥有的 Git 和 GitHub 请求;Codex 不会 接收 App 凭证或 installation token。当没有配置 App 变量时,通过 `GUILTYSPARK_GITHUB_TOKEN_ENV` 提供的 token 仍可作为后备使用。不完整的 App 配置被视为错误。 ## 重放捕获的事件 重放固定数据(fixtures)使用与持久化修复 队列相同的通用事件和发现模式。要进行本地重放,请将目标示例复制到一个被忽略的配置文件中,将 `local_repo` 指向 相关的检出路径,并使用 `fix` 模式: ``` GUILTYSPARK_TARGETS_PATH=targets.local.toml guiltyspark replay \ tests/fixtures/example-upstream-outage.json \ --target inventory-service \ --patch-output data/example.patch ``` 除非提供 `--allow-push` 参数,否则重放会将 `draft-pr` 和 `pr` 降级为 `fix`。这使得 相同的捕获事件既可以用于本地补丁评估,也可以用于明确授权的 draftPR 练习。 ## 部署 Compose 堆栈还通过运行在 `http://localhost:8080` 的 nginx sidecar 提供静态营销网站 。设置 `GUILTYSPARK_SITE_PORT` 以在不同的 主机端口上发布它。仪表板依然在单独的端口 `8343` 上可用。主分支 的推送会在触发 Portainer 重新部署 webhook 之前,将 `site/` 发布为 `ghcr.io/bheus/guiltyspark-site:latest`,因此该堆栈不依赖于仓库的 bind mount。 向 `main` 分支推送约定式提交(conventional commits)。GitHub Actions 会对项目进行测试,创建一个语义化 发布,构建原生的 `linux/arm64` 镜像,将带有版本号和 `latest` 标签的镜像发布到 GHCR, 然后触发 GuiltySpark Portainer 堆栈 webhook,以便堆栈重新拉取 `latest`。 对于一次性的 Portainer 设置,请从此仓库的 `main` 分支创建堆栈, 在 Portainer 中启用堆栈 webhook,并将其生成的 URL 保存为 GitHub 仓库的 secret `PORTAINER_GUILTYSPARK_WEBHOOK`。发布工作流仅在新 镜像被推送之后才会调用该 webhook。如果缺少该 secret,则跳过 webhook 步骤, 而 Portainer 的常规 Git 轮询仍作为后备方案。 在部署时通过环境变量提供目标映射和凭证。 该仓库特意不包含特定于应用程序的目标配置。将每个 目标初始设置为 `observe` 模式,在审查诊断质量后将其升级为 `fix` 模式,并 仅在其验证命令和允许的路径确立后,才使用 `draft-pr` 或 `pr` 模式。 ## 命令 ``` guiltyspark once # poll Loki once and analyze current incidents guiltyspark daemon # run forever guiltyspark doctor # validate configuration and connectivity basics guiltyspark dashboard # serve the web dashboard (default port 8343) ``` ## 仪表板 `guiltyspark dashboard` 提供了一个带有 Monitor 语音风格的 Web 控制台(默认地址为 `http://localhost:8343`)。在 Docker 中,它作为 `guiltyspark-dashboard` compose 服务运行,该服务共享 daemon 的 `/data` 卷并发布 8343 端口。它展示 已编目的发现结果、修复历史,以及实时的 Loki 错误严重级别事件视图。每个实时事件都会被分类到 其流选择器匹配对应标签的目标中;任何与配置 目标不匹配的内容都将作为**未分配的异常**显示,因此 containment 协议之外的 错误仍然可见。容器选择器(由 `GUILTYSPARK_DASHBOARD_FILTER_LABEL` 支持)在服务端缩小了审查范围,因此 `LOKI_LIMIT` 预算仅会用于你选定的容器。 仪表板也是配置的控制平面: - **Containment 协议** —— 添加、修改或停用目标。编辑内容将使用与配置文件相同的规则进行验证, 写入状态存储,并由 daemon 在其下一次循环中提取应用。 - **屏蔽噪音** —— 你认为属于噪音的未分配异常可以被屏蔽;它将 从流中剔除(以 incident 指纹为键)并列入 **已屏蔽的异常**列表中。屏蔽操作会捕获异常的服务、级别、一行样例 以及事件计数,使得该条目在离开数据流后依然保持清晰易读,并且每个条目 都带有一个可编辑的分流(triage)注释供你参考。任何条目都可以恢复。 - **屏蔽模式** —— 一种持久化、服务作用域内的 regex 规则,用于抑制噪音类别的当前 *及未来*变体,而不是一次只处理一个指纹。在启用 `GUILTYSPARK_DASHBOARD_GROUPING` 的情况下,Codex 会提议该模式,并且 UI 会显示 其实时影响范围;在你提交之前不会应用任何内容。规则包含可编辑的 标题和注释,无效的 regex 将被跳过,而不是导致异常视图崩溃。 在启用 `GUILTYSPARK_DASHBOARD_GROUPING` 的情况下,聚类运行在**请求路径之外**: 一个 worker 在后台计算组,当计算 正在进行时,API 会返回 `groups_pending`,并且 UI 会在扁平的回退列表上显示编目状态,每隔 2s 轮询一次,直到结果返回。 该页面是一个 React + TypeScript 应用(位于 `frontend/`,参见上文的开发说明),它 仅与 JSON API 通信,因此后端和客户端可以独立演进。它每 60s 轮询一次 并进行原地协调 —— 打开的事件卡片和正在进行的编辑 在刷新后依然保留。读取端点:`/api/overview`、`/api/findings`、 `/api/remediations`、`/api/anomalies?minutes=N[&containers=…]`、 `/api/containers?minutes=N`、`/api/targets`、`/api/anomalies/ignored`(被屏蔽的 指纹和模式规则)。写入端点:`POST`/`DELETE /api/targets`、 `POST`/`DELETE /api/anomalies/ignore`、`POST /api/anomalies/ignore-batch`(屏蔽一 整个组)、`POST /api/anomalies/suggest-pattern`(向 Codex 请求一个模式 提议)、`POST`/`DELETE /api/anomalies/rules`、`POST /api/anomalies/rules/metadata` (编辑规则的标题/注释)以及 `POST /api/anomalies/note`(编辑被屏蔽异常的 triage 注释)。 仪表板是**未经过身份验证的**,并且它现在可以修改配置并触发 目标更改。请将其保留在受信任的局域网(LAN)中,不要将其暴露在公共互联网上。 ## 认证说明 监控器使用 Codex CLI 而不是 `OPENAI_API_KEY`。在容器内使用你的 ChatGPT 账号登录一次: ``` docker compose run --rm guiltyspark codex login --device-auth ``` 登录信息存储在 `CODEX_HOME` 下,并由 Compose 管理的 `guiltyspark-data` 卷提供支持。
标签:AIOps, Docker, Loki, 安全防御评估, 模块化设计, 自动化修复, 请求拦截, 运维监控, 逆向工具