package logger import ( "compress/gzip" "fmt" "io" "os" "path/filepath" "sync" "time" ) const ( MaxFileSize = 1 * 1024 * 1024 // 1MB LogDir = "logs" ) // TransactionLogger 交易日志记录器 type TransactionLogger struct { mu sync.Mutex files map[string]*logFile // address -> logFile logDir string } // logFile 单个日志文件 type logFile struct { file *os.File size int64 address string logDir string mu sync.Mutex } var ( txLogger *TransactionLogger once sync.Once ) // InitTransactionLogger 初始化交易日志系统 func InitTransactionLogger(logDir string) error { var err error once.Do(func() { if logDir == "" { logDir = LogDir } // 创建日志目录 err = os.MkdirAll(logDir, 0755) if err != nil { return } txLogger = &TransactionLogger{ files: make(map[string]*logFile), logDir: logDir, } }) return err } // getOrCreateLogFile 获取或创建日志文件 func (tl *TransactionLogger) getOrCreateLogFile(address string) (*logFile, error) { tl.mu.Lock() defer tl.mu.Unlock() // 如果已存在,返回现有的 if lf, exists := tl.files[address]; exists { return lf, nil } // 创建新的日志文件 lf := &logFile{ address: address, logDir: tl.logDir, } // 打开或创建文件 filePath := filepath.Join(tl.logDir, fmt.Sprintf("%s.log", address)) file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return nil, fmt.Errorf("创建日志文件失败: %w", err) } // 获取当前文件大小 info, err := file.Stat() if err != nil { file.Close() return nil, fmt.Errorf("获取文件信息失败: %w", err) } lf.file = file lf.size = info.Size() tl.files[address] = lf return lf, nil } // write 写入日志 func (lf *logFile) write(content string) error { lf.mu.Lock() defer lf.mu.Unlock() // 检查是否需要轮转 if lf.size >= MaxFileSize { if err := lf.rotate(); err != nil { return fmt.Errorf("日志轮转失败: %w", err) } } // 写入内容 n, err := lf.file.WriteString(content + "\n") if err != nil { return fmt.Errorf("写入日志失败: %w", err) } lf.size += int64(n) // 立即刷新到磁盘 lf.file.Sync() return nil } // rotate 日志轮转:压缩当前文件,创建新文件 func (lf *logFile) rotate() error { // 关闭当前文件 if lf.file != nil { lf.file.Close() } // 生成备份文件名(带时间戳) timestamp := time.Now().Format("20060102_150405") oldPath := filepath.Join(lf.logDir, fmt.Sprintf("%s.log", lf.address)) backupPath := filepath.Join(lf.logDir, fmt.Sprintf("%s_%s.log", lf.address, timestamp)) // 重命名当前文件 if err := os.Rename(oldPath, backupPath); err != nil { return fmt.Errorf("重命名日志文件失败: %w", err) } // 压缩备份文件 go func() { if err := compressFile(backupPath); err != nil { fmt.Printf("⚠️ 压缩日志文件失败 %s: %v\n", backupPath, err) } else { // 删除原文件 os.Remove(backupPath) } }() // 创建新文件 file, err := os.OpenFile(oldPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("创建新日志文件失败: %w", err) } lf.file = file lf.size = 0 return nil } // compressFile 压缩文件 func compressFile(filePath string) error { // 打开原文件 srcFile, err := os.Open(filePath) if err != nil { return err } defer srcFile.Close() // 创建压缩文件 gzPath := filePath + ".gz" gzFile, err := os.Create(gzPath) if err != nil { return err } defer gzFile.Close() // 创建 gzip writer gzWriter := gzip.NewWriter(gzFile) defer gzWriter.Close() // 复制数据 _, err = io.Copy(gzWriter, srcFile) return err } // LogTopup 记录充值消息 func LogTopup(toAddress string, status string, amount float64, txHash string, blockHeight uint64) { if txLogger == nil { return } lf, err := txLogger.getOrCreateLogFile(toAddress) if err != nil { fmt.Printf("⚠️ 获取日志文件失败: %v\n", err) return } timestamp := time.Now().Format("2006-01-02 15:04:05") content := fmt.Sprintf("%s [topup]-[%s] | 金额: %.6f | 交易哈希: %s | 区块高度: %d | ToAddress: %s", timestamp, status, amount, txHash, blockHeight, toAddress) if err := lf.write(content); err != nil { fmt.Printf("⚠️ 写入日志失败: %v\n", err) } } // LogWithdraw 记录提现消息 func LogWithdraw(toAddress string, status string, amount float64, fromAddress string, txHash string, blockHeight uint64) { if txLogger == nil { return } // 使用 toAddress 作为文件名 lf, err := txLogger.getOrCreateLogFile(toAddress) if err != nil { fmt.Printf("⚠️ 获取日志文件失败: %v\n", err) return } timestamp := time.Now().Format("2006-01-02 15:04:05") content := fmt.Sprintf("%s [withdraw]-[%s] | 金额: %.6f | FromAddress: %s | ToAddress: %s | 交易哈希: %s | 区块高度: %d", timestamp, status, amount, fromAddress, toAddress, txHash, blockHeight) if err := lf.write(content); err != nil { fmt.Printf("⚠️ 写入日志失败: %v\n", err) } } // LogPay 记录支付消息 func LogPay(toAddress string, status string, amount float64, fromAddress string, txHash string, blockHeight uint64, orderId string, queueId string) { if txLogger == nil { return } // 使用 toAddress 作为文件名 lf, err := txLogger.getOrCreateLogFile(toAddress) if err != nil { fmt.Printf("⚠️ 获取日志文件失败: %v\n", err) return } timestamp := time.Now().Format("2006-01-02 15:04:05") content := fmt.Sprintf("%s [pay]-[%s] | 金额: %.6f | FromAddress: %s | ToAddress: %s | 交易哈希: %s | 区块高度: %d | OrderId: %s | QueueId: %s", timestamp, status, amount, fromAddress, toAddress, txHash, blockHeight, orderId, queueId) if err := lf.write(content); err != nil { fmt.Printf("⚠️ 写入日志失败: %v\n", err) } } // Close 关闭所有日志文件 func CloseTransactionLogger() { if txLogger == nil { return } txLogger.mu.Lock() defer txLogger.mu.Unlock() for _, lf := range txLogger.files { if lf.file != nil { lf.file.Close() } } }