Files
m2pool_payment/internal/logger/transaction_logger.go
2025-10-31 13:46:58 +08:00

276 lines
6.0 KiB
Go

package logger
import (
"compress/gzip"
"encoding/json"
"fmt"
"io"
message "m2pool-payment/internal/msg"
"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(status string, fromAddress string, queueId string, transactions map[string]*message.PayData_resp) {
if txLogger == nil {
return
}
// 使用 toAddress 作为文件名
lf, err := txLogger.getOrCreateLogFile(fromAddress)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t, err := json.Marshal(transactions)
if err != nil {
fmt.Println("Error marshalling to JSON:", err)
return
}
timestamp := time.Now().Format("2006-01-02 15:04:05")
content := fmt.Sprintf("%s [pay]-[%s] | FromAddress: %s | QueueId: %s | Transactions: %v",
timestamp, status, fromAddress, queueId, string(t))
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()
}
}
}