diff --git a/app/log.py b/app/log.py index 6a65cf7a..008a1eda 100644 --- a/app/log.py +++ b/app/log.py @@ -5,29 +5,69 @@ from pathlib import Path from typing import Dict, Any import click +from pydantic import BaseSettings -from app.core.config import settings +from app.utils.system import SystemUtils -# 日志级别颜色 + +class LogSettings(BaseSettings): + """ + 日志设置 + """ + # 是否为调试模式 + DEBUG: bool = False + # 日志级别(DEBUG、INFO、WARNING、ERROR等) + LOG_LEVEL: str = "INFO" + # 日志文件最大大小(单位:MB) + LOG_MAX_FILE_SIZE: int = 5 + # 备份的日志文件数量 + LOG_BACKUP_COUNT: int = 3 + # 控制台日志格式 + LOG_CONSOLE_FORMAT: str = "%(leveltext)s%(message)s" + # 文件日志格式 + LOG_FILE_FORMAT: str = "【%(levelname)s】%(asctime)s - %(message)s" + + @property + def LOG_PATH(self): + """ + 获取日志存储路径 + """ + return SystemUtils.get_config_path() / "logs" + + @property + def LOG_MAX_FILE_SIZE_BYTES(self): + """ + 将日志文件大小转换为字节(MB -> Bytes) + """ + return self.LOG_MAX_FILE_SIZE * 1024 * 1024 + + class Config: + case_sensitive = True + env_file = SystemUtils.get_config_path() / "app.env" + env_file_encoding = "utf-8" + + +# 日志级别颜色映射 level_name_colors = { logging.DEBUG: lambda level_name: click.style(str(level_name), fg="cyan"), logging.INFO: lambda level_name: click.style(str(level_name), fg="green"), logging.WARNING: lambda level_name: click.style(str(level_name), fg="yellow"), logging.ERROR: lambda level_name: click.style(str(level_name), fg="red"), - logging.CRITICAL: lambda level_name: click.style( - str(level_name), fg="bright_red" - ), + logging.CRITICAL: lambda level_name: click.style(str(level_name), fg="bright_red"), } class CustomFormatter(logging.Formatter): """ - 定义日志输出格式 + 自定义日志输出格式 """ + def __init__(self, fmt=None): + super().__init__(fmt) + def format(self, record): - seperator = " " * (8 - len(record.levelname)) - record.leveltext = level_name_colors[record.levelno](record.levelname + ":") + seperator + separator = " " * (8 - len(record.levelname)) + record.leveltext = level_name_colors[record.levelno](record.levelname + ":") + separator return super().format(record) @@ -35,15 +75,16 @@ class LoggerManager: """ 日志管理 """ - # 管理所有的Logger + # 管理所有的 Logger _loggers: Dict[str, Any] = {} - # 默认日志文件 + # 默认日志文件名称 _default_log_file = "moviepilot.log" @staticmethod def __get_caller(): """ - 获取调用者的文件名称与插件名称(如果是插件调用内置的模块, 也能写入到插件日志文件中) + 获取调用者的文件名称与插件名称 + 如果是插件调用内置的模块, 也能写入到插件日志文件中 """ # 调用者文件名称 caller_name = None @@ -79,18 +120,17 @@ class LoggerManager: 设置日志 log_file:日志文件相对路径 """ - log_file_path = settings.LOG_PATH / log_file - if not log_file_path.parent.exists(): - log_file_path.parent.mkdir(parents=True, exist_ok=True) + log_file_path = log_settings.LOG_PATH / log_file + log_file_path.parent.mkdir(parents=True, exist_ok=True) # 创建新实例 _logger = logging.getLogger(log_file_path.stem) - # DEBUG - if settings.DEBUG: + if log_settings.DEBUG: _logger.setLevel(logging.DEBUG) else: - _logger.setLevel(logging.INFO) + loglevel = getattr(logging, log_settings.LOG_LEVEL.upper(), logging.INFO) + _logger.setLevel(loglevel) # 移除已有的 handler,避免重复添加 for handler in _logger.handlers: @@ -98,18 +138,20 @@ class LoggerManager: # 终端日志 console_handler = logging.StreamHandler() - console_formatter = CustomFormatter(f"%(leveltext)s%(message)s") + console_formatter = CustomFormatter(log_settings.LOG_CONSOLE_FORMAT) console_handler.setFormatter(console_formatter) _logger.addHandler(console_handler) # 文件日志 - file_handler = RotatingFileHandler(filename=log_file_path, - mode='w', - maxBytes=5 * 1024 * 1024, - backupCount=3, - encoding='utf-8') - file_formater = CustomFormatter(f"【%(levelname)s】%(asctime)s - %(message)s") - file_handler.setFormatter(file_formater) + file_handler = RotatingFileHandler( + filename=log_file_path, + mode="a", + maxBytes=log_settings.LOG_MAX_FILE_SIZE_BYTES, + backupCount=log_settings.LOG_BACKUP_COUNT, + encoding="utf-8" + ) + file_formatter = CustomFormatter(log_settings.LOG_FILE_FORMAT) + file_handler.setFormatter(file_formatter) _logger.addHandler(file_handler) return _logger @@ -120,7 +162,6 @@ class LoggerManager: :param method: 日志方法 :param msg: 日志信息 """ - # 获取调用者文件名和插件名 caller_name, plugin_name = self.__get_caller() # 区分插件日志 @@ -138,45 +179,48 @@ class LoggerManager: self._loggers[logfile] = _logger # 调用logger的方法打印日志 if hasattr(_logger, method): - method = getattr(_logger, method) - method(f"{caller_name} - {msg}", *args, **kwargs) + log_method = getattr(_logger, method) + log_method(f"{caller_name} - {msg}", *args, **kwargs) def info(self, msg: str, *args, **kwargs): """ - 重载info方法 + 输出信息级别日志 """ self.logger("info", msg, *args, **kwargs) def debug(self, msg: str, *args, **kwargs): """ - 重载debug方法 + 输出调试级别日志 """ self.logger("debug", msg, *args, **kwargs) def warning(self, msg: str, *args, **kwargs): """ - 重载warning方法 + 输出警告级别日志 """ self.logger("warning", msg, *args, **kwargs) def warn(self, msg: str, *args, **kwargs): """ - 重载warn方法 + 输出警告级别日志(兼容) """ self.logger("warning", msg, *args, **kwargs) def error(self, msg: str, *args, **kwargs): """ - 重载error方法 + 输出错误级别日志 """ self.logger("error", msg, *args, **kwargs) def critical(self, msg: str, *args, **kwargs): """ - 重载critical方法 + 输出严重错误级别日志 """ self.logger("critical", msg, *args, **kwargs) -# 初始化公共日志 +# 实例化日志设置 +log_settings = LogSettings() + +# 初始化日志管理 logger = LoggerManager() diff --git a/app/utils/system.py b/app/utils/system.py index 59513cfd..ae6593f9 100644 --- a/app/utils/system.py +++ b/app/utils/system.py @@ -471,3 +471,15 @@ class SystemUtils: if os.name == "nt": return src.drive == dest.drive return os.stat(src).st_dev == os.stat(dest).st_dev + + @staticmethod + def get_config_path() -> Path: + """ + 获取配置路径 + """ + if SystemUtils.is_docker(): + return Path("/config") + elif SystemUtils.is_frozen(): + return Path(sys.executable).parent / "config" + else: + return Path(__file__).parents[2] / "config" diff --git a/config/app.env b/config/app.env index 812e6a85..6529b9c0 100644 --- a/config/app.env +++ b/config/app.env @@ -7,6 +7,8 @@ HOST=0.0.0.0 DEBUG=false # 是否开发模式,打开后后台服务将不会启动 DEV=false +# 日志级别(DEBUG、INFO、WARNING、ERROR等),当DEBUG=true时,此配置项将被忽略,日志级别始终为DEBUG +LOG_LEVEL=INFO # 数据库连接池的大小,可适当降低如10-50以减少I/O压力 DB_POOL_SIZE=100 # 数据库连接池最大溢出连接数,可适当降低如0以减少I/O压力