381 lines
9.5 KiB
Go
381 lines
9.5 KiB
Go
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)
|
||
}
|
||
}
|
||
}()
|
||
}
|