opsx
|
fbbd3a3c27
|
refactor(ai): 砍掉 /aiops 整页,模型配置迁入系统设置,浮窗助手保留
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
评估结论落地:整页分析只喂聚合统计(模型看不到日志原文),结论价值低于 /analysis
图表自身;浮窗助手携带真实查询样本,价值成立。
- 删 /aiops 页面与路由(菜单去项);llmProviders 迁 lib 共用
- 模型配置重做为设置页「AI 模型接入」区:状态严谨映射——供应商/模型按已存
baseUrl/model 正确回显(自定义供应商显示 BaseURL 与自定义模型输入,不再重置),
API Key 掩码占位、留空或掩码保存均沿用旧值(不回传掩码)
- 后端删 /ai/analyze 与信号采集链路;/ai/chat 保留(上下文由前端日志样本注入)
|
2026-11-01 00:46:38 +08:00 |
|
opsx
|
c216bdfdd4
|
feat(ai): 助手浮窗聚焦日志查询页,携带当前查询结果对话
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 浮球只在 /logs 渲染(产品定位:辅助分析正在查看的查询结果,其它页面不打扰)
- /logs 查询成功后写入 AI 上下文:LogQL、时间范围、结果条数、error 行数、
最新 20 条样本(去 CRI 包装);对话请求自动携带
- 后端 /ai/chat 接收 context:截断 6000 字 + RedactTextOnly 统一脱敏后并入 system
(ECHO 假模型实测注入完整);空态文案改为查询导向
|
2026-11-01 00:31:48 +08:00 |
|
opsx
|
2efbed882a
|
fix(aiops): 保存配置后分析页状态即时同步;分析页布局重排
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- enabled 状态在 saveConfig 成功后立即 setEnabled(此前只更新 config,
导致'AI 分析未启用'横幅要等下次请求才消失)
- 布局:工具栏改 4 列网格(数据源/时间/应用/问题),执行按钮与提示移到
分隔线下右对齐;未启用横幅加 ✦ 图标 + 「去配置」直达按钮;空态/加载态
加图标居中;消耗行改统计卡风格(数值 tabular 高亮)
|
2026-11-01 00:26:22 +08:00 |
|
opsx
|
5d898e3592
|
fix(ai): 测试连接改为结构化结果,不再倾倒模型原始回复
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
返回 {model, latencyMs, tokensUsed, replyLength};前端提示「连接正常:模型 · 耗时 · tokens」。
模型原始回复不回传——测试要的是通没通/多快,不是看模型说了什么(ECHO 假模型下尤其明显)。
空回复时明确报错提示检查 baseURL 是否 OpenAI 兼容。
|
2026-11-01 00:21:59 +08:00 |
|
opsx
|
f457d6da09
|
feat(ai): 全站 AI 助手浮窗(参考 FlowStack 右下角浮空设计)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- POST /ai/chat:多轮对话(messages 透传)+ 页面上下文注入 —— 前端带当前路径,
后端自动附加该数据源近 15 分钟轻量日志摘要(总量/Top3 应用/告警命中),
追问「这个报错什么原因」有数据依据;脱敏/每日配额/Pro 闸门与整页分析共用
- AiAssistant 浮球+浮窗挂 Layout:仅 admin+Pro 渲染;消息气泡、Enter 发送、
上下文随路由自动采集;免费版不显示
|
2026-10-31 23:46:33 +08:00 |
|
opsx
|
b81b31f322
|
feat(aiops): 模型配置默认 DeepSeek/flash + 模型名真下拉
首次进入配置页自动预填 DeepSeek 端点与 deepseek-flash(只需填 Key);模型名从
datalist 换 shadcn Select(随供应商联动,末项「自定义模型…」转手输)
|
2026-10-31 23:41:43 +08:00 |
|
opsx
|
07aa41bc33
|
fix(aiops): 菜单移至日志告警下系统设置上;模型配置精简
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 菜单顺序:日志查询 → 日志分析 → 日志告警 → 智能运维 → 系统设置
- 模型配置去掉快捷预设按钮组;Base URL 仅选「自定义」供应商时显示;
温度/输出上限/超时/每日token上限/额外脱敏收进「高级设置」折叠(默认收起),
常规配置只剩:供应商下拉 + 模型名 + API Key + 测试/保存
|
2026-10-31 23:36:16 +08:00 |
|
opsx
|
9641503007
|
feat(license): Pro 授权机制 — 离线许可码 + 免费版功能闸门
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
免费版:日志查询/部署管理/采集/用户管理/LDAP/设置;Pro(¥199 个人单实例永久 / ¥499 团队多实例永久):
日志分析、日志告警、智能运维(AI)、审计日志。
- 后端:HMAC-SHA256 离线许可码(签发 CLI / license --plan --edition --seats --customer --days;
永久=days 0),GET /system/license + POST activate;RequirePro 闸门挂 metrics/ai/audit;
过期自动回落免费;测试覆盖签发-校验/篡改/免费拒签
- 前端:菜单 Pro 项带锁标;四个 Pro 页面免费版渲染升级引导卡(功能清单+定价+页内激活);
设置页新增授权区(版本/席位/到期/激活入口);license 词典中英
- domains 注册提升到 main.tsx 全局(修复 license 命名空间在未引 domains 的页面显示裸键)
|
2026-10-31 23:08:06 +08:00 |
|
opsx
|
c60b827262
|
fix(settings): 定时切换主题去掉时区下拉与说明文字,固定用浏览器本地时区
|
2026-10-31 22:52:58 +08:00 |
|
opsx
|
c090ba0564
|
feat(aiops): 长窗口(>1h)分析必须指定应用
全命名空间 6h/24h 聚合数据量过大会拖垮 Loki:前端时间范围旁新增应用范围选择器
(labelValues 候选),>1h 未选应用禁用开始分析并提示;后端同样强制 400(闸门在服务端);
信号采集选择器按 app 收窄后 histogram/stats 从全量变为单应用流,秒级返回。
|
2026-10-31 22:51:55 +08:00 |
|
opsx
|
1610988c2e
|
feat(aiops): 模型配置参考 aiops/opsxshell — 快捷预设 + 供应商下拉
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 快捷预设:deepseek-flash(推荐)/ deepseek-v4-pro / 自定义 一键填入端点与模型
- 供应商目录(9 家 + 自定义):DeepSeek/通义/智谱/Kimi/MiniMax/硅基流动/混元/OpenAI/本地
Ollama·vLLM,选择后自动填 Base URL 与首个推荐模型
- 模型输入支持 datalist 下拉(随供应商联动)且可手输任意模型名;Base URL 保持可编辑
|
2026-10-31 15:23:26 +08:00 |
|
opsx
|
7be41f0a6b
|
feat(aiops): 新增「智能运维」——AI 日志分析(P0,不扫日志内容)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 为什么这样设计(成本优先)===
全集群 370GB/天,把日志原文丢给模型是不可能的(一次几百万 token)。
所以整条链路只做一件事:**把 GB 级日志压成 KB 级信号**。
P0 的信号来源全部是索引/快照,**零日志内容扫描**:
体积 + 趋势 → index/volume_range(实测 24h 仅 54ms)
条数/流数 → index/stats,且只在 ≤2 小时窗口用
(全天 index/stats 实测 20s,绝不能进 AI 路径)
应用排行 → 平台小时级预聚合快照(毫秒)
基线对比 → 上一个等长窗口的 volume_range
告警 → 平台投递记录
实测一次分析:3~6 秒、输入 600~1400 tokens(约 ¥0.005/次)。
=== 后端 ===
models/ai.go AIConfig(APIKey 加密存储)+ AIAnalysisRun(审计)
services/ai_config.go 读写配置、掩码返回、每日 token 用量统计
services/ai_provider.go OpenAI 兼容客户端 + 连通性测试
(DeepSeek/通义/内网 vLLM/Ollama 通用)
services/ai_redact.go 脱敏:凭据/邮箱/手机号/身份证 → 占位符,
**保留内网 IP 与端口**(排查必需)
services/ai_signal.go 信号采集 + 渲染成紧凑文本(省 token)
services/ai_analyze.go 结构化输出解析、配额闸门、审计落库
controllers/ai.go /ai/config /ai/config/test /ai/analyze /ai/runs
成本闸门(三层):
1) 单次:时间范围 ≤24h、maxTokens ≤1500
2) 每用户每日 token 上限(默认 50 万,可配,超限直接拒绝并说明)
3) 审计只留最近 500 条,避免表膨胀
=== 前端 ===
新页面 /aiops(侧栏「智能运维」,管理员),页内单层 tab:
日志分析 / 模型配置 / 分析历史
- 分析:数据源 + 时间预设(15m/1h/6h/24h) + 可选提问 → 结论/证据/建议/不确定 四区
顶部显示本次消耗(in/out tokens、耗时),可查看「发给模型的完整内容」
- 配置:启用开关、Base URL、模型、API Key(掩码)、温度、输出上限、超时、
每日配额、额外脱敏正则;按钮顺序改为「测试连接 → 保存配置」
- 历史:服务端分页(20/页),点行看详情与 payload
=== 两个真实踩坑(已修)===
1) 密码含 @ 导致脱敏漏掉(mysql://user:P@ssw0rd@host)
→ URL basic-auth 规则改为贪婪匹配到最后一个 @,由单测锁定
2) 结论里的时间是 UTC(容器无 tzdata,time.Local=UTC),
模型写"凌晨 6 点下跌"而运维当时是下午 2 点
→ 显式用东八区展示(LOKISTACK_DISPLAY_TZ=UTC 可覆盖)
=== 实测(69147008fcf4)===
配置保存 → key 掩码 sk-***e286;连通性测试返回「正常」
分析 15 分钟窗口:4.1s,结论识别出"窗口末段体积断崖约 -60%"
信号时间 14:23~14:38(本地时间正确)
历史分页 total=3,tokens/耗时字段完整
单测:脱敏 2 个用例 + 模型输出解析 4 个用例 + 6 条 AI 路由断言
|
2026-10-31 06:39:01 +00:00 |
|
opsx
|
3861b8b128
|
fix(collectors): 编辑采集策略时无法把"全集群"改成"只采几个命名空间"
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 现象 ===
编辑 Promtail DaemonSet 采集器,显示"所有命名空间(全集群采集)",
下面每个命名空间的复选框全是灰的,点不动,无法改成只采指定几个。
=== 根因 ===
CollectorEditDialog 里单个命名空间的复选框写死了:
disabled={allSelected} // allSelected = selectedNs.length === 0
而 targetNamespaces 为空正是代表"全集群",也就是**一进来就处于全集群状态**,
于是所有子项被禁用;而"全集群"那个复选框的 handler 又是
onCheckedChange={() => setAll()} // 永远设回 []
两者叠加 → 没有任何操作能离开"全集群"状态,是死胡同。
=== 修复 ===
1. 去掉 disabled:勾任何一个命名空间都会让 selectedNs 非空,
allSelected 自动变 false,"全集群"勾选框随之取消——语义自洽。
2. "全集群"行加一句提示:勾选下方任一命名空间即可改为只采指定范围,
让用户知道下一步该点哪里。
=== 实测(51489599e2d8)===
PUT targetNamespaces=["astralx"] → 读回 ['astralx'] ✅
PUT targetNamespaces=[] → 读回 [](全集群) ✅
恢复 ['astralx','log'] → 读回 ['astralx','log'] ✅
tsc 通过;Pod = Hub latest = 51489599e2d8
注:改完需要点「部署」才会把新的 kubernetes_sd_configs.namespaces 白名单
下发到 Promtail DaemonSet。
|
2026-10-31 02:00:33 +00:00 |
|
opsx
|
9448868a71
|
feat(collectors): Promtail DaemonSet 的部署命名空间固定为 log,表单隐藏该字段
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 需求 ===
创建 Loki 部署后添加采集策略(Promtail DaemonSet)时,"部署节点命名空间"
不该让用户选 —— 采集器必须和 LokiStack 放在一起(log)。
=== 前端 ===
CollectorFormDialog:选择 k8s-daemonset 时整块隐藏 namespace 字段
(连同分类型提示、手动输入开关一起),改为一条只读说明:
「采集器部署命名空间固定为 Loki 所在命名空间(通常 log),与 LokiStack
放在一起,无需选择。要限制采集范围请用下面的『只采集这些命名空间』。」
提交时 DaemonSet 不传 namespace(原来会传用户填的值)。
顺带修掉隐藏后暴露的 tsc 报错:placeholder 里 `collectorType === 'k8s-daemonset'`
在收窄后的分支里已成不可能比较,改为常量 'default'。
=== 后端(强制约束,不只靠前端)===
services/collector.go:
Create:type == k8s-daemonset 时强制清空 req.Namespace
→ 渲染与部署两侧都会回退到 d.loki.Namespace
Update:DaemonSet 忽略 updates["namespace"]
原始渲染逻辑本就有"为空则用 LokiDeployment.Namespace,再兜底 log"。
为什么后端也要拦:API 直接调用(脚本/其他客户端)不该能把采集器
部署到任意命名空间——那样会出现"平台显示在 log、实际 DaemonSet 在别处"
的状态漂移。
=== 实测(0108617e4180)===
现有采集器 promtail-pre: type=k8s-daemonset namespace='log'
PUT {"namespace":"evil-ns"} → 返回 success,但读回仍是 'log' ✅ 被忽略
tsc 通过;Pod = Hub latest = 0108617e4180
|
2026-10-31 01:56:15 +00:00 |
|
opsx
|
f1fc7b4a9a
|
fix(analysis): 排行分页后名次从 1 重新开始,应为 11/21…
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
现象:翻到第 2 页,名次列仍是 1~10。
根因:TopAppsTable 用页内下标算名次(index + 1),分页后每页都从 1 开始。
修复:新增 startIndex 参数,由调用方传入当前页在整体排行中的偏移;
分析页传 (page - 1) * 10,于是第 2 页为 11~20、第 3 页 21~30。
实测:tsc 通过;Pod = Hub latest = 9026d0e4c582
|
2026-10-30 17:50:54 +00:00 |
|
opsx
|
d4422f1541
|
refactor(analysis): 去掉排行 10/30/50 选择器,改为一次取 50 + 前端分页;零点结算昨天
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 1. 为什么去掉「显示条数」选择器 ===
topk 的 N 不影响查询成本(扫描的 chunk 集合完全相同),实测:
topk(10) 17833ms / 11 条
topk(30) 18019ms / 34 条
topk(50) 7394ms / 54 条
topk(100) 17553ms / 74 条
波动来自集群负载,与 N 无关 —— 它只决定最后返回多少序列。
所以"选 30"既不更快也不更慢,本质是个翻页开关;
而选它还要重新等一次十几秒的查询,纯亏。
改为:一次 topk(50),前端 10 条/页 → 5 页只需 1 次查询。
附带收益:占比的分母从"前 10 合计"变成"前 50 合计",更接近真实占比。
副标题明确写清"一次取前 50 个应用(占比按这 50 个合计计算)"。
=== 2. 零点结算昨天(daily volume)===
原来只有 30 分钟 ticker 回补,且启动时间不对齐整点 →
昨天最早的一段可能已经超出 Loki 24h 保留期被删,
结算结果系统性偏低,图上却标"完整一天",属静默错数。
新增 nextMidnightFinalize():每天 00:01 触发 FinalizeYesterdayVolume()
- 昨天定稿 partial=false
- 补一次条数(index/stats,一天一次可接受)
- 顺带刷新今天 partial=true 的柱子
ticker 仍保留,负责当天数据的 30 分钟刷新。
=== 3. 顺带 ===
- 去掉每日量"其中 N 天平台没有留存记录"的提示文案(按用户要求)
- 修 QuickQueryTemplates.tsx 的未使用变量导致 tsc -b 失败
- 工作区里上一轮中断的"排行体积列"改动一并纳入(app-ranking 返回 bytes,
TopAppsTable 增加体积列,快照记录 per-app 字节数)
Pod = Hub latest = a16978d2f88b
|
2026-10-30 17:13:18 +00:00 |
|
opsx
|
e4cefa1fa7
|
feat(analysis): 应用排行支持 10/30/50 切换 + 前端分页
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 可行性评估(先实测再实现)===
topk 的 N 是否影响查询成本?5 分钟窗口实测:
topk(10) 17833ms / 11 条
topk(30) 18019ms / 34 条
topk(50) 7394ms / 54 条
topk(100) 17553ms / 74 条
波动来自集群负载,与 N 无关 —— 扫描的 chunk 集合完全相同,
topk 只决定最后返回多少序列。所以放宽到 50 不增加查询成本。
=== 分页为什么不每页发一次查询 ===
排行是"排序后的聚合结果":要拿第 11~20 名,必须先算出全部应用的量再排序,
不存在"只算 11~20 名"的做法。所以正确设计是:
topk(N) 一次取全 → 前端按 10/页切片
N=50 时 5 页,只查 1 次;每页点一次查询反而会重复扫同一批 chunk,纯浪费。
=== 实现 ===
- hook 参数 rankLimit(10/30/50),两条查询都包 topk(N)
- 分析页:显示条数按钮组,点击即触发查询(同时重置到第 1 页)
- 分页条:共 N 个应用 · 第 x/y 页 · 上一页/下一页
- 长范围走预聚合时,limit 也跟随 N(接口上限 100)
- 切换筛选(刷新)时重置到第 1 页
Pod = Hub latest = 4d4e1a7ec334
|
2026-10-30 16:51:21 +00:00 |
|
opsx
|
7b1ffd0274
|
fix(daily-volume): 30/90/365 天切换"没反应";/logs/stats 加短缓存
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 1. 切换范围没反应(真 bug)===
现象:7/30/90/365 天切换后图完全一样。
根因:GetDailyVolumeHistory 只返回**已抓到的快照**。平台只有 4 天数据,
所以任何 days 都只返回 4 个点 → 图表一模一样。
修复:始终返回 days 个点,缺失的天用 Missing=true 标记。
实测:days=7→7 点(缺3)、30→30 点(缺26)、90→90(缺86)、365→365(缺361)。
为什么必须区分 Missing 和"0 字节":
前者是"平台没留存",后者是"那天确实没日志"。
混在一起会让 30/90 天的图上出现大片 0,被误读成"日志量暴跌/系统停摆"。
前端:缺失的天画灰色浅柱,tooltip 显示"未留存",图下方提示
"其中 N 天平台没有留存记录(灰色柱),不是当天没有日志"。
=== 2. /logs/stats 加 90 秒缓存 ===
分析页每次进入/切范围都打 index/stats,实测 5 分钟窗口冷查询 1.2 秒
(60 分钟更慢)。这类聚合值可容忍分钟级陈旧,加与指标查询一致的缓存:
90s TTL + 时间窗按 2 分钟取整 + 上限 200 条 + 过期清理
实测:重复请求 1227ms → 8ms。
=== 3. 顺带确认已生效的优化 ===
histogram(24h) : 54ms(volume_range)
daily-volume : 10ms(只读库)
stats 二次请求 : 8ms(新缓存)
长范围(>1h) : 不跑 count_over_time(预聚合快照 + 跳过内容扫描)
Pod = Hub latest = 12453ea723c1
|
2026-10-30 16:46:40 +00:00 |
|
opsx
|
5c7a3ec81d
|
perf(daily-volume): 每日量改用 volume_range(20s → 54ms),图表改圆柱
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 核心技巧:体积用 index/volume_range,不要用 index/stats ===
同为 24 小时、同一 selector,两条索引查询差 370 倍:
index/stats : 整天聚合,实测 20s 直接超时(之前每日量接口卡 20s 的根因)
index/volume_range: 按 step 分桶只累加 chunk 字节,实测 54ms,97 个桶
原因:index/stats 要跨全部 chunk 去重统计 streams/entries;
volume_range 只把索引里记录的 chunk 字节数按时间桶求和,不解码内容。
=== 后端改动 ===
services/daily_volume.go:
体积:LokiHistogram(走 volume_range,step 由跨度自动选)→ 累加 54ms
条数:只能 index/stats,降级为「尽力而为」25s 超时;
拿不到就保留上一次的值(不能因为一次超时把历史条数清零),
绝不让它拖住体积入库。
前端图只画体积,条数缺失不影响可读性。
=== 前端改动 ===
DailyVolumeChart:AreaChart(面积)→ BarChart(圆柱)
顶部圆角 radius=[6,6,0,0]、渐变填充、maxBarSize=44
当天未结束的柱子用半透明 + 虚线描边区分,避免把"才过半天"误读成"量掉了"
=== 实测 ===
24h volume_range: 54ms,97 桶,合计 369.61 GB(与全集群 ~370GB/天 吻合)
/logs/daily-volume 接口: 10ms(读库)
Pod = Hub latest = 4cdc8b9c4841
|
2026-10-30 16:40:49 +00:00 |
|
opsx
|
d0adc095dd
|
feat(analysis): 长范围应用排行改走「小时级预聚合」,不再扫日志内容
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 问题(实测)===
按应用排行必须读日志内容(Loki 3.4 的 index/volume 不支持 app 维度聚合,
aggregateBy=app 无效),成本 ∝ 时间范围:
5 分钟 ≈ 1.3GB → 9.8s
15 分钟 ≈ 3.9GB → 19.5s
1 小时 ≈ 15GB → 预计 60~90s
6 小时 ≈ 92GB → 5~8 分钟
24 小时 ≈ 370GB → 不可用
用户在时间范围里选 6h/24h 时,页面必然卡死。
=== 方案 ===
新增 models.AppVolumeSnapshot + services/app_volume.go:
- 后台任务每小时第 5 分钟抓「上一个完整小时」的 per-app 条数
- 用**瞬时查询**(/loki/api/v1/query + [1h])而不是 query_range:
单点评估、扫描量相同、省掉多步开销;并绕开 LokiMetrics 的 90s 上限
与重型查询信号量(后台任务不该和前台抢令牌)
- 每源每小时只留 top 50 应用,保留 90 天
- 幂等:同 (hour, source, ns, app) 更新而非重复插入
- 超出 Loki 26h 保留期的窗口跳过(抓也抓不到,白等几分钟)
新增 GET /api/v1/logs/app-ranking?sourceId&from&to&limit → 读库毫秒级
前端:analysis 判断范围 > 60 分钟就走预聚合接口,并停掉实时重查询
(rankingEnabled 置 false);表格副标题说明「读的是预聚合快照,已留存 N 小时,
不含错误数与趋势」。
=== 顺带修掉的根因 bug ===
useInView 原来用 useRef + useEffect([inView]):分析页首屏 data===null 时
内容块不渲染,ref.current 为 null,effect 直接 return;等数据到了 DOM 才挂上,
但依赖没变 → effect 不重跑 → IntersectionObserver 从未创建 → inView 永远 false
→ 重查询永不发出 → 页面一直显示"没有应用维度的数据"。
改成 callback ref(节点挂载即作为依赖),并加 scroll/resize 兜底 + 挂载瞬间量一次。
另外分析页加 8 秒无条件兜底:可见性判定再失灵,8 秒后也一定查一次。
默认时间范围 15 分钟 → 5 分钟(19.5s → 9.8s)。
=== 实测 ===
Pod = Hub latest = 98de87e7f895
5 分钟窗口 by-app:9.8s(75 序列);15 分钟:19.5s
|
2026-10-30 16:35:35 +00:00 |
|
opsx
|
7f6fb364de
|
refactor(analysis): 移除重复的「应用日志量流图」与「Top 5 饼图」,并按需触发重查询
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 为什么这两个可以去掉(不是拍脑袋)===
排行表已有 9 列:排名 / 应用 / 命名空间 / 日志量 / **占比** / 错误数 /
错误率 / 趋势(sparkline) / 操作(查看日志)。
- Top 5 饼图 = 应用 + 数量 + 占比 —— 表里全都有,且表里更多
- 流图 = 各应用随时间的堆叠 —— 表里每行已有 sparkline 趋势
两者都是同一份 by-app 数据的重复呈现,纯增加视觉噪音。
=== 但"去掉它们"本身不减查询,真正的减负在这一步 ===
之前这三个组件都依赖同一条 by-app 重查询(10~15 秒)。
因为饼图/流图在首屏,所以那条查询只能一进页面就全量跑(rankingEnabled=true)。
现在图都没了,重查询只服务底部排行表 → 恢复可见性门控:
rankingEnabled: rankingView.inView
效果:打开分析页只跑索引类查询(histogram/stats/daily-volume,均 <100ms),
用户滚到排行卡才发那条重查询。
「活跃应用」卡片在未统计时显示 —(原来显示 0 个,容易误读成"没有应用")。
=== 布局 ===
趋势图从 2/3 宽改为整行,页面高度明显下降。
=== 实测(ddf63053b952)===
Pod = Hub latest = v0.1 = ddf63053b952
NamespacePie / AppStreamChart 在页面中引用数均为 0
|
2026-10-30 16:15:44 +00:00 |
|
opsx
|
3b297d0429
|
feat(collectors): 采集项支持「表单 / YAML」双模式编辑(方案 A)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 设计边界(刻意不做"编辑完整 promtail.yaml")===
开放的是**采集项本身**的 YAML 表示(字段与 models.ScrapeJob 一一对应):
name / paths / labels / encoding / json / system
multiline(firstline,maxLines) / timestamp / dropIfMatch / rateLimit
clients、positions、server、scrape_configs 外层仍由平台生成。
理由(真实故障面):
用户删掉 positions → 重启后从头重采,大量重复 + Loki 以
entry too far behind 拒绝整批;删掉 clients → 完全不外发。
这两类问题在页面上看不出来,只能登机器看日志,排障成本极高。
只开放采集规则,写坏了最多某条日志没采到,不会让采集器整体失效。
=== 后端 ===
新增 services/collector_jobs_yaml.go:
RenderJobsYAML(jobs) → 带边界说明注释的 YAML(明确写"clients/positions 不在此修改")
ParseJobsYAML(text) → 复用 models.ValidateScrapeJobs,与表单完全同一套校验
错误信息必须具体:
YAML 语法错 → 「YAML 语法错误:yaml: line 1: did not find expected ',' or ']'」
语义错 → 「采集项「app」至少需要填写一个日志路径」
新增路由(用 /collector-jobs/* 前缀,避开与 /collectors/:id 的 gin 通配冲突):
POST /api/v1/collector-jobs/render
POST /api/v1/collector-jobs/parse
=== 前端 ===
编辑弹框(主机模式)加「表单 / YAML」切换:
- 切到 YAML:调 render 把当前采集项渲染出来
- 切回表单:调 parse 回填;解析失败**留在 YAML 模式**并显示原因
(绝不能悄悄丢掉用户改动)
- 停留在 YAML 模式点保存:先 parse,失败则中止保存
- 编辑区 min-h 固定、等宽字体、可拖拽高度
=== 测试 ===
TestRenderParseJobsYAMLRoundTrip:
表单→YAML→表单 无损往返,逐字段核对
(multiline.maxLines / rateLimit / dropIfMatch / timestamp / labels / system)
TestParseJobsYAMLErrors:空内容、语法错、空列表、缺名称、缺路径、名称非法
→ 6 种输入都给出具体错误,不是笼统"格式错误"
=== 实测(c11d2badce64)===
render: code=0,17 行,含边界说明
parse : code=0,1 个采集项;往返校验 name=app paths=1 multiline=True
rateLimit={rate:2000,burst:4000} drop=1 json=True
错误: 「采集项「app」至少需要填写一个日志路径」
「YAML 语法错误:yaml: line 1: did not find expected ',' or ']'」
Pod = Hub latest = c11d2badce64
|
2026-10-30 16:10:51 +00:00 |
|
opsx
|
c8d026adc7
|
feat(collectors): DaemonSet 不再填日志路径,改为「只采集哪些命名空间」多选
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
用户指出的三个问题,逐条核实后的结论:
1) 「采集日志路径」对 DaemonSet 确实是多余的 —— 确认后端模板里写死了
`replacement: /var/log/pods/*$1/*.log`(按 pod UID 精确匹配容器标准输出),
表单里填的 logPath 对 DaemonSet 完全不参与渲染。
→ 选择 DaemonSet 时隐藏该字段,提交时强制置空。
2) 「可以指定采集多个命名空间」—— 后端**早就支持**:
models.Collector.TargetNamespaces + promtailNamespaceFilter(),
空数组 = 全集群。只是**前端新建弹框没暴露**(编辑弹框里有)。
→ 新建弹框补上多选 chips(全集群 / 各命名空间可点选),并补齐前后端透传:
dto.CreateCollectorRequest.TargetNamespaces
controller → services.CreateCollectorRequest.TargetNamespaces
models.Collector.TargetNamespaces
之前前端即使传了也会被 Go 静默丢弃(无该字段)。
3) 「命名空间」字段语义已在上一提交补了分类型说明:
- DaemonSet:采集器自身部署在哪个命名空间(不影响采什么)
- Sidecar:目标应用 Deployment 所在命名空间
新增 TestDaemonsetTargetNamespacesFilter:
nil → 不生成 namespaces 白名单(全集群)
["astralx"] → 生成且包含 astralx
["astralx","default"] → 两个都在
=== 实测(d45dfa18ecc5)===
前端文案「只采集这些命名空间」「不选 = 采集整个集群…」均已进包
渲染测试 3 种输入全部符合预期
注:本次 docker push latest 遇到 Docker Hub 502,v0.1 成功而 latest 仍指向旧镜像,
导致首次滚动后 Pod 仍是旧代码;重推 latest + 强制重建 Pod 后确认
Pod digest = d45dfa18ecc5 —— 记录这个坑:push 失败必须核对三方 digest。
|
2026-10-30 16:03:24 +00:00 |
|
opsx
|
87909ed403
|
docs(collectors): 采集策略弹框「命名空间」补分类型说明
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
用户困惑:这个字段到底是"只采集指定命名空间"还是"组件要部署的命名空间"?
查代码后确认——**同一个字段在三种采集类型下含义完全不同**:
k8s-fluentbit-sidecar:
= 目标应用 Deployment 所在的命名空间。
平台会 Deployments(ns).Get(target) 然后 patch 注入 fluent-bit sidecar,
同时把它作为日志的 namespace 标签。
**不是**日志过滤条件。
k8s-daemonset:
= Promtail DaemonSet 部署到哪个命名空间。
DaemonSet 挂载 /var/log/pods,采集**整个集群所有命名空间**的容器日志,
不按命名空间做过滤。一般就填 Loki 所在的 log。
host-promtail:不使用该字段。
原来这三种情况共用一句"命名空间",用户只能猜。
现在字段下方按类型显示对应说明(min-h-4 固定高度,不抖动)。
实测:三条提示文案均已进包(6fe430f59eef)。
|
2026-10-30 15:53:08 +00:00 |
|
opsx
|
a14bdb5e07
|
fix(analysis): 每日量不再懒加载;按应用统计期间显示 loading(不再假装"没有数据")
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
用户的三个现象,对应三个独立缺陷:
=== 1. 每日日志量"一直没数据" ===
接口只读平台库,实测 **10ms**,却仍被 inView 懒加载门控。
ref 虽然挂在 DOM 上,但滚动判定在多层布局里不可靠 → enabled 一直 false
→ 请求根本不发 → 页面显示"暂无历史数据"。
改为:只要有数据源就加载(enabled = Boolean(sourceId))。
这个接口 10ms,没有任何理由懒加载。
=== 2. 排行/流图"打开没有数据,等很久也没 loading" ===
按应用统计要 10~15 秒(读日志内容,物理下限)。
但 loading 是**整页共用**的:索引类指标 100ms 就回来、
重查询还在跑时 loading 就已经变 false,
卡片于是渲染成"没有应用维度的数据"——用户以为查完了、没数据。
改为:
- 新增 appsLoading,专门标记"按应用统计"这段重查询
- 卡片在 appsLoading 期间显示 spinner + "正在按应用统计…"
- loading 与 appsLoading 都在 finally 清理(成功/失败/取消都清)
=== 3. data 为 null 时整页空白 ===
原来 `) : !data || !sourceId ? null : (` 直接 return null,
首屏或重查询异常时是**整页空白**,和"没数据"无法区分。
改为显示 spinner + "正在按应用统计(需读取日志内容,通常 10~30 秒)"。
=== 关于"用 Go 定时任务统计每日总量"===
这个机制**早就存在**(services/daily_volume.go):
- 启动 20s 后回补 + 每 30 分钟一次 ticker
- 用 index/stats 取整天的条数与字节,**不读日志内容**,通常秒级
- 快照写入平台库,保留 400 天
实测当前库内已有 4 天数据(10ms 返回):
2026-10-27 32.5 GB
2026-10-28 23.9 GB
2026-10-29 133.6 GB
2026-10-30 239.3 GB
问题只在"前端没去请求",不在采集侧。
实测(df3b477e6740):每日量接口 10ms 返回 4 天,Pod 1/1 Running。
|
2026-10-30 15:49:52 +00:00 |
|
opsx
|
ad0d35f72a
|
feat(alerts,logs): A 方案—告警窗口 15m→5m 消重复扫描;数据源可编辑/删除且删后不被自动重建
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
=== 1. 告警窗口 15m → 5m(阈值同比例 1/3)===
背景:检查间隔已改 5 分钟,但规则窗口仍是 15 分钟 ——
同一个 15 分钟窗口每 5 分钟被完整重扫一遍,等于 3 倍重复扫描。
在 370GB/天、全命名空间 selector 下,这是 Loki CPU 的最大稳态来源。
FATAL 10 → 4 窗口 5m
Go panic 10 → 4 窗口 5m
JVM OOM 5 → 2 窗口 5m
StackOverflow 5 → 2 窗口 5m
应用错误激增 100000 → 35000 窗口 5m(改名 error > 3.5万)
ingress 5xx 2000 → 700 窗口 5m
ingress 4xx 20000 → 7000 窗口 5m
DB 连接失败 1000 → 350 窗口 5m
阈值按 1/3 缩放是必须的:窗口缩短 3 倍后,同样的流量在 5 分钟内只有
1/3 的条数,阈值不动就永远打不到了。
迁移 v10→v11 只改"窗口仍是 15 且名字匹配内置规则"的项,用户手调的不动。
=== 2. 数据源支持编辑/删除(含托管源)===
原来前后端都把托管源锁死:
- 前端 `!managed &&` 直接不渲染编辑/删除按钮
- 后端 DeleteSource 返回「平台部署的数据源随部署管理,请先删除对应部署」
用户的诉求是"数据源必须能编辑和删除",且确实有场景(不想让某个源出现在
查询下拉里、想改认证头)。
=== 3. 修「删了又回来」的真 bug ===
EnsureLogSourcesForRunningDeployments() 在 **每次打开数据源列表和启动时**
都会给 running 部署补齐数据源 —— 所以删掉托管源后,刷新页面它又回来了,
用户看到的现象就是"删不掉"。
新增 LokiDeployment.AutoSourceDisabled:
- 删除托管源时置位(DisableAutoSourceFor)
- EnsureLogSourceForDeployment 见到该开关直接 return
- 部署被编辑/重新部署时重置(EnableAutoSourceFor),即"你要用它了就恢复"
前端配套文案:
- 编辑托管源弹窗提示"重新部署会覆盖集群/Service/端口,认证与请求头保留"
- 删除确认明确"删除后不会再自动重建,重新部署该 Loki 才恢复"
=== 实测(20300eef92d2)===
v11 迁移后 8 条规则全部 窗口=5m,阈值 4/4/2/2/35000/700/7000/350
go test ./... 全绿
|
2026-10-30 15:28:30 +00:00 |
|
logan
|
b433b5f9dd
|
fix: 删除操作清理远端资源并拦截悬空引用
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 部署删除: k8s 模式清理 StatefulSet/Service/ConfigMap 与 PVC,先卸载关联采集器
- 采集器删除: 同步卸载成功后再删记录,失败保留避免孤儿
- 集群/存储/主机删除: 有部署或采集器引用时拒绝,避免悬空外键
- Token RBAC 补 services/proxy,保证 cluster-svc 数据源可查询
|
2026-10-30 01:17:26 +08:00 |
|
opsx
|
e579eabe48
|
fix(wizard): 存储步骤去掉推荐文案;空态列表补腾讯云对象存储(COS 后端早已支持)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 16:20:34 +08:00 |
|
opsx
|
c1f822fb10
|
refactor: 移除资源用量的可选 Prometheus 接入与平台监控页
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- 资源用量弹窗:删除 applyPrometheus/promQueryValue 与峰值/限流字段
(LOKI_USAGE_PROMETHEUS_URL 从未内置,代码恒走降级),统一提示
「未接入 Prometheus:暂无峰值/限流数据」;扩容建议改基于 metrics-server 实时值
- 移除「平台监控」菜单页与 /api/v1/system/metrics-summary;
原始指标保留在标准 /api/v1/metrics(Prometheus 文本格式)可自行抓取
- 手册/落地页/CHANGELOG 同步(去除备份恢复与监控页宣传,指标端点表述中性化)
|
2026-10-29 15:16:18 +08:00 |
|
opsx
|
f87fb4b2b3
|
refactor: 移除备份恢复功能(API/CLI/手册)
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
日志数据的保留与安全由对象存储 + Loki retention 承担,平台侧整库备份恢复价值有限:
删除 services/backup.go、/system/{backup,restore,restore/apply,backups} 路由与 handler、
CLI backup/restore 子命令、前端 api 方法与类型;自检所需的 dataDirPath/密钥指纹并入 selfcheck;
用户手册删备份章节并修正 FAQ 升级建议;CHANGELOG 记录移除。
|
2026-10-29 14:48:25 +08:00 |
|
opsx
|
2bd77b57b4
|
feat(monitor): 平台自监控页(/monitor,管理员)
- 后端 /api/v1/system/metrics-summary(admin):从 Prometheus registry 聚合
HTTP 请求/状态码/Top 路由、DB 落盘与大小、告警评估、Loki 查询、runtime 快照
- 前端统计卡 + Top 路由表 + 分布明细(状态码/告警/Loki/落盘),中英文案齐全
- 菜单位于「日志分析」与「日志告警」之间
|
2026-10-29 14:04:25 +08:00 |
|
opsx
|
b8d08ef6d1
|
fix(alerts): 内置规则排除 Loki 自身日志,消除自我污染误报
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
Loki 会把执行过的查询语句记成自身 info 日志,查询文本包含规则关键字时
告警会'命中自己'(如 StackOverflowError 规则匹配到告警引擎自己的查询记录),
造成持续误报——全命名空间类内置规则一律加 app!="loki";存量规则启动时自动升级;
顺带清理旧版遗留的重复规则「应用错误过多(error > 1000)」(与现模板重复且阈值会误报);
前端新建/模板默认查询同步;红线测试锁死
|
2026-10-29 13:46:46 +08:00 |
|
opsx
|
abe28964a1
|
fix(i18n): 全站文案书面化 + 中英翻译质量修复
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
中文(zh):约 40 处口语化改书面(「谁在什么时候改了什么」→「操作人、时间与变更前后对比」、
「找出最吵的业务」→「定位日志量最高的业务」、「点」→「点击」、创建你的第一个→创建首个等);
「副本 / Pod」统一为「Pod」。
英文(en)修复:
- 功能 bug:wizard openGrafana ({url}) 插值失效→({{url}});settings notify 空态链接词悬空破句
- 语义补齐:clusters/hosts 删除确认补 Warning 与后果句;tokenInsecureHint 补 Kubeconfig 建议
(并删 en 死键 tokenRecommended);retention liveHint/costWarning/upgradeHint 补丢失半句
- 术语统一:data source / collection policy / log lines / Listen IP (internal) / Total deployments
- 复数:users confirmDesc、pagination total、range lastMinutes 补 _one;storage deleteWarning 两形态对齐
- 风格:Select filtered users、Delivery History、Log Scrape Jobs、App label source、
Deployment Logs 连字符改间隔号;删除 zh 死键 liveTitle
|
2026-10-29 13:42:51 +08:00 |
|
opsx
|
df1075f1f5
|
fix(layout): 顶栏用户区去掉盾牌图标与管理员徽章,只留用户名
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
|
2026-10-29 13:31:05 +08:00 |
|
opsx
|
a69142fbfe
|
refactor(frontend): Live Tail 重构 + alerts 拆分 + NotifySection 去重
- Live Tail:输入 500ms 防抖后才切换轮询查询;轮询 AbortController 取消 + 版本号丢弃乱序响应
- alerts/index 508→286 行:表单与模板拆出 RuleFormDialog(浏览器实测模板/通道/提交可用)
- NotifySection 两份逐字节副本合一为共享组件;settings 词典注册进 domains(alerts 直访不再裸键)
|
2026-10-29 13:29:25 +08:00 |
|
opsx
|
231fa8e774
|
fix(settings): 日志保留时间移出顶部并紧凑化
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
通用配置卡内顺序改为 主题 → 语言 → 日志保留时间;保留时间从嵌套整卡
压缩为单行分区(标签 + 两个数字输入 + 内联保存),与主题/语言分区同族,
删去 desc 与两条 hint 冗余文案(中英同步)。
|
2026-10-29 13:06:31 +08:00 |
|
opsx
|
4f74fe6c3f
|
ci(engineering): Gitea Actions 门禁 + 前端测试设施 + 版本注入修复
CI / Backend (go vet + test) (push) Canceled after 0s
CI / Frontend (lint + build + test) (push) Canceled after 0s
- .gitea/workflows/ci.yml:后端 gofmt/vet/test + 前端 lint/test/build 双 job 门禁
- vitest + @testing-library + jsdom 落地,14 个命脉用例:useListQuery 乱序丢弃/卸载中止、
http.ts 401 单飞刷新+队列重放+失败登出+silent 直拒、审计防抖、StepDeploy 部署目标、query-utils
- 版本注入断裂修复:main 补 buildTime/gitCommit 符号,build.sh/Dockerfile 注入 main.version,
/health 返回真实版本,前端 package.json 版本对齐 2.0.0
- gofmt 清零(8 个存量未格式化文件)
|
2026-10-29 13:02:19 +08:00 |
|
opsx
|
2975452c21
|
fix(security,data): P0 — 命令注入/停止毁数据/采集路径绕过/DB 清库/前端竞态
后端(实机 10.10.10.161 验证 host-binary 全链路 + 注入双路径 400):
- Loki 版本号严格 ^\d+.\d+.\d+$ 校验并接入 Update 路径:version 拼进 SSH shell 命令,
旧校验放行 3.0.0'; id; echo ' 等注入载荷(root RCE)
- Stop/Restart 清理清单移除 PVC:停止部署曾连带删除 loki-data PVC,filesystem 存储日志被永久清空(测试锁死红线)
- 采集敏感路径防护覆盖默认 LogPath 分支与 collector Create(/etc/shadow 不再可绕过入库)
- 数据文件损坏时原文件改名 .corrupt-* 留档再空库启动,不再被静默覆盖(loadFromDisk 抽出可测)
前端:
- 审计页用户名过滤 300ms 防抖 + AbortController/版本号(逐键竞态数据错乱)
- 向导自动部署改用创建返回实例(或 id 最大且 pending),不再盲取 list[0] 对错误实例发起真实部署
- 路由级 ErrorBoundary:单页崩溃/chunk 404 只影响当前页
|
2026-10-29 13:02:11 +08:00 |
|
opsx
|
5e59347c40
|
feat(settings,analysis): 日志保留时间可配(发送历史保留 7 天)+ 分析页手动刷新;修复我引入的 RWMutex 死锁
=== 0. 【严重】修复上一版引入的死锁(平台写路径全卡死)===
上一版把保留期读取写成 `GetRetentionSettings()`(内部 db.RLock()),
却在 `db.Lock()` 已持有的情况下调用(recordDelivery / RecordAudit 的裁剪路径)。
Go 的 sync.RWMutex **不可重入** → 第一次写投递历史/审计日志就永久死锁。
现象:登录 28 秒返回空(登录也要写审计)、所有写接口挂住。
go build / go vet / 之前的单测(不触发写路径)全都查不出来。
修法:
- 拆出 retentionFromDBLocked(db) / alertDeliveryRetentionLocked(db) /
auditLogRetentionLocked(db),调用方已持锁时用无锁版本
- upsertSettingLocked 里**不能**用 db.NextID()(内部 d.mu.Lock(),同样死锁),
改为锁内自算 max(id)+1
- 新增 retention_test.go:持写锁调用裁剪函数,2 秒超时未返回即判死锁
实测修复后:登录 103ms(原 28000ms)、panic/deadlock 命中 0。
=== 1. 平台数据保留期可配(系统设置 → 通用配置 → 日志保留时间)===
新增 GET/PUT /api/v1/settings/retention:
alertDeliveryDays 告警发送历史保留天数,默认 7(原写死"最近 50 条")
auditLogDays 审计日志保留天数,默认 90(原写死常量)
保存后立即按新策略裁剪一次,不必等下次触发。
校验:发送历史 1~365 天、审计 1~3650 天,超出返回明确中文原因(不是 500)。
注意与「Loki 日志保留期」区分:那个是部署级配置(组件部署 → 保留策略)。
=== 2. 发送历史:50 条硬上限 → 保留 7 天 ===
后端 Deliveries 接口原来写死 `len(out) < 50`,现在按保留期取(响应上限 500 可调),
标题改为「发送历史(保留 N 天)」,N 从设置里读。
=== 3. 菜单「日志服务」→「组件部署」===
=== 4. 分析页:去掉「全部命名空间」+ 改手动刷新 ===
- 移除「全部命名空间」选项:{namespace=~".+"} 要扫全集群(约 370GB/天),
一次分析几十秒,得不偿失;历史值 __all__ 也会被忽略并回退到策略/第一个 ns
- 筛选改为「选择值 / 已生效值」分离:切换命名空间或时间范围**不再自动查询**,
只把刷新按钮点亮并提示「筛选条件已改,点刷新生效」;
点一次才应用并取数。
原因:一次分析要跑多条 count_over_time,切一下下拉就重查一轮,
快速连点会把 Loki 打满(并发护栏 429),还拖慢别人。
=== 实测(9739c7c092e7)===
登录 103ms / token 273 字符
GET retention → {alertDeliveryDays:7, auditLogDays:90}
PUT 0 天 → 400「告警发送历史保留天数需在 1~365 之间」
平台日志 panic/deadlock 命中 0
go test ./... 全绿(含 2 个新死锁回归用例)
|
2026-10-29 04:16:52 +00:00 |
|
opsx
|
7369a17598
|
feat(alerts): 数据库连接告警覆盖 MySQL/PG/Redis 主流报错;规则表单三字段对齐
=== 1. 「数据库连接失败」匹配模式 3 → 25 个 ===
原来只匹配 connection refused / reset by peer / too many connections,
漏掉了 PG 的 FATAL: too many clients、Redis 的 NOAUTH / WRONGPASS /
connection pool timeout 等实际最常见的报错。
按数据库分类补齐:
通用网络层
connection refused / connection reset by peer / broken pipe /
no route to host / cannot assign requested address /
connection timed out / i/o timeout / unexpected EOF /
server closed the connection unexpectedly
MySQL
too many connections / communications link failure /
driver: bad connection / could not create connection /
Access denied for user
PostgreSQL
FATAL: password authentication failed / sorry, too many clients /
terminating connection
Redis
NOAUTH / WRONGPASS / READONLY you can't write against /
CLUSTERDOWN / max number of clients reached
连接池(Java/Go 应用层最先炸的地方)
connection pool / connection is not available
**刻意不加**的(会匹配到几乎所有网络日志,等于没告警):
裸 "EOF"、"timeout"、"error"、"refused"、"deadlock"
阈值保持 1000/15min(模式变多不代表命中变多,都是真错误才命中)。
迁移 v9→v10 只替换"仍是旧默认查询"的规则,用户自定义过的不动。
=== 2. 规则表单三个字段没对齐 ===
现象:统计窗口(分) / 触发阈值(条) / 冷却(分) 三格并排,
但只有中间的「触发阈值」下方多了一行提示文字,
导致中间那格更高、提示文字悬在底部,看起来像没对齐。
修法:三格统一为「标签 + 输入框 + 一行提示(min-h-4 固定高度)」,
底边自然齐平;提示文案改为:
统计窗口 → 统计最近多少分钟
触发阈值 → 超过该条数才告警
冷却 → 冷却内不重复发送
(顺带把阈值提示从"0 = 出现即告警"改掉:默认阈值已无 0,0 反而是危险配置)
小屏改单列堆叠(grid-cols-1 sm:grid-cols-3)。
=== 实测(936a127b1d15)===
v10 迁移:告警规则 "数据库连接失败" 扩展匹配模式
线上规则:25 个模式,含 NOAUTH / too many clients,阈值 1000,冷却 240m
Pod = latest = v0.1 = 936a127b1d15
|
2026-10-29 03:56:07 +00:00 |
|
opsx
|
cb1ef1dff4
|
fix(alerts,analysis): 告警引擎稳定性 5 项修复;每日量接口去掉同步 Loki 查询(20s→10ms)
=== 一、告警引擎稳定性审查(引擎本身是异步的:ticker + goroutine)===
1) 【高】ticker 无重叠保护
ticker 每 1 分钟触发;一轮规则检查 8 条 × 20~30s = 2~4 分钟,远超一分钟。
原来每过一分钟就再开一轮 → 查询越堆越多,抢走页面查询的 Loki 资源,
每轮越来越慢,最终表现为"告警不响了"。
→ 新增 alertCheckRunning(atomic.Bool),上一轮未完成直接跳过本轮并记日志。
2) 【高】单条规则无超时
RunAlertCheck(context.Background()) 没有 deadline:Loki 一旦挂住,整轮永久运行,
叠加问题 1 会让引擎再也不启动新一轮(静默死亡)。
→ 每轮总超时 5 分钟(alertRoundTimeout)+ 单条规则 90s(alertRuleTimeout)。
3) 【中】panic 只在整个 tick 层 recover
一条规则 panic 会让本轮后续规则全部跳过。
→ per-rule recover,panic 记为一条 failed 投递记录,后续规则继续。
4) 【中】冷却记录每分钟写一条,把真实发送记录挤光
AlertDeliveries 只保留最近 50 条,而处于冷却期时每个 tick 都写一条 cooldown,
一小时内真实的"已发送"记录就全被挤掉了,发送历史没法看。
→ recordCooldownThrottled():同一规则 10 分钟最多写一条。附单测。
5) 规则串行本身保留(避免同时压垮 Loki),但配合 1) 才安全。
=== 二、每日日志量接口:20 秒 → 10 毫秒 ===
根因:GetDailyVolumeHistory 里**同步** await 一次"整天 index/stats"补抓当天数据,
那次查询超时 20s → 整个接口 20s → 前端超时 → 页面显示"每日日志量没数据";
更糟的是这条重查询还和页面的 histogram/stats 抢 Loki,导致那两个接口同时 502
(访问日志实证:daily-volume latency_ms 20000.997,stats/histogram 502)。
→ 接口改为**只读库**,不触发任何 Loki 查询;当天数据由后台每 30 分钟的
定时任务补(CaptureRecentDailyVolumes)。
→ 同时把整天 index/stats 的超时从 20s 提到 90s(370GB/天,20s 不够)。
实测:10ms。
=== 三、通知通道改名「默认告警」(schemaVersion v8→v9) ===
"lark 默认告警" / "默认通知通道(待配置 Webhook)" → 统一为「默认告警」。
只改名,不动 Webhook、不动 enabled:
首次部署创建的占位通道必须保持「空 Webhook + 禁用」,由管理员自己填,
否则会出现"已启用但空地址"的通道,告警一命中就全部投递失败。
=== 四、其它文案 ===
「每日日志量」副标题精简为「每天的日志总量,平台保留历史趋势」,
并移除「平台留存历史」徽标(用户反馈多余)。
=== 实测(303134c0c718)===
/logs/daily-volume:10ms(原 20000ms)
通道列表:id=1 name="默认告警" enabled=true hasWebhook=true
go test ./... 全绿(含新增 TestRecordCooldownThrottled)
|
2026-10-29 03:38:30 +00:00 |
|
opsx
|
607b67a2ed
|
fix(alerts,analysis,dashboard): 阈值再上调 + 告警风暴全局闸门;修 subtitle/门控/饼图 Top5
=== 1. 告警阈值第二轮上调(500~5000 这一档仍然太敏感)===
实测业务高峰期这些量级就是常态,阈值贴着基线等于"每天准点报警":
应用错误激增 50000 → 100000(冷却 120m → 240m)
ingress 5xx 500 → 2000 (冷却 240m)
ingress 4xx 5000 → 20000 (冷却 240m)
数据库连接失败 200 → 1000 (冷却 240m)
迁移 v7→v8,只改"仍等于旧默认值"的规则,用户手动调过的不动。
=== 2. 告警风暴抑制:新增全局闸门 ===
单条规则的 cooldown 只能防"同一条重复",挡不住"多条规则同时炸"。
实际踩过:一次存储抖动让 8 条内置规则同时命中,10 秒内群里刷了 8 条,
之后大家开始无视这个群 —— 告警疲劳比漏报更难挽回。
新增 overStormLimit():最近 1 小时内全平台成功发送数 ≥ 12 条时,
后续命中不再发送,只写一条状态为 cooldown 的发送记录并注明
"告警风暴抑制:最近 1 小时已发送 N 条(上限 12)",可见可追溯。
=== 3. 分析页 subtitle 仍是裸 key ===
中文语言包缺顶层 subtitle(英文包有),i18next 找不到就把 key 原样输出。
补齐:「按应用与命名空间看日志量、错误与趋势,找出最吵的业务」。
=== 4. 分析页三条"没有应用维度的数据"的真正原因 ===
「活跃应用」「应用日志量 Top 5」「应用日志量流图」都依赖 by-app 重查询,
而这条查询被**底部排行卡片的可见性**门控(rankingEnabled: rankingView.inView)——
用户不滚到底就永远不发请求,于是顶部三块永远是空的。
改为进页面就发(两阶段渲染:索引类指标先出,app 维度到货后补)。
懒加载只保留在真正位于底部的「每日日志量」「Ingress 分析」。
=== 5. 饼图改 Top 5 + 文案 ===
用户要求:应用日志量 Top 5 用饼图。TOP_SLICES 8 → 5,
标题改「应用日志量 Top 5」,副标题写明"其余归入其他"。
=== 6. 卡片顺序 ===
「每日日志量」从中间移到「应用排行」之后(更靠底部,慢慢加载)。
=== 7. 仪表盘 ===
整个「平台自检」卡片(含所有指标行)按用户要求移除。
=== 实测(2e8c88817b6a)===
v8 迁移:4 条规则阈值上调,日志齐全
当前阈值:FATAL>10 / panic>10 / OOM>5 / StackOverflow>5 /
应用错误>100000 / ingress 5xx>2000 / 4xx>20000 / DB 连接>1000
冷却:低频规则 120m,高频规则 240m
|
2026-10-28 23:43:14 +00:00 |
|
opsx
|
fbefcc86df
|
feat(logs,analysis): 分页改"加载更多"+触底自动加载;修懒加载不触发;指标短缓存
=== 1. 日志列表:去掉页码分页,改「加载更多」+ 触底自动加载(节流)===
为什么不用"上一页/下一页/共 N 页":
- Loki 是**游标分页**,不支持 offset,页码天生跳不了页
- 规模上没意义:15 分钟 4000 万条 → 按 200/页算 40 万页
- 带 |= / |~ 行过滤时总数根本算不出来(index/stats 只接受 label matcher)
日志排查的真实交互是"继续往下看",所以改为:
- 列表底部:「已加载 N 条 · 约 M 条」+「加载更多」按钮
- 滚动到底**自动加载**,但 2 秒节流 —— 每批都要后端扫 Loki,
快速滚动连续触发会撞并发护栏(429),反而比手动点更卡
- 到 5000 条停止并提示"缩小时间范围或加过滤条件"(虚拟滚动只省 DOM,
不省数组内存,无上限会一直涨)
- 修正语义 bug:分页原来是 setEntries(result.entries) **整页替换**,
改成追加(否则"加载更多"等于翻页)
- 删除 pageCursors 翻页栈与 prevPage/nextPage/pageOf/pageOfTotal 文案
=== 2. /logs/stats 错误映射:Loki 400 → 我们的 400 + 人话 ===
之前带行过滤时 Loki 回 400 "only label matchers are supported",
被统一包装成 502「Kubernetes API 代理返回 HTTP 400…」,
前端只能当失败静默 → 用户看到"总页数没了"却不知道原因。
现在返回 400 +「含过滤条件(|= / |~)时无法统计总页数:Loki 只支持按标签统计」,
前端据此在条数旁给出提示。
=== 3. 分析页:修"延迟加载永远不触发"(数据一直不出来)===
现象:应用排行/流图/饼图/活跃应用全是"没有应用维度的数据",每日量"已有 0 天",
但接口实测正常(by-app 返回 75 条序列、daily-volume 返回 2 天)。
根因:这三块用 useInView 门控,而 ref 挂在 <Card> 上,观察器不触发 → 永远 false
→ hook 直接 return → 页面永远空。
修法:
- 改用普通 <div ref> 包裹,不依赖组件是否转发 ref
- useInView 加**几何兜底**:1 秒后若元素已在视口内就直接置 true
(有些容器里 IntersectionObserver 的 root 判定会和预期不一致)
=== 4. 分析页指标短缓存(26s → 11ms)===
`sum by (app)(count_over_time(...))` 扫 15 分钟 astralx(约 6.6GB)实测 16~28 秒,
Loki 自带结果缓存不命中(时间窗每次都在变)。点刷新/切页都要再等半分钟。
现在按 (数据源, 查询, 时间窗) 缓存 90 秒,时间窗按 2 分钟取整当 key:
实测第 1 次 23514ms → 第 2 次 **11ms**
代价:命中缓存时数据最多旧 ~2 分钟,对聚合视图可接受。
=== 5. 仪表盘 ===
去掉「平台自检」「N 项需关注」两段文字:卡片本身就是自检结果,
标题重复一遍没信息量;状态改由图标+边框颜色表达。
=== 6. 其它文案修正 ===
饼图空态从「没有命名空间维度的数据」改为「没有应用维度的数据」
(饼图上一版已改成应用维度,文案没跟上)。
=== 实测(1da8a148d756)===
Pod = Hub latest = v0.1 = 1da8a148d756
stats:纯 selector → 200(7005 万条);带 |= error → 400 + 人话
分析指标缓存:23514ms → 11ms
每日量:2 天(10-27 32.5GB / 10-28 356.5GB)
文案:加载更多 / 已到末尾 / 达到上限 均进包;代码中已无 prevPage/nextPage 引用
|
2026-10-28 23:36:32 +00:00 |
|
opsx
|
6d6e17745c
|
fix(logs): 恢复 POST /logs/histogram(直方图 404)+ 系统设置改为侧栏五分页
=== 1. 修复 /analysis 与 /logs 直方图 404(我上一轮 patch 的误伤)===
现象:分析页直接报 "API endpoint not found";日志查询页直方图不显示。
根因:37e2f0f 里一次模糊匹配的 patch 把
logs.POST("/histogram", logsCtrl.Histogram)
改成了
logs.GET("/histogram", logsCtrl.Histogram)
前端 logsApi.histogram 一直用 POST(body 带 sourceId/query/start/end),
于是 POST /logs/histogram 全部 404。访问日志实证:
POST /api/v1/logs/histogram → 404 ×7
这类"方法写错"编译期查不出来,go build / tsc 都是绿的。
修法:同时注册 POST(前端用)与 GET(curl 调试用)。
新增 backend/routers/router_test.go 守门:
TestCriticalRoutes 断言 19 条关键路由的 method+path 组合都存在
TestHistogramAcceptsPost 单独断言 histogram 必须同时有 POST 与 GET
以后再把 POST 写成 GET,测试会直接红。
=== 2. 系统设置放回侧栏,改成 5 个顶层 tab ===
按使用反馈调整:
系统设置(侧栏一个入口)
├ 通用配置 /settings ← tab 起点
├ 用户管理 /users
├ 审计日志 /audit
├ LDAP 接入 /settings/ldap ← 新拆分
└ 默认镜像源 /settings/registry ← 新拆分
为什么拆:原来 /settings 页面内部已有 4 个 tab(通用/告警通知/LDAP/镜像源),
如果侧栏再加一层 tab 就是"两层 tab",点两下才知道自己在哪。现在扁平化成单层。
配套改动:
- 新增 pages/settings/ldap.tsx、registry.tsx(各自只渲染一块内容)
- settings 主页去掉内层 Tabs,只留通用配置(外观/语言/改密码/关于)
- 上一版把设置放到了右上角齿轮,现在移除齿轮入口
- 菜单文案:settings 页标题 系统设置 → 通用配置;新增 LDAP 接入 / 默认镜像源
- 删除设置页副标题「通用配置、告警通知、LDAP 接入与默认镜像源」(用户要求去掉)
- 删除 settings 里已不存在的 tabs.notify 文案
(注意 settings.notify.* 这些 key 仍被告警页的 NotifySection 用着,保留)
=== 3. 告警通知已不在系统设置里 ===
它在上一轮就搬到了「日志告警 → 通知通道」tab,
本次顺带把系统设置里遗留的 tab 文案一并清掉,避免出现"看似有两处配置入口"。
=== 实测 ===
POST /api/v1/logs/histogram → 200(修复前 404)
go test ./... 全绿(含新增的 routers 包)
tsc -b --force 通过
|
2026-10-28 23:12:42 +00:00 |
|
opsx
|
37e2f0febc
|
feat(analysis,alerts): 每日量落库看历史趋势、趋势图/饼图可读性、告警三分页
=== 1. 每日日志量必须落库(核心问题:Loki 只留 24 小时)===
实测:请求"近 7 天每日量",Loki 只返回 27 个**小时**桶,全部在最近 24h 内 ——
因为 retention_period=24h,更早的 chunk 已经被删了。
也就是说「固定近 7 天」这张卡片,过去一直是"6 天空白 + 1 天有数据",
用户看着像坏了,实际是数据物理不存在。
方案:平台自己留存历史。
- models.DailyVolumeSnapshot{Date(本地YYYY-MM-DD), SourceID, Bytes, Entries, Partial}
- services/daily_volume.go
CaptureDailyVolume 按天抓 index/stats 落库(幂等覆盖)
StartDailyVolumeCapture 启动 20s 后回补 + 每 30 分钟检查一次
GetDailyVolumeHistory 读库返回最近 N 天(今天会实时补抓)
- 保留 400 天(够看年度趋势),按 source+date 去重
- 前端可选 7 / 30 / 90 / 365 天,能看多长取决于平台积累多久
为什么用"每 30 分钟检查昨天有没有落库"而不是固定 00:10 定时:
平台可能整晚没运行,固定时点的定时器会直接错过;检查式回补不依赖平台在线时刻。
Partial 标记(关键,否则会误读):
- 当天未结束 → partial=true
- 待抓取日期的起点已超出 24h 保留期 → partial=true(Loki 里就没那么多数据)
实测:10-27 32.5GB/2.65亿条 partial=true(保留期外),
10-28 348GB/28.56亿条 partial=true(当天进行中)
=== 2. 修一个 Go 副本 bug(自己写的,靠实测发现)===
第一版用 `for _, snap := range db.DailyVolumeSnapshots` 后改 snap 字段,
range 拿到的是**副本**,改动不会写回切片 → 同一天永远保持首次抓取的值,
partial 也永不更新(实测发现 10-27 该是 true 却显示 false)。
改为 `for i := range ... { snap := &db.DailyVolumeSnapshots[i] }`。
=== 3. 趋势图可读性 ===
从"裸折线"换成"渐变面积图",并在图上方给出峰值 / 均值:
- 面积图能一眼看出"这一段整体比上一段高",折线只能靠对齐格子数
- 峰值/均值避免用户自己对刻度
- Y 轴与 tooltip 统一 formatBytes,"每桶体积"写进 subtitle
顺带确认:趋势图只画体积(字节),不再混入 error(条数)——单位不同不能同轴。
=== 4. 饼图:命名空间分布 → 应用日志量分布 ===
本环境的命名空间只有 astralx / log 两个,饼图没有信息量;
改成按应用看谁在刷日志(前 8 名 + 其他),才是运维真正要的答案。
=== 5. 告警页拆成三个 Tab ===
「告警规则 / 通知通道 / 发送历史」独立成分页(用 ui/tabs)。
之前通道是折叠块、历史堆在规则下面,规则一多就要滚很久才看得到。
发送历史增加空状态文案,不再是"空页面不知道有没有在发"。
=== 6. 内置规则关联默认通道 + 首次部署 webhook 留空的评估 ===
结论:**webhook 可以留空,但通道必须是禁用状态**。
- 若种子给的是"已启用 + 空地址"通道,规则一命中就必然投递失败,
发送历史堆满红色失败,反而让人以为平台坏了
- 所以首次部署建一个**禁用**的占位通道「默认通知通道(待配置 Webhook)」,
内置规则全部挂到它;管理员填好 Webhook 再启用即可,不用逐条关联
- 保存时加校验:启用则必须有非空 Webhook(返回明确原因,不是裸 500)
- schemaVersion v5→v6:把已有的"无通道"规则补挂到默认通道
实测:8 条规则全部已关联通道
=== 实测(v11-1028-2252 / 187062644c0f)===
/logs/daily-volume?days=7 → 2 天数据,partial 标记正确
规则 8 条全部有 channelIds
平台 ready、健康 ok
|
2026-10-28 22:54:12 +00:00 |
|
opsx
|
cb91470d75
|
perf(analysis): 分层加载 + 修复裸 i18n key + 单位混用;附 Loki 查询方式的实测对比
=== 1. 界面显示裸 key(trend.subtitle / daily.subtitle / table.subtitle)===
根因:中文语言包只加了 title,**没有 subtitle**,而英文包有。
i18next 找不到 key 就把 key 原样输出,所以界面上出现 "trend.subtitle" 这种文字。
已补齐 zh 的 trend/stream/pie/daily/table 五个 subtitle,并把文案改成
人话(说明数据来源与是否需要等待),例如:
trend : 按时间分桶的日志量(来自索引统计,秒级返回)
daily : 近 7 个自然日的每日总量(来自索引统计,不受上方时间范围影响)
table : 按日志条数排前 10 的应用,点「查看日志」跳到查询页
=== 2. 分层加载:上面的先出,下面滚到再算 ===
新增 hooks/useInView.ts(IntersectionObserver,提前 200px 触发,只触发一次)。
- 首屏只跑索引类查询(histogram + index/stats),**合计 <100ms**
- 「每日日志量」「应用排行」「Ingress 分析」三块滚动到视口才发查询
- 排行卡片加载中显示"正在按应用统计(需读取日志内容,通常 20~30 秒)…",
不再是空表格让人以为卡死
=== 3. 修复自己引入的单位混用(重要)===
趋势图数据源从 count_over_time 换成 index/volume_range 后,返回值是**字节**,
但排行表里的值是**条数**,两者混用导致:
- 统计卡显示 7956179750 这种看不懂的裸数字
- 排行占比算成 0.0x%(分子条数、分母字节)
修法:
- 统计卡拆成两张且都走索引接口(实测 histogram 50ms / index-stats 43ms):
日志条数(索引统计) ← index/stats entries,精确条数
日志体积(索引统计) ← volume_range 累计,格式化为 GB
- 排行占比分母改用 apps 自身条数合计
- 去掉"告警行数"卡片:为了把重查询从 3 条降到 2 条已放弃全局 warn 统计,
显示一个恒为 0 的数字比不显示更误导
=== 4. Loki 查询方式实测对比(15 分钟 astralx,370GB/天)===
直接打 Loki,逐个验证卡在哪:
sum(count_over_time({ns}[30s])) step=30s 61s 超时 ← 全集群全局
sum by (app,ns)(count_over_time([30s])) step=30s 19.7s 5.5GB ← 现用
sum by (app,ns)(count_over_time([15m])) step=15m 47s 超时 ← 反直觉!
sum by (app)(count_over_time([30s] |= error)) step=30s 12.7s 5.5GB
单 app gateway count_over_time step=30s 12.6s 0.58GB
index/volume (limit=200) 2.2s ← 只给 selector 级,不能按 app
index/volume_range (step=60s) 133ms
index/stats 112ms ← entries/bytes 精确
两个结论(写进注释,避免以后又"优化"回去):
a) **不要把 range 和 step 一起放大**:`[15m]` + step=15m 看着"点数少",
实际要让 Loki 为单个 evaluate 点加载 15m 的 chunk,比 [30s]+step=30s 慢 2 倍以上
(47s 超时 vs 19.7s)。
b) **按 app 分组无法走索引**:Loki 3.4 的 index/volume 只回落到 selector 标签,
不支持 aggregateBy=app(传了也无效果)。所以 per-app 排行只能读日志内容,
20 秒左右是这台集群的物理下限 —— 只能靠懒加载 + 明确提示来改善体感。
=== 实测(v8-1028-2232 / 7a05683a91ae)===
logs/histogram 50ms logs/stats 43ms
by-app 排行 25077ms(懒加载,滚动到才发)
中文 subtitle 已进包:来自索引统计 / 按量排前 / 各命名空间的日志量占比 /
近 7 个自然日 / 按日志条数排前 10 各命中 1 次
|
2026-10-28 22:34:04 +00:00 |
|
opsx
|
fa7ddd67c1
|
perf(analysis): 分析页按索引/内容分层,告警默认阈值抬高
=== 1. 分析页"一直转圈/请求超时(40000ms)"的真实原因 ===
三个问题叠加,逐个量化:
a) 首屏用空 namespace 发查询
页面初始 namespace='',等策略返回前就发出 `{namespace=~".+"}` 全集群查询,
策略到位后又查一次 → 一次访问跑两遍。
→ 加 namespaceReady 门控:解析完再发(effectiveSourceId)。
b) histogram 索引查询和内容查询抢同一个信号量
/logs/histogram 首选 index/volume_range(毫秒级),但仍要先抢重型令牌,
而令牌被 4 条 count_over_time(各 14~29s)占满 → 排队/429/超时。
→ 索引路径**不再占用重型令牌**(15s 独立超时),只有回退到
count_over_time 时才抢令牌。实测各跨度(15m/1h/6h/24h)均 0s。
c) 重查询条数过多
原来 4 条全局 count_over_time(total/error/warn)+ 2 条 by-app,
串行 1~2 分钟。现在只留 2 条 by-app:
- 全局总量已由 histogram(索引)提供
- 全局 error 由 errByApp 序列求和得出
- 全局 warn 放弃(优先级最低却要 20s)
实测 by-app 单条 26s,两条串行 ~52s;主图 0s 先出。
d) 日报 7 天 count_over_time(2.6TB 扫描)必然失败
→ 改走 /logs/histogram(索引)按自然日聚合,只出"每日总量",
应用维度交给下方 Top 应用表格(有自己的时间范围)。
实测 7 天跨度毫秒级。
=== 2. 告警默认阈值抬高(告警疲劳治理)===
验证时用 `{namespace="astralx"}` + 阈值 0 触发了一次:
15 分钟命中 3051 万条 —— 阈值 0 等于把告警变成日志流复读机。
新基线(15 分钟窗口,冷却统一 120 分钟):
致命日志 FATAL 0 → 10
Go panic 0 → 10
JVM OutOfMemoryError 0 → 5
StackOverflowError 0 → 5
应用错误激增 1000 → 50000(改名,原"error > 1000"名不副实)
ingress-nginx 5xx 100 → 500
ingress-nginx 4xx 1000 → 5000
数据库连接失败 50 → 200
迁移只改"仍等于旧默认值"的规则,用户手动调过的阈值不动。
设计原则写进 seed 注释:绝不用 threshold 0;宁可少报也不要让人对告警脱敏;
绝对阈值不跨环境通用,大集群必须按 app/namespace 收窄。
=== 实测(v6-1028-2218 / 0bf66199de66)===
规则 8 条,阈值=0 的规则数 0,冷却全部 120m
histogram astralx(15m/1h/6h/24h): 0s
by-app metrics astralx 15m: 26s,正常返回数据
|
2026-10-28 22:20:19 +00:00 |
|
opsx
|
95dcd9408c
|
fix(logs,analysis,alerts): 数据源不可达、分析页超时、告警规则混入基础设施项
=== 1. 主机模式数据源地址 0.0.0.0 → 主机真实 IP(查询页"永远加载中"的根因)===
现象:切换数据源后命名空间/App 下拉不动,一直显示"加载中…",控制台报
Get "http://0.0.0.0:3100/loki/api/v1/labels": dial tcp 0.0.0.0:3100: connect: connection refused
根因:主机模式部署的 endpoint 记的是**容器监听地址** 0.0.0.0:3100,
而平台进程不在被采集主机上,0.0.0.0 只在本机有意义。
EnsureLogSourceForDeployment 把它原样写成了数据源 URL。
修法:
- 新增 reachableEndpoint():把 0.0.0.0 / [::] 换成关联主机的真实 IP
- schemaVersion v3→v4 迁移 fixUnreachableLogSourceURLs(),修正已有脏数据
- 实测:http://0.0.0.0:3100 → http://172.19.16.8:3100,该源 label 查询由 502 变 200
=== 2. 前端:区分"加载中"与"加载失败" ===
原来用 `loading={!options.length}` 表示加载中 —— 数据源不可达时列表永远为空,
于是界面永远转圈,把真实错误完全藏起来了(这次的 0.0.0.0 就是这样被掩盖的)。
改为独立的 namespaceLoading / namespaceError 两个状态:
- 切换数据源先清空旧 options(否则显示上一个源的命名空间,看着像"没反应")
- 失败显示"加载失败"并在旁边给「重试」按钮,不用刷新整页
=== 3. 日志分析页:主图改走索引接口(原来 5 条 count_over_time 全超时)===
实测 astralx(约 370GB/天)15 分钟窗口:
count_over_time → exec 29s + 排队 95s,超过 30s 护栏 → 502
index/volume_range → 0s
改为两阶段渲染:
- 总量趋势改用 /logs/histogram(内部 index/volume_range),**0s 出图**
- 错误/警告/app 维度仍需读日志内容,串行执行,先渲染主图再更新
- heavyQueryTimeout 30s → 90s(实测 error 维度查询 14s 可完成)
=== 4. 告警规则按日志平台定位收敛 ===
- seed 里删掉「Kubernetes 节点 NotReady」「容器 OOMKilled」:
这类属于 kube-state-metrics / Prometheus 的职责,用日志字符串匹配不可靠
- 迁移里清掉线上已存在的同类规则(3 条)
- 修 AlertRule / NotifyChannel 重复 ID(同 logSources 一样漏在 sequences 重建外)
=== 5. 告警页 UI ===
- 规则列表由"每条一张卡"改为紧凑表格(一屏看全,阈值可对齐比较)
- 通知通道(飞书/Lark/Webhook)从「系统设置」搬到告警页折叠区:
规则和"发到哪"是一件事的两半,分两页配容易漏配导致告警发不出去
=== 6. 权限收敛 ===
- /alerts 加 adminOnly:普通用户按查询授权只看「日志查询」,不再看到告警菜单
- 系统设置从侧栏移到右上角齿轮(低频平台配置,不占侧栏一位)
=== 7. 迁移设计教训(写进代码注释)===
v2 时把"告警去重 + 清基础设施规则"塞进 case 1,但线上库已到 v2,
那段代码永远不执行。**结构变更只要逻辑变了、目标库已达该版本,就必须再抬一版**,
不能改历史迁移分支。
=== 实测(v4-1028-2210 / 979bdb4a48e8)===
v4 迁移:数据源 URL 修正 http://0.0.0.0:3100 → http://172.19.16.8:3100
source 1 label:200,0s(astralx / log)
source 2 label:200,0s(不再 connection refused)
分析页 histogram astralx 15m:0s
分析页 metrics error astralx 15m:14s
告警规则:10 条(已无基础设施类)、ID 唯一
另清理 172.19.16.8 上 13 个残留容器,回收 6.1GB
|
2026-10-28 22:11:48 +00:00 |
|
opsx
|
1a4c75e28c
|
feat(menu): 日志查询/日志分析拆为独立菜单 + 部署菜单改名「组件部署」
按使用反馈调整信息架构:
=== 1. 日志查询、日志分析保持独立菜单(不折叠)===
上一版把两者并进「日志」分组,实际使用中不方便:
- 日志查询是最高频入口,不该多点一次 tab 才到
- 日志分析是管理员专用的聚合视图,与查询的心智模型不同
改为两个独立菜单项,/logs 的菜单名保持「日志查询」不变(用户明确要求),
/analysis 保留 adminOnly(仅管理员可见)。
=== 2. /deployments 菜单改名:部署 Loki → 组件部署 ===
命名评估(三个候选):
组件部署 ✅ 选中:部署对象是基础设施组件(Loki 实例),
与本环境大量 cex-* 业务服务明确区分。
运维在侧栏看到「服务部署」第一反应会是"发布业务服务",容易误点。
服务部署 ✗ 与业务服务语义冲突
部署 Loki ✗ 以后支持多组件(Fluent Bit / Grafana)就名不副实
文案改动:菜单 menu.deployments、页面标题、组内 tab 全部统一为「组件部署」,
en 同步为 Component Deploy / Component Deploy 页面标题。
=== 3. 顺带修:启动日志每次都打印「首次部署」===
旧实现无条件打印
log.Printf("首次部署请查看启动日志或 %s/initial-admin-password.txt ...")
于是每次重启日志里都有「首次部署」,排查时极易被误导成
"平台重新初始化了、数据是不是丢了"(本次就因此自查了一轮)。
改为仅当密码文件存在时才提示(admin 改密后该文件会被删除)。
=== 当前菜单结构(8 项)===
仪表盘 / 部署向导 / 资源接入(集群·主机·存储) / 日志服务(组件部署·日志采集)
/ 日志查询 / 日志分析(admin) / 日志告警 / 系统管理(用户·审计·设置)
=== 实测 ===
bundle 文案:组件部署1 日志查询2 日志分析2 资源接入1 日志服务1 系统管理1
启动日志「首次部署」出现次数:0
CLI self-check:overall=ok(数据库 54.7 KB,CLI 模式读取)
CLI backup:记录数 88(修复前为 0)、密钥指纹 d714ebda23908331
镜像:lghost/lokistack:latest + v0.1 = aa6313fee5bb
|
2026-10-28 21:44:15 +00:00 |
|
opsx
|
b49677f6a6
|
feat: 运维标杆化打磨 P0/P1/P2 —— 审计日志、真实指标、备份恢复、菜单收敛
审计发现 5 处硬伤(/metrics 空壳、零备份、prod 双副本数据分裂、零审计、资源偏紧),
本轮全部落地。
=== P0-1 真实 /metrics(原来是空壳)===
原实现只返回一行注释 `# LokiStack Metrics`,注释却写着"可被 Prometheus 抓取",
平台挂了/DB 写不进去/告警引擎卡死都只能靠用户反馈。
现接入 prometheus/client_golang,四层指标:
HTTP : lokistack_http_requests_total / _duration_seconds / _in_flight_requests
存储 : lokistack_db_size_bytes / _db_save_duration_seconds / _db_save_total / _db_records
业务 : lokistack_deployments{status,mode} / lokistack_collectors{status,type,run_mode}
后台任务: lokistack_alert_evaluations_total / _duration_seconds / lokistack_loki_queries_total
+ go_* / process_*(GoCollector、ProcessCollector)
关键点:path 标签用 c.FullPath() 路由模板,不用原始 URL —— 否则 117 个 Deployment
会把 Prometheus 基数炸掉。指标定义下沉 pkg/metrics 叶子包,避开
services ↔ repository 循环依赖。
=== P0-2 备份与恢复(原来 0 处代码)===
平台全部状态在 /app/data/opsxloki.json(51KB)+ kubeconfigs/ + known_hosts/,
DB 丢了 = 失去"这些资源是谁建的"的映射,从此无法管理/卸载/升级在跑的 Loki。
CLI : lokistack backup --output <path> / restore <pkg> / self-check / version
API : GET /system/backup(下载)/ POST /system/restore(校验暂存)/
POST /system/restore/apply(确认应用)/ GET /system/backups
K8s : kubernetes/pdb-backup.yaml —— 每日 03:30 CronJob,保留最近 7 份
安全设计:
- manifest.json 记录 ENCRYPTION_KEY 的 sha256 前 16 位**指纹**,用于恢复时
校验"这把钥匙能不能解开这份备份",不泄露密钥本身
- 恢复分两步(校验暂存 → 确认应用),应用前自动把当前 DB 备份为
opsxloki.json.before-restore.<时间戳>
- 包内不含明文密钥(kubeconfig 本就是加密态)
=== P0-3 prod compose 双副本 → 单副本 ===
lokistack-1 / lokistack-2 各挂独立数据卷 + nginx 轮询 = 数据分裂:
页面刷新两次可能看到不同结果,写操作只落一个副本。不是高可用。
已改单实例(nginx 仅做反代),文件顶部与 nginx.conf 都写明原因。
=== P0-4 操作审计日志(借鉴 /usr/local/src/aiops 的字段口径)===
aiops 是每个 controller 各写 recordXxxAudit();本平台有 60+ 写接口,
逐个埋点必然漏,漏掉的那个恰好最危险(删集群、改存储凭证)。
改为 **gin 中间件统一记录** + handler 通过 SetAudit 补充业务语义:
models/audit.go : 字段对齐 aiops,另加 Method/Path/Before/After
services/audit.go : 写入 + 90 天/2 万条自动裁剪 + 脱敏 + diff 生成
middleware/audit.go : 自动识别 module/action/resource,跳过只读接口
controllers/audit.go: 列表/详情/统计(仅 admin)
敏感字段(password/secretKey/privateKey/token)写入前统一替换为 ***。
=== P1 稳定性 ===
- 平台资源 1C/1Gi → 4C/4Gi 限(500m/512Mi 请求):JSON DB 全量读写 +
每分钟告警评估 + 批量发布并发,1Gi 会 OOM
- terminationGracePeriodSeconds: 30 —— 收到 SIGTERM 要等 DB 完整落盘,
否则 kubelet SIGKILL 会把 JSON 写一半(全库丢失)
- liveness timeoutSeconds 10s(默认 1s 写盘时会被误杀)
- PDB minAvailable: 0 —— 让 kubectl drain 正确等待重建,不卡维护
- DB schemaVersion + migrateSchema() 版本递进迁移(原先加字段全靠零值兜底)
- scripts/release.sh —— 不可变 tag <版本>-<日期>-<commit> + IfNotPresent,
根治 latest 并发构建互相覆盖导致的静默回滚(本项目已实际踩过)
- PVC 保持 30Gi(按长期增长预留,云盘单价低,不做在线扩容)
=== P2 易用性 ===
- 一键自检 GET /system/self-check + 仪表盘自检卡:真实探活 Loki /ready、
集群凭据解密、磁盘空间、主机状态、近 24h 失败操作
- 审计日志页 /audit:统计卡 + 用户名/模块/结果筛选 + 变更前后 diff 弹窗
- 菜单收敛 13 → 7(页内 tab 切换,URL 全部不变):
资源接入 = 集群 + 主机 + 存储
日志服务 = Loki 实例 + 采集器
日志 = 查询 + 分析
系统管理 = 用户 + 审计 + 设置
新增 GroupTabs 组件 + meta.group/groupKey,侧栏按分组去重并正确高亮
测试:go vet 通过、go test 全绿、tsc 通过
|
2026-10-28 21:27:01 +00:00 |
|