重构:重命名核心组件并增强查询功能

主要更改:

1. 核心重命名
   - Seqlog -> LogHub (更准确地反映其作为日志中枢的角色)
   - NewSeqlog() -> NewLogHub()
   - LogCursor -> ProcessCursor (更准确地反映其用于处理场景)
   - seqlog_manager.go -> loghub.go (文件名与结构体名对应)

2. TopicProcessor.Reset 增强
   - 如果正在运行且没有待处理的日志,会自动停止后重置
   - 如果有待处理的日志,返回详细错误(显示已处理/总记录数)
   - 简化了 LogHub.ResetTopic,移除显式 Stop 调用

3. 新增查询方法
   - TopicProcessor.QueryFromFirst(count) - 从第一条记录向索引递增方向查询
   - TopicProcessor.QueryFromLast(count) - 从最后一条记录向索引递减方向查询
   - LogHub.QueryFromFirst(topic, count)
   - LogHub.QueryFromLast(topic, count)

4. 测试覆盖
   - 添加 query_test.go - QueryFromProcessing 测试
   - 添加 TestQueryFromFirstAndLast - TopicProcessor 查询测试
   - 添加 TestLogHubQueryFromFirstAndLast - LogHub 查询测试
   - 添加 TestTopicResetWithPendingRecords - Reset 增强功能测试

5. 示例代码
   - 添加 example/get_record/ - 演示 QueryFromProcessing 用法
   - 更新所有示例以使用 LogHub 和新 API

所有测试通过 

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 13:26:21 +08:00
parent dfdc27c67f
commit 90cc9e21c9
14 changed files with 795 additions and 275 deletions

View File

