package main import ( "encoding/json" "fmt" "log/slog" "math/rand" "net/http" "os" "strconv" "sync" "time" "code.tczkiot.com/seqlog" ) var ( seq *seqlog.Seqlog logger *slog.Logger queryCache = make(map[string]*seqlog.RecordQuery) queryCacheMu sync.RWMutex ) func main() { // 初始化 logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ Level: slog.LevelInfo, })) // 创建 Seqlog seq = seqlog.NewSeqlog("logs", logger, func(topic string, rec *seqlog.Record) error { // 简单的日志处理:只打印摘要信息 dataPreview := string(rec.Data) if len(dataPreview) > 100 { dataPreview = dataPreview[:100] + "..." } logger.Info("处理日志", "topic", topic, "size", len(rec.Data), "preview", dataPreview) return nil }) if err := seq.Start(); err != nil { logger.Error("启动失败", "error", err) os.Exit(1) } defer seq.Stop() logger.Info("Seqlog 已启动") // 启动后台业务模拟 go simulateBusiness() // 启动 Web 服务器 http.HandleFunc("/", handleIndex) http.HandleFunc("/api/topics", handleTopics) http.HandleFunc("/api/stats", handleStats) http.HandleFunc("/api/query", handleQuery) http.HandleFunc("/api/write", handleWrite) addr := ":8080" logger.Info("Web 服务器启动", "地址", "http://localhost"+addr) if err := http.ListenAndServe(addr, nil); err != nil { logger.Error("服务器错误", "error", err) } } // 模拟业务写日志 func simulateBusiness() { topics := []string{"app", "api", "database", "cache"} actions := []string{"查询", "插入", "更新", "删除", "连接", "断开", "备份", "恢复", "同步"} status := []string{"成功", "失败", "超时", "重试"} ticker := time.NewTicker(2 * time.Second) defer ticker.Stop() for range ticker.C { // 随机选择 topic 和内容 topic := topics[rand.Intn(len(topics))] action := actions[rand.Intn(len(actions))] st := status[rand.Intn(len(status))] // 随机生成日志大小:2KB 到 10MB // 80% 概率生成小日志(2KB-100KB) // 15% 概率生成中日志(100KB-1MB) // 5% 概率生成大日志(1MB-10MB) var logSize int prob := rand.Intn(100) if prob < 80 { // 2KB - 100KB logSize = 2*1024 + rand.Intn(98*1024) } else if prob < 95 { // 100KB - 1MB logSize = 100*1024 + rand.Intn(924*1024) } else { // 1MB - 10MB logSize = 1024*1024 + rand.Intn(9*1024*1024) } // 生成日志内容 header := fmt.Sprintf("[%s] %s %s - 用时: %dms | 数据大小: %s | ", time.Now().Format("15:04:05"), action, st, rand.Intn(1000), formatBytes(int64(logSize))) // 填充随机数据到指定大小 data := make([]byte, logSize) copy(data, []byte(header)) // 填充可读的模拟数据 fillOffset := len(header) patterns := []string{ "user_id=%d, session=%x, ip=%d.%d.%d.%d, ", "query_time=%dms, rows=%d, cached=%v, ", "error_code=%d, retry_count=%d, ", "request_id=%x, trace_id=%x, ", } for fillOffset < logSize-100 { pattern := patterns[rand.Intn(len(patterns))] var chunk string switch pattern { case patterns[0]: chunk = fmt.Sprintf(pattern, rand.Intn(10000), rand.Intn(0xFFFFFF), rand.Intn(256), rand.Intn(256), rand.Intn(256), rand.Intn(256)) case patterns[1]: chunk = fmt.Sprintf(pattern, rand.Intn(1000), rand.Intn(10000), rand.Intn(2) == 1) case patterns[2]: chunk = fmt.Sprintf(pattern, rand.Intn(500), rand.Intn(5)) case patterns[3]: chunk = fmt.Sprintf(pattern, rand.Intn(0xFFFFFFFF), rand.Intn(0xFFFFFFFF)) } remaining := logSize - fillOffset if len(chunk) > remaining { chunk = chunk[:remaining] } copy(data[fillOffset:], []byte(chunk)) fillOffset += len(chunk) } // 写入日志 if _, err := seq.Write(topic, data); err != nil { logger.Error("写入日志失败", "error", err, "size", logSize) } else { logger.Info("写入日志", "topic", topic, "size", formatBytes(int64(logSize))) } } } func formatBytes(bytes int64) string { if bytes < 1024 { return fmt.Sprintf("%d B", bytes) } if bytes < 1024*1024 { return fmt.Sprintf("%.1f KB", float64(bytes)/1024) } return fmt.Sprintf("%.2f MB", float64(bytes)/1024/1024) } // 首页 func handleIndex(w http.ResponseWriter, r *http.Request) { html := `