Initial qiwei secondary development handoff

This commit is contained in:
2026-06-23 21:11:20 +08:00
commit 858cb68f4f
207 changed files with 52782 additions and 0 deletions

380
logger/logger.go Normal file
View File

@@ -0,0 +1,380 @@
package logger
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"sync"
"time"
)
// LogEntry 定义日志条目结构体实际实现在operation_logger.go
// 这里保留类型定义以保持向后兼容性
type LogEntry struct {
ID int64 `json:"id"`
Time string `json:"time"`
Source string `json:"source"`
Type string `json:"type"`
Content string `json:"content"`
Duration int64 `json:"duration"`
}
// LogLevel 定义日志级别
type LogLevel int
const (
LevelDebug LogLevel = iota
LevelInfo
LevelWarning
LevelError
)
// Logger 日志器结构体
type Logger struct {
Logger *log.Logger
LogLevel LogLevel
LogEnabled bool
mu sync.Mutex
// 日志文件相关字段
logFile *os.File
logDir string
exeName string
maxFileSize int64
currentSize int64
Lock *sync.Mutex
}
// GetLogDir 获取日志目录路径
func (l *Logger) GetLogDir() string {
return l.logDir
}
// 确保日志文件大小不超过限制
func ensureLogFileSize(logDir string, baseLogName string) string {
// 尝试最多10次找到一个可用的文件名
for i := 0; i < 10; i++ {
var logFileName string
if i == 0 {
logFileName = baseLogName
} else {
logFileName = fmt.Sprintf("%s_%d.txt", baseLogName[:len(baseLogName)-4], i)
}
logFilePath := filepath.Join(logDir, logFileName)
// 检查文件是否存在以及大小
fileInfo, err := os.Stat(logFilePath)
if err != nil || fileInfo.Size() < 5*1024*1024 { // 5MB
return logFilePath
}
}
// 如果所有尝试都失败,返回一个默认文件名
return filepath.Join(logDir, fmt.Sprintf("%s_default.txt", baseLogName[:len(baseLogName)-4]))
}
// NewLogger 创建一个新的日志器
func NewLogger(exeName string, enabled bool, level LogLevel) (*Logger, error) {
// 获取程序路径
exePath, err := os.Executable()
var logDir string
if err != nil {
// 如果获取程序路径失败,使用临时目录作为备选
logDir = filepath.Join(os.TempDir(), fmt.Sprintf("%s_logs", exeName))
} else {
// 使用程序所在目录的Log子目录
exeDir := filepath.Dir(exePath)
logDir = filepath.Join(exeDir, "Log")
}
// 创建基础日志目录
if err := os.MkdirAll(logDir, 0755); err != nil {
return nil, fmt.Errorf("无法创建日志目录: %v", err)
}
// 获取当前日期用于创建日期子目录格式2006-01-02
today := time.Now().Format("2006-01-02")
dateDir := filepath.Join(logDir, today)
// 创建日期子目录
if err := os.MkdirAll(dateDir, 0755); err != nil {
return nil, fmt.Errorf("无法创建日期子目录: %v", err)
}
// 生成当前日期时间的日志文件名格式appName_YYYYMMDD_HHmmss.txt
timeStr := time.Now().Format("20060102_150405")
baseLogName := fmt.Sprintf("%s_%s.txt", exeName, timeStr)
logFilePath := filepath.Join(dateDir, baseLogName)
// 确保新文件不会超过大小限制
logFilePath = ensureLogFileSize(dateDir, baseLogName)
// 打开日志文件
logFile, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, fmt.Errorf("无法打开日志文件: %v", err)
}
// 获取文件信息以初始化当前大小
fileInfo, err := logFile.Stat()
if err != nil {
return nil, fmt.Errorf("无法获取文件信息: %v", err)
}
// 创建logger
logger := log.New(logFile, "", log.LstdFlags)
// 返回日志器
return &Logger{
Logger: logger,
LogLevel: level,
LogEnabled: enabled,
logFile: logFile,
logDir: logDir,
exeName: exeName,
maxFileSize: 5 * 1024 * 1024, // 5MB
currentSize: fileInfo.Size(),
Lock: &sync.Mutex{},
}, nil
}
// Close 关闭日志文件
func (l *Logger) Close() {
l.Lock.Lock()
defer l.Lock.Unlock()
if l.logFile != nil {
l.logFile.Close()
l.logFile = nil
}
}
// Debug 记录调试日志
func (l *Logger) Debug(format string, v ...interface{}) {
if l == nil {
return
}
if l.LogEnabled && l.LogLevel <= LevelDebug {
l.log("[调试]", format, v...)
}
}
// Info 记录信息日志
func (l *Logger) Info(format string, v ...interface{}) {
if l == nil {
return
}
if l.LogEnabled && l.LogLevel <= LevelInfo {
l.log("[信息]", format, v...)
}
}
// Warn 记录警告日志
func (l *Logger) Warn(format string, v ...interface{}) {
if l == nil {
return
}
if l.LogEnabled && l.LogLevel <= LevelWarning {
l.log("[警告]", format, v...)
}
}
// Error 记录错误日志
func (l *Logger) Error(format string, v ...interface{}) {
if l == nil {
return
}
if l.LogEnabled && l.LogLevel <= LevelError {
l.log("[错误]", format, v...)
}
}
// log 实际的日志记录函数
func (l *Logger) log(level string, format string, v ...interface{}) {
if l == nil || l.Lock == nil || l.Logger == nil {
return
}
l.Lock.Lock()
defer l.Lock.Unlock()
// 格式化日志消息
message := fmt.Sprintf(format, v...)
fullMessage := fmt.Sprintf("%s %s", level, message)
// 写入日志
l.Logger.Println(fullMessage)
// 更新当前文件大小估计
// 注意:这只是估计值,实际大小可能不同
l.currentSize += int64(len(fullMessage) + 20) // 20是为时间戳等额外内容预留的空间
// 检查是否需要创建新文件
if l.LogEnabled && l.currentSize >= l.maxFileSize {
// 关闭当前文件
if l.logFile != nil {
l.logFile.Close()
}
// 创建新的日志文件
today := time.Now().Format("2006-01-02")
dateDir := filepath.Join(l.logDir, today)
// 创建日期子目录
if err := os.MkdirAll(dateDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "无法创建日期子目录: %v\n", err)
dateDir = l.logDir // 如果创建失败,回退到基础日志目录
}
timeStr := time.Now().Format("20060102_150405")
baseLogName := fmt.Sprintf("%s_%s.txt", l.exeName, timeStr)
logFilePath := filepath.Join(dateDir, baseLogName)
// 确保新文件不会超过大小限制
logFilePath = ensureLogFileSize(dateDir, baseLogName)
// 打开新的日志文件
var err error
l.logFile, err = os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
// 重新设置logger的输出
l.Logger.SetOutput(l.logFile)
// 重置当前大小
l.currentSize = 0
} else {
// 如果无法打开新文件,输出错误到标准错误
fmt.Fprintf(os.Stderr, "无法创建新的日志文件: %v\n", err)
}
}
}
// SetEnabled 设置日志总开关
func (l *Logger) SetEnabled(enabled bool) {
l.Lock.Lock()
defer l.Lock.Unlock()
if l.LogEnabled == enabled {
return
}
l.LogEnabled = enabled
if enabled {
// 如果启用日志,但文件已关闭,重新打开
if l.logFile == nil {
today := time.Now().Format("2006-01-02")
dateDir := filepath.Join(l.logDir, today)
// 创建日期子目录
if err := os.MkdirAll(dateDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "无法创建日期子目录: %v\n", err)
dateDir = l.logDir // 如果创建失败,回退到基础日志目录
}
timeStr := time.Now().Format("20060102_150405")
baseLogName := fmt.Sprintf("%s_%s.txt", l.exeName, timeStr)
logFilePath := filepath.Join(dateDir, baseLogName)
// 确保新文件不会超过大小限制
logFilePath = ensureLogFileSize(dateDir, baseLogName)
// 打开日志文件
var err error
l.logFile, err = os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err == nil {
// 设置logger的输出
l.Logger.SetOutput(l.logFile)
// 获取文件当前大小
fileInfo, err := l.logFile.Stat()
if err == nil {
l.currentSize = fileInfo.Size()
}
} else {
// 如果无法打开文件,输出错误到标准错误
fmt.Fprintf(os.Stderr, "无法打开日志文件: %v\n", err)
}
}
} else {
// 如果禁用日志,将输出重定向到/dev/null
l.Logger.SetOutput(io.Discard)
}
}
// SetLogLevel 设置日志级别
func (l *Logger) SetLogLevel(level LogLevel) {
l.Lock.Lock()
defer l.Lock.Unlock()
l.LogLevel = level
}
// CleanOldLogs 清理指定天数之前的旧日志文件
func CleanOldLogs(logDir string, daysToKeep int) error {
if daysToKeep <= 0 {
daysToKeep = 30 // 默认保留30天
}
cutoffTime := time.Now().AddDate(0, 0, -daysToKeep)
// 遍历日志目录下的所有子目录和文件
err := filepath.Walk(logDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // 跳过错误,继续处理
}
// 跳过目录
if info.IsDir() {
return nil
}
// 只处理.txt和.log文件
ext := strings.ToLower(filepath.Ext(path))
if ext != ".txt" && ext != ".log" {
return nil
}
// 检查文件修改时间
if info.ModTime().Before(cutoffTime) {
// 删除旧日志文件
if removeErr := os.Remove(path); removeErr != nil {
// 记录错误但不中断清理过程
fmt.Printf("删除旧日志文件失败: %s, 错误: %v\n", path, removeErr)
} else {
fmt.Printf("已删除旧日志文件: %s\n", path)
}
}
return nil
})
return err
}
// StartLogCleanupScheduler 启动日志清理定时器
func StartLogCleanupScheduler(logDir string, daysToKeep int, checkInterval time.Duration) {
if daysToKeep <= 0 {
daysToKeep = 30
}
if checkInterval <= 0 {
checkInterval = 24 * time.Hour // 默认每天检查一次
}
go func() {
ticker := time.NewTicker(checkInterval)
defer ticker.Stop()
// 立即执行一次清理
if err := CleanOldLogs(logDir, daysToKeep); err != nil {
fmt.Printf("日志清理失败: %v\n", err)
}
// 定时清理
for range ticker.C {
if err := CleanOldLogs(logDir, daysToKeep); err != nil {
fmt.Printf("日志清理失败: %v\n", err)
}
}
}()
}