@@ -437,7 +437,7 @@ func TestSeqlogBasic(t *testing.T) {
os.MkdirAll(tmpDir, 0755)
defer os.RemoveAll(tmpDir)
seqlog := NewSeqlog(tmpDir, slog.Default(), nil)
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
// 注册 handler
appLogs := make([]string, 0)
@@ -493,7 +493,7 @@ func TestSeqlogDefaultHandler(t *testing.T) {
return nil
}
seqlog := NewSeqlog(tmpDir, slog.Default(), defaultHandler)
seqlog := NewLogHub(tmpDir, slog.Default(), defaultHandler)
// 注册特定 handler
seqlog.RegisterHandler("special", func(rec *Record) error {
@@ -534,7 +534,7 @@ func TestSeqlogDynamicRegistration(t *testing.T) {
os.MkdirAll(tmpDir, 0755)
defer os.RemoveAll(tmpDir)
seqlog := NewSeqlog(tmpDir, slog.Default(), nil)
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
// 先注册 handlerhandler 现在是必填项)
logs := make([]string, 0)
@@ -566,7 +566,7 @@ func TestDynamicConfigUpdate(t *testing.T) {
os.MkdirAll(tmpDir, 0755)
defer os.RemoveAll(tmpDir)
seqlog := NewSeqlog(tmpDir, slog.Default(), nil)
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
// 注册 handler
logs := make([]string, 0)
@@ -746,7 +746,7 @@ func TestSeqlogAutoRecovery(t *testing.T) {
return nil
}
seqlog1 := NewSeqlog(tmpDir, slog.Default(), defaultHandler)
seqlog1 := NewLogHub(tmpDir, slog.Default(), defaultHandler)
seqlog1.Start()
// 写入一些日志
@@ -777,7 +777,7 @@ func TestSeqlogAutoRecovery(t *testing.T) {
mu.Unlock()
// 第二阶段:重启并自动恢复
seqlog2 := NewSeqlog(tmpDir, slog.Default(), defaultHandler)
seqlog2 := NewLogHub(tmpDir, slog.Default(), defaultHandler)
seqlog2.Start()
// 写入新日志
@@ -880,7 +880,7 @@ func TestSeqlogCleanup(t *testing.T) {
os.MkdirAll(tmpDir, 0755)
defer os.RemoveAll(tmpDir)
seqlog := NewSeqlog(tmpDir, slog.Default(), nil)
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
seqlog.Start()
// 写入多个 topic 的日志
@@ -1118,7 +1118,7 @@ func TestSeqlogStats(t *testing.T) {
return nil
}
seq := NewSeqlog(tmpDir, slog.Default(), handler)
seq := NewLogHub(tmpDir, slog.Default(), handler)
if err := seq.Start(); err != nil {
t.Fatalf("failed to start seqlog: %v", err)
}
@@ -1229,16 +1229,19 @@ func TestRecordQuery(t *testing.T) {
}
defer query.Close()
// 测试查询当前位置
current, err := query.QueryOldest(startIdx, 1)
// 测试查询当前位置(使用 QueryNewest 查询 startIdx
current, err := query.QueryNewest(startIdx-1, 1)
if err != nil {
t.Fatalf("failed to query current: %v", err)
}
if len(current) != 1 {
t.Fatalf("expected 1 current result, got %d", len(current))
}
if string(current[0].Data) != "message 5" {
t.Errorf("expected current 'message 5', got '%s'", string(current[0].Data))
if string(current[0].Record.Data) != "message 5" {
t.Errorf("expected current 'message 5', got '%s'", string(current[0].Record.Data))
}
if current[0].Index != startIdx {
t.Errorf("expected index %d, got %d", startIdx, current[0].Index)
}
// 手动判断状态
status := GetRecordStatus(startIdx, startIdx, endIdx)
@@ -1246,43 +1249,53 @@ func TestRecordQuery(t *testing.T) {
t.Errorf("expected status Processing, got %s", status)
}
// 测试向后查询(查询更早的记录,返回按索引递增排序
backResults, err := query.QueryNewest(startIdx-1, 3)
// 测试 QueryOldest查询更早的记录向索引递减方向
// QueryOldest(5, 3) 查询索引 2, 3, 4
backResults, err := query.QueryOldest(startIdx, 3)
if err != nil {
t.Fatalf("failed to query backward: %v", err)
}
if len(backResults) != 3 {
t.Errorf("expected 3 backward results, got %d", len(backResults))
}
// 向后查询返回按索引递增排序的结果
// 返回按索引递增排序的结果2, 3, 4
expectedBack := []string{"message 2", "message 3", "message 4"}
for i, rec := range backResults {
if string(rec.Data) != expectedBack[i] {
t.Errorf("backward[%d]: expected '%s', got '%s'", i, expectedBack[i], string(rec.Data))
if string(rec.Record.Data) != expectedBack[i] {
t.Errorf("backward[%d]: expected '%s', got '%s'", i, expectedBack[i], string(rec.Record.Data))
}
// 手动判断状态:索引 2, 3, 4
recStatus := GetRecordStatus(startIdx-3+i, startIdx, endIdx)
expectedIndex := startIdx - 3 + i
if rec.Index != expectedIndex {
t.Errorf("backward[%d]: expected index %d, got %d", i, expectedIndex, rec.Index)
}
// 手动判断状态:索引 2, 3, 4 都已处理
recStatus := GetRecordStatus(rec.Index, startIdx, endIdx)
if recStatus != StatusProcessed {
t.Errorf("backward[%d]: expected status Processed, got %s", i, recStatus)
}
}
// 测试向前查询(查询更新的记录
forwardResults, err := query.QueryOldest(endIdx, 3)
// 测试 QueryNewest查询更新的记录向索引递增方向
// QueryNewest(endIdx, 3) 从 endIdx 向后查询,查询索引 6, 7, 8
forwardResults, err := query.QueryNewest(endIdx-1, 3)
if err != nil {
t.Fatalf("failed to query forward: %v", err)
}
if len(forwardResults) != 3 {
t.Errorf("expected 3 forward results, got %d", len(forwardResults))
}
// 向前查询返回顺序结果
// 返回按索引递增排序的结果6, 7, 8
expectedForward := []string{"message 6", "message 7", "message 8"}
for i, rec := range forwardResults {
if string(rec.Data) != expectedForward[i] {
t.Errorf("forward[%d]: expected '%s', got '%s'", i, expectedForward[i], string(rec.Data))
if string(rec.Record.Data) != expectedForward[i] {
t.Errorf("forward[%d]: expected '%s', got '%s'", i, expectedForward[i], string(rec.Record.Data))
}
// 手动判断状态:索引 6, 7, 8
recStatus := GetRecordStatus(endIdx+i, startIdx, endIdx)
expectedIndex := endIdx + i
if rec.Index != expectedIndex {
t.Errorf("forward[%d]: expected index %d, got %d", i, expectedIndex, rec.Index)
}
// 手动判断状态:索引 6, 7, 8 待处理
recStatus := GetRecordStatus(rec.Index, startIdx, endIdx)
if recStatus != StatusPending {
t.Errorf("forward[%d]: expected status Pending, got %s", i, recStatus)
}
@@ -1384,7 +1397,7 @@ func TestSeqlogQuery(t *testing.T) {
return nil
}
seq := NewSeqlog(tmpDir, slog.Default(), handler)
seq := NewLogHub(tmpDir, slog.Default(), handler)
if err := seq.Start(); err != nil {
t.Fatalf("failed to start seqlog: %v", err)
}
@@ -1414,7 +1427,7 @@ func TestSeqlogQuery(t *testing.T) {
// 获取 processor 用于查询(带状态)
processor, _ := seq.GetProcessor("app")
index := processor.Index()
totalCount := processor.GetRecordCount()
// 测试查询当前
if startIdx < endIdx {
@@ -1439,7 +1452,7 @@ func TestSeqlogQuery(t *testing.T) {
}
// 测试向前查询
if startIdx < index.Count() {
if startIdx < totalCount {
forward, err := processor.QueryOldest(endIdx, 3)
if err != nil {
t.Fatalf("failed to query forward: %v", err)
@@ -1569,7 +1582,7 @@ func TestSeqlogEventSubscription(t *testing.T) {
return nil
}
seq := NewSeqlog(tmpDir, slog.Default(), handler)
seq := NewLogHub(tmpDir, slog.Default(), handler)
if err := seq.Start(); err != nil {
t.Fatalf("failed to start seqlog: %v", err)
}
@@ -1612,7 +1625,7 @@ func TestMultiTopicEventSubscription(t *testing.T) {
return nil
}
seq := NewSeqlog(tmpDir, slog.Default(), handler)
seq := NewLogHub(tmpDir, slog.Default(), handler)
if err := seq.Start(); err != nil {
t.Fatalf("failed to start seqlog: %v", err)
}
@@ -1658,7 +1671,7 @@ func TestMultiTopicEventSubscription(t *testing.T) {
func TestTopicReset(t *testing.T) {
tmpDir := t.TempDir()
seqlog := NewSeqlog(tmpDir, slog.Default(), nil)
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
// 注册 handler
seqlog.RegisterHandler("test", func(rec *Record) error {
@@ -1750,6 +1763,50 @@ func TestTopicReset(t *testing.T) {
seqlog.Stop()
}
// TestTopicResetWithPendingRecords 测试当有待处理日志时 Reset 返回错误
func TestTopicResetWithPendingRecords(t *testing.T) {
tmpDir := t.TempDir()
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
// 注册一个慢速 handler让日志堆积
slowHandler := func(rec *Record) error {
time.Sleep(100 * time.Millisecond) // 模拟慢速处理
return nil
}
seqlog.RegisterHandler("test", slowHandler)
seqlog.Start()
// 快速写入多条日志
for i := 0; i < 10; i++ {
data := []byte(fmt.Sprintf("message %d", i))
if _, err := seqlog.Write("test", data); err != nil {
t.Fatalf("写入失败: %v", err)
}
}
// 短暂等待,让一部分日志开始处理但不是全部
time.Sleep(200 * time.Millisecond)
// 尝试重置,应该失败因为有待处理的日志
err := seqlog.ResetTopic("test")
if err == nil {
t.Fatal("期望 Reset 失败因为有待处理的日志,但成功了")
}
t.Logf("预期的错误: %v", err)
// 停止处理
seqlog.Stop()
// 现在 Reset 应该成功(停止后没有待处理的日志)
processor, _ := seqlog.GetProcessor("test")
if err := processor.Reset(); err != nil {
t.Fatalf("停止后 Reset 应该成功: %v", err)
}
}
// TestQueryOldestNewest 测试 QueryOldest 和 QueryNewest
func TestQueryOldestNewest(t *testing.T) {
tmpDir := t.TempDir()
@@ -1773,10 +1830,11 @@ func TestQueryOldestNewest(t *testing.T) {
}
}
// 测试 QueryOldest - 索引 0 开始查询 3 条
oldest, err := processor.QueryOldest(0, 3)
// 测试 QueryNewest - 查询索引 0, 1, 2向索引递增方向
// QueryNewest(-1, 3) 从 -1 向后查询,得到索引 0, 1, 2
oldest, err := processor.QueryNewest(-1, 3)
if err != nil {
t.Fatalf("QueryOldest failed: %v", err)
t.Fatalf("QueryNewest failed: %v", err)
}
if len(oldest) != 3 {
t.Errorf("expected 3 records, got %d", len(oldest))
@@ -1787,14 +1845,18 @@ func TestQueryOldestNewest(t *testing.T) {
if string(oldest[i].Record.Data) != expected {
t.Errorf("oldest[%d]: expected %s, got %s", i, expected, string(oldest[i].Record.Data))
}
t.Logf("Oldest[%d]: %s - %s", i, string(oldest[i].Record.Data), oldest[i].Status)
if oldest[i].Index != i {
t.Errorf("oldest[%d]: expected index %d, got %d", i, i, oldest[i].Index)
}
t.Logf("Oldest[%d]: index=%d, %s - %s", i, oldest[i].Index, string(oldest[i].Record.Data), oldest[i].Status)
}
// 测试 QueryNewest - 索引 9 结束查询 3 条
// 测试 QueryOldest - 查询索引 7, 8, 9向索引递减方向
// QueryOldest(10, 3) 从 10 向前查询,得到索引 7, 8, 9
totalCount := processor.GetRecordCount()
newest, err := processor.QueryNewest(totalCount-1, 3)
newest, err := processor.QueryOldest(totalCount, 3)
if err != nil {
t.Fatalf("QueryNewest failed: %v", err)
t.Fatalf("QueryOldest failed: %v", err)
}
if len(newest) != 3 {
t.Errorf("expected 3 records, got %d", len(newest))
@@ -1805,13 +1867,17 @@ func TestQueryOldestNewest(t *testing.T) {
if string(newest[i].Record.Data) != expected {
t.Errorf("newest[%d]: expected %s, got %s", i, expected, string(newest[i].Record.Data))
}
t.Logf("Newest[%d]: %s - %s", i, string(newest[i].Record.Data), newest[i].Status)
if newest[i].Index != 7+i {
t.Errorf("newest[%d]: expected index %d, got %d", i, 7+i, newest[i].Index)
}
t.Logf("Newest[%d]: index=%d, %s - %s", i, newest[i].Index, string(newest[i].Record.Data), newest[i].Status)
}
// 测试超出范围
all, err := processor.QueryOldest(0, 100)
// 测试超出范围 - 查询所有记录
// QueryNewest(-1, 100) 从 -1 向后查询,会返回所有记录(最多 100 条)
all, err := processor.QueryNewest(-1, 100)
if err != nil {
t.Fatalf("QueryOldest(0, 100) failed: %v", err)
t.Fatalf("QueryNewest(-1, 100) failed: %v", err)
}
if len(all) != 10 {
t.Errorf("expected 10 records, got %d", len(all))
@@ -1828,11 +1894,195 @@ func TestQueryOldestNewest(t *testing.T) {
}
defer processor2.Close()
emptyOldest, err := processor2.QueryOldest(0, 10)
emptyNewest, err := processor2.QueryNewest(-1, 10)
if err != nil {
t.Fatalf("QueryOldest on empty failed: %v", err)
t.Fatalf("QueryNewest on empty failed: %v", err)
}
if len(emptyOldest) != 0 {
t.Errorf("expected 0 records, got %d", len(emptyOldest))
if len(emptyNewest) != 0 {
t.Errorf("expected 0 records, got %d", len(emptyNewest))
}
}
// TestQueryFromFirstAndLast 测试 QueryFromFirst 和 QueryFromLast
func TestQueryFromFirstAndLast(t *testing.T) {
tmpDir := t.TempDir()
// 创建 TopicProcessor
processor, err := NewTopicProcessor(tmpDir, "test", nil, &TopicConfig{
Handler: func(rec *Record) error {
return nil
},
})
if err != nil {
t.Fatal(err)
}
defer processor.Close()
// 写入 10 条测试数据
for i := 0; i < 10; i++ {
data := fmt.Sprintf("message %d", i)
if _, err := processor.Write([]byte(data)); err != nil {
t.Fatal(err)
}
}
// 测试 QueryFromFirst - 从第一条记录向索引递增方向查询
t.Run("QueryFromFirst", func(t *testing.T) {
// 查询前 3 条记录
records, err := processor.QueryFromFirst(3)
if err != nil {
t.Fatalf("QueryFromFirst failed: %v", err)
}
if len(records) != 3 {
t.Fatalf("expected 3 records, got %d", len(records))
}
// 验证结果:应该是索引 0, 1, 2
for i := 0; i < 3; i++ {
expectedData := fmt.Sprintf("message %d", i)
if string(records[i].Record.Data) != expectedData {
t.Errorf("records[%d]: expected %s, got %s", i, expectedData, string(records[i].Record.Data))
}
if records[i].Index != i {
t.Errorf("records[%d]: expected index %d, got %d", i, i, records[i].Index)
}
t.Logf("FromFirst[%d]: index=%d, %s - %s", i, records[i].Index, string(records[i].Record.Data), records[i].Status)
}
// 查询超过总数的记录
allRecords, err := processor.QueryFromFirst(100)
if err != nil {
t.Fatalf("QueryFromFirst(100) failed: %v", err)
}
if len(allRecords) != 10 {
t.Errorf("expected 10 records, got %d", len(allRecords))
}
})
// 测试 QueryFromLast - 从最后一条记录向索引递减方向查询
t.Run("QueryFromLast", func(t *testing.T) {
// 查询最后 3 条记录
records, err := processor.QueryFromLast(3)
if err != nil {
t.Fatalf("QueryFromLast failed: %v", err)
}
if len(records) != 3 {
t.Fatalf("expected 3 records, got %d", len(records))
}
// 验证结果:应该是索引 7, 8, 9按索引递增顺序排列
for i := 0; i < 3; i++ {
expectedIndex := 7 + i
expectedData := fmt.Sprintf("message %d", expectedIndex)
if string(records[i].Record.Data) != expectedData {
t.Errorf("records[%d]: expected %s, got %s", i, expectedData, string(records[i].Record.Data))
}
if records[i].Index != expectedIndex {
t.Errorf("records[%d]: expected index %d, got %d", i, expectedIndex, records[i].Index)
}
t.Logf("FromLast[%d]: index=%d, %s - %s", i, records[i].Index, string(records[i].Record.Data), records[i].Status)
}
// 查询超过总数的记录
allRecords, err := processor.QueryFromLast(100)
if err != nil {
t.Fatalf("QueryFromLast(100) failed: %v", err)
}
if len(allRecords) != 10 {
t.Errorf("expected 10 records, got %d", len(allRecords))
}
})
// 测试空数据库
t.Run("EmptyDatabase", func(t *testing.T) {
emptyProcessor, err := NewTopicProcessor(t.TempDir(), "empty", nil, &TopicConfig{
Handler: func(rec *Record) error {
return nil
},
})
if err != nil {
t.Fatal(err)
}
defer emptyProcessor.Close()
// QueryFromFirst 应该返回空数组
firstRecords, err := emptyProcessor.QueryFromFirst(10)
if err != nil {
t.Fatalf("QueryFromFirst on empty failed: %v", err)
}
if len(firstRecords) != 0 {
t.Errorf("expected 0 records, got %d", len(firstRecords))
}
// QueryFromLast 应该返回空数组
lastRecords, err := emptyProcessor.QueryFromLast(10)
if err != nil {
t.Fatalf("QueryFromLast on empty failed: %v", err)
}
if len(lastRecords) != 0 {
t.Errorf("expected 0 records, got %d", len(lastRecords))
}
})
}
// TestLogHubQueryFromFirstAndLast 测试 LogHub 的 QueryFromFirst 和 QueryFromLast
func TestLogHubQueryFromFirstAndLast(t *testing.T) {
tmpDir := t.TempDir()
seqlog := NewLogHub(tmpDir, slog.Default(), nil)
seqlog.RegisterHandler("test", func(rec *Record) error {
return nil
})
seqlog.Start()
defer seqlog.Stop()
// 写入测试数据
for i := 0; i < 10; i++ {
data := fmt.Sprintf("message %d", i)
if _, err := seqlog.Write("test", []byte(data)); err != nil {
t.Fatal(err)
}
}
// 测试 QueryFromFirst
firstRecords, err := seqlog.QueryFromFirst("test", 3)
if err != nil {
t.Fatalf("QueryFromFirst failed: %v", err)
}
if len(firstRecords) != 3 {
t.Fatalf("expected 3 records, got %d", len(firstRecords))
}
for i := 0; i < 3; i++ {
if firstRecords[i].Index != i {
t.Errorf("firstRecords[%d]: expected index %d, got %d", i, i, firstRecords[i].Index)
}
}
// 测试 QueryFromLast
lastRecords, err := seqlog.QueryFromLast("test", 3)
if err != nil {
t.Fatalf("QueryFromLast failed: %v", err)
}
if len(lastRecords) != 3 {
t.Fatalf("expected 3 records, got %d", len(lastRecords))
}
for i := 0; i < 3; i++ {
expectedIndex := 7 + i
if lastRecords[i].Index != expectedIndex {
t.Errorf("lastRecords[%d]: expected index %d, got %d", i, expectedIndex, lastRecords[i].Index)
}
}
// 测试不存在的 topic
_, err = seqlog.QueryFromFirst("nonexistent", 10)
if err == nil {
t.Error("expected error for nonexistent topic")
}
_, err = seqlog.QueryFromLast("nonexistent", 10)
if err == nil {
t.Error("expected error for nonexistent topic")
}
}