Commit Graph
15 Commits
Author SHA1 Message Date
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 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 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 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 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 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 a32f93f533 feat(deployments,analysis): 保留弹框重设计 + 分析页命名空间筛选 + 过期 chunk 真正删除
=== 一、保留策略弹框重设计(RetentionDialog)===
旧版问题:
1. 主输入是 YAML textarea,用户必须会写 `limits_config.retention_period: 30d`
2. 三个按钮:取消 / 仅保存 / 保存并应用 —— 「仅保存」只改部署记录不重载 Loki,
   今天已经造成一次真实故障:DB 记录 7 天、运行时 ConfigMap 是 24h,配置漂移
3. 「查看完整配置」是独立按钮 + 独立状态,多一次点击
4. 硬编码提示 `retention_delete_delay: 2h`,而实际已改成 10m,属于误导

新版:
- 主输入改为**保留天数数字框**(1–3650),YAML 由前端拼,用户不用写
- 快捷预设 1/7/14/30/90,实时显示「= 720 小时」
- >7 天给出存储成本提示
- 完整运行 config 改成 `<details>` 折叠,展开即自动加载(去掉独立按钮)
- **按钮只剩 2 个:取消 / 保存并应用**
- 删掉硬编码的 compactor 提示块

=== 二、日志分析页加命名空间筛选 ===
根因:analysis 页写死 namespace: '',等价 `{namespace=~".+"}` —— 一次分析扫全集群
(实测约 370 GB/天),5 条 count_over_time 跑 30s 都出不来,页面永远转圈。

- 新增命名空间下拉(从 Loki label values 拉取,localStorage 记忆)
- 默认取业务命名空间 astralx(其次列表首个),不再默认全集群
- 「全部命名空间(较慢)」降级为显式选项
- 命名空间未就绪时先显示 loading,避免先发一次全集群查询
- 5 条查询改**分批(每批 2 条)**执行 + 429 自动重试一次

=== 三、后端并发/超时调整 ===
- 重查询并发上限 2 → 4,且拿不到令牌时**排队最多 10s**再拒绝
  (原来立即 429,导致分析页 5 条并发里有 3 条直接失败)
- maxLogQueryLimit 1000 → 5000,与前端下拉完全对齐(原来前端选 5000 会被静默截断)

=== 四、过期 chunk 真正删除(关键修复)===
实测发现:索引表已从 COS 删除,但 chunk 对象仍残留 21,354 个 / 24.8 GB,
最老的 45 小时前 —— 因为 Loki 默认 `retention_delete_delay` = **2h**,
标记后要等 2 小时才真正删 chunk。

- 配置显式设置 `compactor.retention_delete_delay: 10m`
- 已用 `loki -verify-config` 校验(config is valid)
- 删除前后对比:
    对象数 46,536 → 23,934;占用 53.61 GB → 27.25 GB
    超期对象 21,354 个 / 24.8 GB → **0 个**
    桶内最老对象 45.2h → 24.4h(= 保留期 1 天)
- deployment_config_test 同步调整:retention_delete_delay 从"禁止出现"移入"必须存在"

镜像:lghost/lokistack:latest + v0.1 = sha256:b4d98e016bca
2026-10-28 17:05:57 +00:00
opsx 3a8eb521e2 chore(i18n,ui): 全站冗长文案精简 + shadcn 样板汉化 + 一致性修复
- 文案:约 25 组长文案中英同步精简(数据源引导/RBAC 注解/采集提示/删除确认等),三处重复 Ingress 教学收敛
- 汉化 shadcn 样板:分页 Previous/Next/aria、弹窗与抽屉 Close 接入 i18n(common.ui.*)
- 一致性:查询授权面板 p-4、仪表盘卡片 p-6、分析卡头 pb-3、部署总数图标配色、
  每日统计错误可重试、策略卡/最近部署行 truncate、状态徽章字重、三处原生 checkbox 换 shadcn、Label 浏览器圆角
- 词典:删除无引用的 raw.*/views.* 死键;zh 复数键风格统一(importCount/hits)
- 快捷查询提示、Ingress 统计口径与表内说明按反馈删减
2026-10-28 22:44:06 +08:00
opsx 5ce90fe2e5 fix(analysis): 去掉原始日志底栏与命名空间筛选
按产品/设计锁死:删底部 goQuery 虚线卡(表内「查看日志」保留);筛选栏去掉命名空间下拉与 labelValues 加载,指标固定全命名空间聚合。
2026-10-28 22:06:38 +08:00
Codex 11d0fff764 fix(frontend): 补齐 analysis 构建依赖与共享组件搬迁
补 commit hooks/图表组件、路由与 i18n domains、logs metrics API;
StatCard/TimeRangePicker/ingress-access-log 抽到共享路径,干净 checkout 可编过。
2026-10-28 22:00:30 +08:00
Codex f993d5b420 fix(frontend): logs/analysis P0 — 职责拆分与布局收口
砍 analysis 明细可视化改「去日志查询」;无数据源 EmptyState;日图标固定近7天。
logs 去掉负 margin、页头常显、筛选/查询分层;level 色阶;Label 保持默认收起。
2026-10-28 21:54:40 +08:00