Files

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