mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-29 22:06:38 +08:00
7d71f1e4e1
- New internal/repository/logstore abstraction: exported domain interfaces (AccessLogStore/ObservabilityStore/UserAccessLogStore/StatusStore), config-driven provider (Active/Build/Migrating/SetConfigReader), GORM implementation for PostgreSQL/SQLite (incl. hourly rollups computed in real time, migration listers, PG partition maintenance), and a ClickHouse wrapper preserving the native batch path; repository facade delegates to logstore; import-lint test enforces apps never import analyticsrepo. - ClickHouse is now optional: the log DB is either the main DB (postgres when database.enabled, else sqlite) or clickhouse; boot validation + first-run seed; log_database / log_db_migration are protected keys. - New user task 切换日志数据库 (of_log_db_switch): freeze log writes, drain batch writers, copy all 6 raw log tables by id (preserving IDs) with target-partition pre-creation for PG, flip log_database on success, clear the freeze flag on failure. - Per-store retention (log_retention_days_*) with expiry cleanup folded into the daily system_cleanup task; legacy database_auto_cleanup_* and of_database_auto_cleanup decommissioned. - goose migrations: 6 log tables in PG (2 monthly-partitioned) + SQLite, retention config seeds, schedule cleanup; GET /api/v1/admin/status/log-database endpoint; frontend retention settings, switch-task UI and status badge; changelog and docs updated. docs(plan): log database decoupling implementation plan docs(design): log database decoupling design (ClickHouse optional)
66 lines
2.0 KiB
Go
66 lines
2.0 KiB
Go
// Copyright 2026 Arctel.net
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package logstore
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Rain-kl/Wavelet/internal/model"
|
|
analyticsmodel "github.com/Rain-kl/Wavelet/internal/model/analytics"
|
|
)
|
|
|
|
// TestGormAccessLogInsertBatchHooks 覆盖访问日志写入入口:
|
|
// 冻结检查、hook 入队、不直接落库、flush 后可见(行为与旧 repository clickhouse 包装一致)。
|
|
func TestGormAccessLogInsertBatchHooks(t *testing.T) {
|
|
ResetForTest()
|
|
SetConfigReader(func(_ context.Context, key string) (string, error) {
|
|
return "", nil
|
|
})
|
|
defer ResetForTest()
|
|
|
|
s := newTestGormStore(t)
|
|
ctx := context.Background()
|
|
now := time.Now().UTC()
|
|
|
|
var hooked []analyticsmodel.NodeAccessLog
|
|
SetAccessLogHooks(AccessLogHooks{
|
|
QueueNodeAccessLogs: func(logs []analyticsmodel.NodeAccessLog) {
|
|
hooked = append(hooked, logs...)
|
|
},
|
|
})
|
|
defer SetAccessLogHooks(AccessLogHooks{})
|
|
|
|
records := []*model.OpenFlareAccessLog{
|
|
{NodeID: "n1", LoggedAt: now, RemoteAddr: "1.1.1.1", StatusCode: 200, BytesSent: 100},
|
|
{NodeID: "n1", LoggedAt: now, RemoteAddr: "2.2.2.2", StatusCode: 404},
|
|
}
|
|
if err := s.InsertBatch(ctx, records); err != nil {
|
|
t.Fatalf("insert batch: %v", err)
|
|
}
|
|
if len(hooked) != 2 || hooked[0].RemoteAddr != "1.1.1.1" || hooked[0].BytesSent != 100 || hooked[1].StatusCode != 404 {
|
|
t.Fatalf("hook rows mismatch: %+v", hooked)
|
|
}
|
|
// 写入入口只入队、不直接落库。
|
|
rows, err := s.List(ctx, model.OpenFlareAccessLogQuery{NodeID: "n1"})
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
if len(rows) != 0 {
|
|
t.Fatalf("entry insert must not write rows, got %d", len(rows))
|
|
}
|
|
// flush 后可见。
|
|
if err := s.BatchInsertNodeAccessLogs(ctx, hooked); err != nil {
|
|
t.Fatalf("flush: %v", err)
|
|
}
|
|
rows, err = s.List(ctx, model.OpenFlareAccessLogQuery{NodeID: "n1"})
|
|
if err != nil {
|
|
t.Fatalf("list after flush: %v", err)
|
|
}
|
|
if len(rows) != 2 {
|
|
t.Fatalf("list after flush want 2, got %d", len(rows))
|
|
}
|
|
}
|