Files
m2pool_payment/internal/logger/transaction_logger.go
2025-11-18 11:10:16 +08:00

484 lines
12 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 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(fromAddress string, status string, amount float64, toAddress string, txHash string, blockHeight uint64) {
if txLogger == nil {
return
}
// 使用 toAddress 作为文件名
lf, err := txLogger.getOrCreateLogFile(fromAddress)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
timestamp := time.Now().Format("2006-01-02 15:04:05")
content := fmt.Sprintf("%s [提现]-[%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) {
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",
timestamp, status, fromAddress, queueId)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// 记录当前监听的钱包和所有消息
func LogETHNode(msg string) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile("ethnode")
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
timestamp := time.Now().Format("2006-01-02 15:04:05")
content := fmt.Sprintf("[ETH-NODE:%s]: %s", timestamp, msg)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// =============================== RMQ <-> Listen 通信日志 ===============================
// LogRmqToListenTopupReq 记录 RMQ -> Listen 充值请求
// 使用 address 作为文件名
func LogRmqToListenTopupReq(queueId, chain, symbol, address string, timestamp uint64) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(address)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
// TopupReq 没有 fromAddress使用 "-" 代替toAddress 是 addressamount 为 0
content := fmt.Sprintf("[TopupReq]: %s--%s-%s-%s-0",
t, address, chain, symbol)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogRmqToListenWithdrawReq 记录 RMQ -> Listen 提现请求
// 使用 fromAddress 作为文件名
func LogRmqToListenWithdrawReq(queueId, chain, symbol, fromAddress, toAddress string, amount, fee float64, timestamp uint64) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(fromAddress)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
content := fmt.Sprintf("[WithdrawReq]: %s-%s-%s-%s-%s-%.6f",
t, fromAddress, toAddress, chain, symbol, amount)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogRmqToListenPayReq 记录 RMQ -> Listen 支付请求
// 使用 fromAddress 作为文件名
func LogRmqToListenPayReq(queueId, chain, symbol, fromAddress, toAddress string, amount, fee float64, timestamp uint64) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(fromAddress)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
content := fmt.Sprintf("[PayReq]: %s-%s-%s-%s-%s-%.6f",
t, fromAddress, toAddress, chain, symbol, amount)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogRmqToListenRemoveReq 记录 RMQ -> Listen 移除监听请求
// 使用 address 作为文件名
func LogRmqToListenRemoveReq(queueId string, msgType int, chain, symbol, address string, timestamp uint64) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(address)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
// RemoveReq 没有 fromAddresstoAddress 是 addressamount 为 0
content := fmt.Sprintf("[RemoveReq]: %s--%s-%s-%s-0",
t, address, chain, symbol)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogListenToRmqTopupResp 记录 Listen -> RMQ 充值响应
// 使用 address 作为文件名
func LogListenToRmqTopupResp(queueId, chain, symbol, address, fromAddress, txHash string, amount float64, blockHeight uint64, status int) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(address)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
// TopupResp 中 address 是目标地址toAddressfromAddress 是来源地址
content := fmt.Sprintf("[TopupResp]: %s-%s-%s-%s-%s-%.6f",
t, fromAddress, address, chain, symbol, amount)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogListenToRmqWithdrawResp 记录 Listen -> RMQ 提现响应
// 使用 fromAddress 作为文件名
func LogListenToRmqWithdrawResp(queueId, chain, symbol, fromAddress, toAddress, txHash string, amount, fee float64, blockHeight uint64, status int) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(fromAddress)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
content := fmt.Sprintf("[WithdrawResp]: %s-%s-%s-%s-%s-%.6f",
t, fromAddress, toAddress, chain, symbol, amount)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogListenToRmqPayResp 记录 Listen -> RMQ 支付响应
// 使用 fromAddress 作为文件名
func LogListenToRmqPayResp(queueId, chain, symbol, fromAddress, toAddress, txHash string, amount, fee float64, blockHeight uint64, status int) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(fromAddress)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
content := fmt.Sprintf("[PayResp]: %s-%s-%s-%s-%s-%.6f",
t, fromAddress, toAddress, chain, symbol, amount)
if err := lf.write(content); err != nil {
fmt.Printf("⚠️ 写入日志失败: %v\n", err)
}
}
// LogListenToRmqRemoveResp 记录 Listen -> RMQ 移除监听响应
// 使用 address 作为文件名
func LogListenToRmqRemoveResp(queueId string, msgType int, chain, symbol, address string, status int) {
if txLogger == nil {
return
}
lf, err := txLogger.getOrCreateLogFile(address)
if err != nil {
fmt.Printf("⚠️ 获取日志文件失败: %v\n", err)
return
}
t := time.Now().Format("2006-01-02 15:04:05")
// 格式:[msg-Type]: time-fromaddress-toaddress-chain-symbol-amount
// RemoveResp 没有 fromAddresstoAddress 是 addressamount 为 0
content := fmt.Sprintf("[RemoveResp]: %s--%s-%s-%s-0",
t, address, chain, symbol)
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()
}
}
}