mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-12 18:26:36 +08:00
93 lines
4.9 KiB
Markdown
93 lines
4.9 KiB
Markdown
---
|
||
name: "logstore"
|
||
description: "Wavelet 项目专用:当新增或修改日志/分析用途表(访问日志、审计流水、可观测时序)、接入 internal/repository/logstore、切换日志主库、实现 PG/SQLite 回落,或判断一张表该走业务主库还是日志库时必须使用。"
|
||
---
|
||
|
||
# 日志用途表开发
|
||
|
||
开始前阅读根目录 `AGENTS.md`。DDL 用 `database-migration`;高频写入队列用 `clickhouse-batchwriter`;切换任务用 `new-async-task`。本技能只回答:**这张表是不是日志表,以及如何接入可切换的日志主库。**
|
||
|
||
分层与切换协议见 [日志用途表](../../../docs/LOGSTORE.md)。
|
||
|
||
## 先判定
|
||
|
||
日志表同时满足:
|
||
|
||
- 追加写入、几乎不更新单行
|
||
- 按时间查询/聚合,允许按保留天数删除
|
||
- 关闭 ClickHouse 后仍要能写、能查
|
||
- 不参与用户/配置/任务等事务一致性
|
||
|
||
**不要**做成日志表:用户、配置、任务执行、上传元数据、需要事务或强一致的业务实体。这些走主库 `repository`,不要进 `logstore`。
|
||
|
||
当前框架已接入的日志表:`w_user_access_logs`(管理端 API 访问审计)。
|
||
|
||
## 分层
|
||
|
||
| 层级 | 路径 | 职责 |
|
||
| :--- | :--- | :--- |
|
||
| 抽象 | `internal/repository/logstore` | 接口 + `Active`/`BuildForMigration`;apps **只**面向这里 |
|
||
| CH 实现 | `logstore` 委托 `internal/repository/analytics` | 原生 `PrepareBatch` / `ChDB` 查询 |
|
||
| 主库实现 | `logstore` GORM | PG(按月分区)与 SQLite(普通表) |
|
||
| Model | `internal/model/analytics` | 实体、`TableName`、`InsertColumns`、`BatchInsertSQL`,无 IO |
|
||
| 入队 | `internal/apps/<domain>` + `batchwriter` | `FlushFunc` 调 `logstore.Active().….BatchInsert` |
|
||
| 切换 | `internal/apps/admin/logs` 的 `logs:db_switch` | 冻结写入 → 排空 → 复制 → 翻转 `log_database` |
|
||
| 清理 | `logstore.CleanupExpired`,由 `system:cleanup` 调用 | 按库读取保留天数后 `DeleteBefore` |
|
||
|
||
`log_database` ∈ {`postgres`,`sqlite`,`clickhouse`},且只能是「随主库」或 ClickHouse:主库为 PG 时日志不能是 SQLite,反之亦然。`log_database` / `log_db_migration` 受保护,禁止管理端手动改。
|
||
|
||
## 新增一张日志表
|
||
|
||
按顺序做,列名三库必须一致。
|
||
|
||
1. **Model**
|
||
在 `internal/model/analytics/` 定义 struct;实现 `TableName()`;批量写再提供 `InsertColumns()` / `BatchInsertSQL()`。
|
||
|
||
2. **三套 DDL**(`database-migration`)
|
||
- ClickHouse:`goose/clickhouse/`,`MergeTree`,`PARTITION BY toYYYYMM(时间列)`。
|
||
- PostgreSQL:`goose/postgres/`,高频表用 `PARTITION BY RANGE (时间列)`,复合主键必须包含分区键。
|
||
- SQLite:`goose/sqlite/`,普通表 + 时间/过滤列索引。
|
||
不要在 PG/SQLite 上复制 CH 物化视图;聚合在查询时实时算。
|
||
|
||
3. **logstore 接口**
|
||
在对应 Store(现有 `UserAccessLogStore`,或新域自建接口并挂到 `Store`)补齐至少:
|
||
- 写入:`BatchInsert`(flush 目标;内调 `ensureWritable`)
|
||
- 查询:业务需要的 List/Count/聚合
|
||
- 迁移:`ListForMigration(afterID, limit)`、`MigrationRange`、`DeleteAll`、`EnsurePartitions`(PG 按月预建,CH/SQLite no-op)
|
||
- 清理:`DeleteBefore(cutoff)`
|
||
|
||
4. **双实现**
|
||
- CH:委托 `analyticsrepo`,零额外查询路径。
|
||
- GORM:PG/SQLite 共用一套;方言 SQL 只放小函数(如按日 `to_char` / `strftime`)。零值 `id` 落库前用 `idgen.NextUint64ID()`。
|
||
|
||
5. **`buildStore`**
|
||
在 `provider.go` 的 CH / GORM 分支同时挂上新域。
|
||
|
||
6. **写入**
|
||
apps 用独立 `batchwriter` 实例;`FlushFunc` → `logstore.Active(ctx)` → `BatchInsert`。禁止 `analyticsrepo.BatchInsert`、禁止 `db.ChConn`。迁移任务调用域的 `Drain`(等队列空一个 flush 周期,不要 `Stop` writer)。
|
||
|
||
7. **切换任务**
|
||
在 `copy*` 流程增加该表:`DeleteAll` 目标 → `MigrationRange` + `EnsurePartitions` → 按 id 分页复制。不要改切换协议(仍冻结写入、源数据不删、成功才翻转)。
|
||
|
||
8. **清理**
|
||
`CleanupExpired` 对该表 `DeleteBefore`;保留天数用已有 `log_retention_days_*`,不要为单表再发明一套 key,除非产品明确要求独立 TTL。
|
||
|
||
## 禁止
|
||
|
||
- apps 直接 `import` `internal/repository/analytics` 或 `db.ChConn` / `db.ChDB` 做日志读写
|
||
- 只建 CH 表、不建 PG/SQLite 回落
|
||
- 在 Handler 里逐条 `PrepareBatch` + `Send`
|
||
- 把业务表「顺便」放进 logstore 以便关 CH
|
||
- 管理端 API 改 `log_database` / `log_db_migration`
|
||
|
||
## 验证
|
||
|
||
```bash
|
||
go test ./internal/repository/logstore ./internal/repository/analytics
|
||
go test ./internal/apps/admin/logs ./internal/apps/risk_control ./internal/platform/bootstrap
|
||
make swagger # 若改了状态/查询 API
|
||
make code-check
|
||
```
|
||
|
||
对照:`w_user_access_logs` 的 model、三库 goose、`logstore` GORM/CH、`risk_control.InitLogWriter`、`logs.LogDBSwitchHandler`、`system:cleanup`。
|