Files
urldb/utils/logger.go
2025-07-29 14:00:01 +08:00

362 lines
7.2 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 utils
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"runtime"
"sync"
"time"
)
// LogLevel 日志级别
type LogLevel int
const (
DEBUG LogLevel = iota
INFO
WARN
ERROR
FATAL
)
// String 返回日志级别的字符串表示
func (l LogLevel) String() string {
switch l {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARN"
case ERROR:
return "ERROR"
case FATAL:
return "FATAL"
default:
return "UNKNOWN"
}
}
// Logger 统一日志器
type Logger struct {
debugLogger *log.Logger
infoLogger *log.Logger
warnLogger *log.Logger
errorLogger *log.Logger
fatalLogger *log.Logger
file *os.File
mu sync.Mutex
config *LogConfig
}
// LogConfig 日志配置
type LogConfig struct {
LogDir string // 日志目录
LogLevel LogLevel // 日志级别
MaxFileSize int64 // 单个日志文件最大大小MB
MaxBackups int // 最大备份文件数
MaxAge int // 日志文件最大保留天数
EnableConsole bool // 是否启用控制台输出
EnableFile bool // 是否启用文件输出
EnableRotation bool // 是否启用日志轮转
}
// DefaultConfig 默认配置
func DefaultConfig() *LogConfig {
return &LogConfig{
LogDir: "logs",
LogLevel: INFO,
MaxFileSize: 100, // 100MB
MaxBackups: 5,
MaxAge: 30, // 30天
EnableConsole: true,
EnableFile: true,
EnableRotation: true,
}
}
var (
globalLogger *Logger
onceLogger sync.Once
)
// InitLogger 初始化全局日志器
func InitLogger(config *LogConfig) error {
var err error
onceLogger.Do(func() {
if config == nil {
config = DefaultConfig()
}
globalLogger, err = NewLogger(config)
})
return err
}
// GetLogger 获取全局日志器
func GetLogger() *Logger {
if globalLogger == nil {
InitLogger(nil)
}
return globalLogger
}
// NewLogger 创建新的日志器
func NewLogger(config *LogConfig) (*Logger, error) {
if config == nil {
config = DefaultConfig()
}
logger := &Logger{
config: config,
}
// 创建日志目录
if config.EnableFile {
if err := os.MkdirAll(config.LogDir, 0755); err != nil {
return nil, fmt.Errorf("创建日志目录失败: %v", err)
}
}
// 初始化日志文件
if err := logger.initLogFile(); err != nil {
return nil, err
}
// 初始化日志器
logger.initLoggers()
// 启动日志轮转检查
if config.EnableRotation {
go logger.startRotationCheck()
}
return logger, nil
}
// initLogFile 初始化日志文件
func (l *Logger) initLogFile() error {
if !l.config.EnableFile {
return nil
}
l.mu.Lock()
defer l.mu.Unlock()
// 关闭现有文件
if l.file != nil {
l.file.Close()
}
// 创建新的日志文件
logFile := filepath.Join(l.config.LogDir, fmt.Sprintf("app_%s.log", GetCurrentTime().Format("2006-01-02")))
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return fmt.Errorf("创建日志文件失败: %v", err)
}
l.file = file
return nil
}
// initLoggers 初始化各个级别的日志器
func (l *Logger) initLoggers() {
var writers []io.Writer
// 添加控制台输出
if l.config.EnableConsole {
writers = append(writers, os.Stdout)
}
// 添加文件输出
if l.config.EnableFile && l.file != nil {
writers = append(writers, l.file)
}
multiWriter := io.MultiWriter(writers...)
// 创建各个级别的日志器
l.debugLogger = log.New(multiWriter, "[DEBUG] ", log.LstdFlags)
l.infoLogger = log.New(multiWriter, "[INFO] ", log.LstdFlags)
l.warnLogger = log.New(multiWriter, "[WARN] ", log.LstdFlags)
l.errorLogger = log.New(multiWriter, "[ERROR] ", log.LstdFlags)
l.fatalLogger = log.New(multiWriter, "[FATAL] ", log.LstdFlags)
}
// log 内部日志方法
func (l *Logger) log(level LogLevel, format string, args ...interface{}) {
if level < l.config.LogLevel {
return
}
// 获取调用者信息
_, file, line, ok := runtime.Caller(2)
if !ok {
file = "unknown"
line = 0
}
// 提取文件名
fileName := filepath.Base(file)
// 格式化消息
message := fmt.Sprintf(format, args...)
// 添加调用位置信息
fullMessage := fmt.Sprintf("[%s:%d] %s", fileName, line, message)
switch level {
case DEBUG:
l.debugLogger.Println(fullMessage)
case INFO:
l.infoLogger.Println(fullMessage)
case WARN:
l.warnLogger.Println(fullMessage)
case ERROR:
l.errorLogger.Println(fullMessage)
case FATAL:
l.fatalLogger.Println(fullMessage)
os.Exit(1)
}
}
// Debug 调试日志
func (l *Logger) Debug(format string, args ...interface{}) {
l.log(DEBUG, format, args...)
}
// Info 信息日志
func (l *Logger) Info(format string, args ...interface{}) {
l.log(INFO, format, args...)
}
// Warn 警告日志
func (l *Logger) Warn(format string, args ...interface{}) {
l.log(WARN, format, args...)
}
// Error 错误日志
func (l *Logger) Error(format string, args ...interface{}) {
l.log(ERROR, format, args...)
}
// Fatal 致命错误日志
func (l *Logger) Fatal(format string, args ...interface{}) {
l.log(FATAL, format, args...)
}
// startRotationCheck 启动日志轮转检查
func (l *Logger) startRotationCheck() {
ticker := time.NewTicker(1 * time.Hour) // 每小时检查一次
defer ticker.Stop()
for range ticker.C {
l.checkRotation()
}
}
// checkRotation 检查是否需要轮转日志
func (l *Logger) checkRotation() {
if !l.config.EnableFile || l.file == nil {
return
}
// 检查文件大小
fileInfo, err := l.file.Stat()
if err != nil {
return
}
// 如果文件超过最大大小,进行轮转
if fileInfo.Size() > l.config.MaxFileSize*1024*1024 {
l.rotateLog()
}
// 清理旧日志文件
l.cleanOldLogs()
}
// rotateLog 轮转日志文件
func (l *Logger) rotateLog() {
l.mu.Lock()
defer l.mu.Unlock()
// 关闭当前文件
if l.file != nil {
l.file.Close()
}
// 重命名当前日志文件
currentLogFile := filepath.Join(l.config.LogDir, fmt.Sprintf("app_%s.log", GetCurrentTime().Format("2006-01-02")))
backupLogFile := filepath.Join(l.config.LogDir, fmt.Sprintf("app_%s_%s.log", GetCurrentTime().Format("2006-01-02"), GetCurrentTime().Format("15-04-05")))
if _, err := os.Stat(currentLogFile); err == nil {
os.Rename(currentLogFile, backupLogFile)
}
// 创建新的日志文件
l.initLogFile()
l.initLoggers()
}
// cleanOldLogs 清理旧日志文件
func (l *Logger) cleanOldLogs() {
if l.config.MaxAge <= 0 {
return
}
files, err := filepath.Glob(filepath.Join(l.config.LogDir, "app_*.log"))
if err != nil {
return
}
cutoffTime := GetCurrentTime().AddDate(0, 0, -l.config.MaxAge)
for _, file := range files {
fileInfo, err := os.Stat(file)
if err != nil {
continue
}
if fileInfo.ModTime().Before(cutoffTime) {
os.Remove(file)
}
}
}
// Close 关闭日志器
func (l *Logger) Close() error {
l.mu.Lock()
defer l.mu.Unlock()
if l.file != nil {
return l.file.Close()
}
return nil
}
// 全局便捷函数
func Debug(format string, args ...interface{}) {
GetLogger().Debug(format, args...)
}
func Info(format string, args ...interface{}) {
GetLogger().Info(format, args...)
}
func Warn(format string, args ...interface{}) {
GetLogger().Warn(format, args...)
}
func Error(format string, args ...interface{}) {
GetLogger().Error(format, args...)
}
func Fatal(format string, args ...interface{}) {
GetLogger().Fatal(format, args...)
}