Files
seqlog/example/webui/main.go
bourdon 810664eb12 重构:优化记录格式并修复核心功能
- 修改记录存储格式为 [4B len][8B offset][4B CRC][16B UUID][data]
- 修复 TopicProcessor 中 WaitGroup 使用错误导致 handler 不执行的问题
- 修复写入保护逻辑,避免 dirtyOffset=-1 时误判为写入中
- 添加统计信息定期持久化功能
- 改进 UTF-8 字符截断处理,防止 CJK 字符乱码
- 优化 Web UI:显示人类可读的文件大小,支持点击外部关闭弹窗
- 重构示例代码,添加 webui 和 webui_integration 示例

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 17:54:49 +08:00

115 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"fmt"
"log/slog"
"math/rand"
"os"
"strings"
"sync"
"time"
"unicode/utf8"
"code.tczkiot.com/seqlog"
)
func main() {
// 创建日志目录
baseDir := "./logs"
if err := os.MkdirAll(baseDir, 0755); err != nil {
panic(err)
}
// 创建 LogHub
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
// 自定义处理器:打印处理的记录
handler := func(topic string, record *seqlog.Record) error {
previewSize := min(int(record.Len), 100)
validPreviewSize := previewSize
if previewSize > 0 && previewSize < int(record.Len) {
// 只有在截断的情况下才需要检查
// 从后往前最多检查 3 个字节,找到最后一个完整的 UTF-8 字符边界
for i := 0; i < 3 && validPreviewSize > 0; i++ {
if utf8.Valid(record.Data[:validPreviewSize]) {
break
}
validPreviewSize--
}
}
fmt.Printf("[%s] 处理记录: %s\n", topic, string(record.Data[:validPreviewSize]))
return nil
}
hub := seqlog.NewLogHub(baseDir, logger, handler)
// 启动 LogHub会自动发现和启动所有 topic
if err := hub.Start(); err != nil {
panic(err)
}
defer hub.Stop()
// topic 列表(会在第一次写入时自动创建)
topics := []string{"app", "system", "access"}
// 在后台启动 Web UI 服务器
go func() {
fmt.Println("启动 Web UI 服务器: http://localhost:8080")
if err := hub.ServeUI(":8080"); err != nil {
fmt.Printf("Web UI 服务器错误: %v\n", err)
}
}()
// 生成随机大小的数据2KB 到 10MB
generateRandomData := func(minSize, maxSize int) []byte {
size := minSize + rand.Intn(maxSize-minSize)
// 使用重复字符填充,模拟实际日志内容
return []byte(strings.Repeat("X", size))
}
// 启动多个并发写入器(提高并发数)
var wg sync.WaitGroup
concurrentWriters := 10 // 10 个并发写入器
for i := range concurrentWriters {
wg.Add(1)
go func(writerID int) {
defer wg.Done()
count := 0
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
count++
// 随机选择 topic
topic := topics[rand.Intn(len(topics))]
// 生成随机大小的数据2KB 到 2MB
minSize := 2 * 1024 // 2KB
maxSize := 2 * 1024 * 1024 // 2MB
randomData := generateRandomData(minSize, maxSize)
// 组合消息头和随机数据
message := fmt.Sprintf("[Writer-%d] 日志 #%d - %s - 大小: %d bytes\n",
writerID, count, time.Now().Format(time.RFC3339), len(randomData))
fullData := append([]byte(message), randomData...)
if _, err := hub.Write(topic, fullData); err != nil {
fmt.Printf("写入失败: %v\n", err)
}
}
}(i)
}
// 等待用户中断
fmt.Println("\n==== Seqlog Web UI 示例 ====")
fmt.Println("访问 http://localhost:8080 查看 Web UI")
fmt.Println("按 Ctrl+C 退出")
// 阻塞主线程
select {}
}