2025-07-31 14:38:12 +08:00
|
|
|
|
import os
|
2025-08-11 17:42:52 +08:00
|
|
|
|
import logging
|
|
|
|
|
|
import datetime
|
|
|
|
|
|
import sys
|
|
|
|
|
|
import glob
|
|
|
|
|
|
import time
|
2025-08-11 17:54:14 +08:00
|
|
|
|
import traceback
|
2025-08-11 17:42:52 +08:00
|
|
|
|
from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler
|
|
|
|
|
|
from config.config import LOG_DIR, LOG_FILE, LOG_LEVEL, LOG_MAX_SIZE, LOG_BACKUP_COUNT, LOG_RETENTION_DAYS
|
|
|
|
|
|
|
|
|
|
|
|
from .url_censor import censor_url
|
2025-07-18 18:59:19 +08:00
|
|
|
|
|
2025-08-06 15:22:44 +08:00
|
|
|
|
class URLCensorFormatter(logging.Formatter):
|
|
|
|
|
|
"""自定义的日志格式化器,用于隐藏日志消息中的URL"""
|
|
|
|
|
|
|
|
|
|
|
|
def format(self, record):
|
|
|
|
|
|
# 先使用原始的format方法格式化日志
|
|
|
|
|
|
formatted_message = super().format(record)
|
2025-08-07 00:31:24 +08:00
|
|
|
|
# 临时禁用URL隐藏,直接返回原始消息
|
|
|
|
|
|
return formatted_message
|
|
|
|
|
|
# 然后对格式化后的消息进行URL审查(已禁用)
|
|
|
|
|
|
# return censor_url(formatted_message)
|
2025-08-06 15:22:44 +08:00
|
|
|
|
|
2025-07-18 18:59:19 +08:00
|
|
|
|
class Logger:
|
|
|
|
|
|
def __init__(self, filename, stream):
|
|
|
|
|
|
self.terminal = stream
|
2025-08-07 00:31:24 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 确保目录存在
|
|
|
|
|
|
log_dir = os.path.dirname(filename)
|
|
|
|
|
|
if log_dir and not os.path.exists(log_dir):
|
|
|
|
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
|
|
|
|
print(f"已创建日志目录: {log_dir}")
|
|
|
|
|
|
|
|
|
|
|
|
# 以追加模式打开,避免覆盖现有内容
|
|
|
|
|
|
self.log = open(filename, "a", encoding="utf-8", errors="replace")
|
|
|
|
|
|
self.log.write("\n\n--- New logging session started ---\n\n")
|
|
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
|
|
# 如果打开文件失败,记录错误并使用空的写入操作
|
|
|
|
|
|
print(f"Error opening log file {filename}: {e}")
|
|
|
|
|
|
self.log = None
|
2025-07-18 18:59:19 +08:00
|
|
|
|
|
|
|
|
|
|
def write(self, message):
|
2025-08-07 00:31:24 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 临时禁用URL隐藏
|
|
|
|
|
|
# censored_message = censor_url(message)
|
|
|
|
|
|
censored_message = message # 直接使用原始消息
|
|
|
|
|
|
self.terminal.write(censored_message)
|
|
|
|
|
|
if self.log:
|
|
|
|
|
|
self.log.write(censored_message)
|
|
|
|
|
|
self.flush()
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
# 发生错误时记录到控制台
|
|
|
|
|
|
self.terminal.write(f"Error writing to log: {e}\n")
|
2025-08-11 17:42:52 +08:00
|
|
|
|
|
2025-07-18 18:59:19 +08:00
|
|
|
|
def flush(self):
|
2025-08-07 00:31:24 +08:00
|
|
|
|
try:
|
|
|
|
|
|
self.terminal.flush()
|
|
|
|
|
|
if self.log:
|
|
|
|
|
|
self.log.flush()
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2025-07-18 18:59:19 +08:00
|
|
|
|
|
|
|
|
|
|
def close(self):
|
2025-08-07 00:31:24 +08:00
|
|
|
|
try:
|
|
|
|
|
|
if self.log:
|
|
|
|
|
|
self.log.write("\n--- Logging session ended ---\n")
|
|
|
|
|
|
self.log.close()
|
|
|
|
|
|
self.log = None
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
pass
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
2025-08-11 17:54:14 +08:00
|
|
|
|
# 增加异常钩子,确保未捕获的异常也会记录到日志文件中
|
|
|
|
|
|
def log_uncaught_exceptions(exc_type, exc_value, exc_traceback):
|
|
|
|
|
|
"""处理未捕获的异常,记录到日志中"""
|
|
|
|
|
|
if issubclass(exc_type, KeyboardInterrupt):
|
|
|
|
|
|
# 对于键盘中断,使用默认处理
|
|
|
|
|
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取主日志记录器
|
|
|
|
|
|
logger = logging.getLogger('main')
|
|
|
|
|
|
|
|
|
|
|
|
# 格式化异常信息
|
|
|
|
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
|
|
|
|
error_message = '未捕获的异常:\n' + ''.join(lines)
|
|
|
|
|
|
|
|
|
|
|
|
# 记录到日志中
|
|
|
|
|
|
logger.critical(error_message)
|
|
|
|
|
|
|
|
|
|
|
|
# 同时也显示在控制台
|
|
|
|
|
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
|
|
|
|
|
|
|
|
|
|
# 设置全局异常处理器
|
|
|
|
|
|
sys.excepthook = log_uncaught_exceptions
|
|
|
|
|
|
|
2025-08-11 17:42:52 +08:00
|
|
|
|
def cleanup_old_logs(retention_days=7):
|
|
|
|
|
|
"""清理超过指定天数的旧日志文件
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
retention_days: 日志保留天数,默认7天
|
|
|
|
|
|
"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
now = time.time()
|
|
|
|
|
|
cutoff = now - (retention_days * 86400) # 86400秒 = 1天
|
|
|
|
|
|
|
|
|
|
|
|
# 获取所有日志文件
|
|
|
|
|
|
log_files = glob.glob(os.path.join(LOG_DIR, "log-*.txt"))
|
|
|
|
|
|
|
|
|
|
|
|
for log_file in log_files:
|
|
|
|
|
|
# 检查文件修改时间
|
|
|
|
|
|
if os.path.getmtime(log_file) < cutoff:
|
|
|
|
|
|
try:
|
|
|
|
|
|
os.remove(log_file)
|
|
|
|
|
|
print(f"已删除过期日志: {log_file}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"删除日志文件失败 {log_file}: {e}")
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"清理旧日志文件时出错: {e}")
|
|
|
|
|
|
|
2025-07-31 14:38:12 +08:00
|
|
|
|
def setup_logger(name):
|
|
|
|
|
|
"""设置并返回一个命名的logger
|
|
|
|
|
|
|
2025-08-11 17:42:52 +08:00
|
|
|
|
使用统一的日志文件,添加日志轮转功能,实现自动清理过期日志
|
|
|
|
|
|
|
2025-07-31 14:38:12 +08:00
|
|
|
|
Args:
|
|
|
|
|
|
name: logger的名称
|
2025-08-11 17:42:52 +08:00
|
|
|
|
|
2025-07-31 14:38:12 +08:00
|
|
|
|
Returns:
|
|
|
|
|
|
logging.Logger: 配置好的logger对象
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 创建logger
|
|
|
|
|
|
logger = logging.getLogger(name)
|
|
|
|
|
|
|
|
|
|
|
|
# 避免重复添加处理器
|
|
|
|
|
|
if logger.hasHandlers():
|
|
|
|
|
|
return logger
|
2025-08-11 17:42:52 +08:00
|
|
|
|
|
|
|
|
|
|
# 根据配置设置日志级别
|
|
|
|
|
|
log_level = getattr(logging, LOG_LEVEL.upper(), logging.DEBUG)
|
|
|
|
|
|
logger.setLevel(log_level)
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
|
|
|
|
|
# 确保日志目录存在
|
2025-08-11 17:42:52 +08:00
|
|
|
|
os.makedirs(LOG_DIR, exist_ok=True)
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
2025-08-11 17:42:52 +08:00
|
|
|
|
# 清理过期日志文件
|
|
|
|
|
|
cleanup_old_logs(LOG_RETENTION_DAYS)
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
2025-08-11 17:42:52 +08:00
|
|
|
|
# 创建主日志文件的轮转处理器
|
2025-08-07 00:31:24 +08:00
|
|
|
|
try:
|
|
|
|
|
|
# 确保主日志文件目录存在
|
|
|
|
|
|
log_file_dir = os.path.dirname(LOG_FILE)
|
|
|
|
|
|
if log_file_dir and not os.path.exists(log_file_dir):
|
|
|
|
|
|
os.makedirs(log_file_dir, exist_ok=True)
|
|
|
|
|
|
print(f"已创建主日志目录: {log_file_dir}")
|
2025-08-11 17:42:52 +08:00
|
|
|
|
|
|
|
|
|
|
# 使用RotatingFileHandler实现日志轮转
|
|
|
|
|
|
main_file_handler = RotatingFileHandler(
|
|
|
|
|
|
LOG_FILE,
|
|
|
|
|
|
maxBytes=LOG_MAX_SIZE,
|
|
|
|
|
|
backupCount=LOG_BACKUP_COUNT,
|
|
|
|
|
|
encoding="utf-8"
|
|
|
|
|
|
)
|
|
|
|
|
|
main_file_handler.setLevel(log_level)
|
2025-08-07 00:31:24 +08:00
|
|
|
|
except (IOError, OSError) as e:
|
|
|
|
|
|
print(f"无法创建主日志文件处理器: {e}")
|
|
|
|
|
|
main_file_handler = None
|
|
|
|
|
|
|
2025-07-31 14:38:12 +08:00
|
|
|
|
# 创建控制台处理器
|
|
|
|
|
|
console_handler = logging.StreamHandler()
|
2025-08-11 17:42:52 +08:00
|
|
|
|
console_handler.setLevel(logging.INFO) # 控制台只显示INFO以上级别
|
|
|
|
|
|
|
|
|
|
|
|
# 创建更详细的格式器,包括模块名、文件名和行号
|
|
|
|
|
|
formatter = URLCensorFormatter('%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s')
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
2025-08-11 17:54:14 +08:00
|
|
|
|
# 设置处理器的格式化器
|
2025-07-31 14:38:12 +08:00
|
|
|
|
console_handler.setFormatter(formatter)
|
2025-08-07 00:31:24 +08:00
|
|
|
|
if main_file_handler:
|
|
|
|
|
|
main_file_handler.setFormatter(formatter)
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
|
|
|
|
|
# 添加处理器到logger
|
|
|
|
|
|
logger.addHandler(console_handler)
|
2025-08-07 00:31:24 +08:00
|
|
|
|
if main_file_handler:
|
|
|
|
|
|
logger.addHandler(main_file_handler)
|
2025-07-31 14:38:12 +08:00
|
|
|
|
|
2025-08-11 17:54:14 +08:00
|
|
|
|
# 确保异常可以被正确记录
|
|
|
|
|
|
logger.propagate = True
|
|
|
|
|
|
|
2025-07-31 14:38:12 +08:00
|
|
|
|
return logger
|