Spaces:
Paused
Paused
| # ============================================================================== | |
| # Copyright (C) 2021 Evil0ctal | |
| # | |
| # This file is part of the Douyin_TikTok_Download_API project. | |
| # | |
| # This project is licensed under the Apache License 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at: | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # ============================================================================== | |
| # __ | |
| # /> フ | |
| # | _ _ l | |
| # /` ミ_xノ | |
| # / | Feed me Stars ⭐ ️ | |
| # / ヽ ノ | |
| # │ | | | | |
| # / ̄| | | | | |
| # | ( ̄ヽ__ヽ_)__) | |
| # \二つ | |
| # ============================================================================== | |
| # | |
| # Contributor Link: | |
| # - https://github.com/Evil0ctal | |
| # - https://github.com/Johnserf-Seed | |
| # | |
| # ============================================================================== | |
| import threading | |
| import time | |
| import logging | |
| import datetime | |
| from pathlib import Path | |
| from rich.logging import RichHandler | |
| from logging.handlers import TimedRotatingFileHandler | |
| class Singleton(type): | |
| _instances = {} # 存储实例的字典 | |
| _lock: threading.Lock = threading.Lock() # 线程锁 | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| def __call__(cls, *args, **kwargs): | |
| """ | |
| 重写默认的类实例化方法。当尝试创建类的一个新实例时,此方法将被调用。 | |
| 如果已经有一个与参数匹配的实例存在,则返回该实例;否则创建一个新实例。 | |
| """ | |
| key = (cls, args, frozenset(kwargs.items())) | |
| with cls._lock: | |
| if key not in cls._instances: | |
| instance = super().__call__(*args, **kwargs) | |
| cls._instances[key] = instance | |
| return cls._instances[key] | |
| def reset_instance(cls, *args, **kwargs): | |
| """ | |
| 重置指定参数的实例。这只是从 _instances 字典中删除实例的引用, | |
| 并不真正删除该实例。如果其他地方仍引用该实例,它仍然存在且可用。 | |
| """ | |
| key = (cls, args, frozenset(kwargs.items())) | |
| with cls._lock: | |
| if key in cls._instances: | |
| del cls._instances[key] | |
| class LogManager(metaclass=Singleton): | |
| def __init__(self): | |
| if getattr(self, "_initialized", False): # 防止重复初始化 | |
| return | |
| self.logger = logging.getLogger("Douyin_TikTok_Download_API_Crawlers") | |
| self.logger.setLevel(logging.INFO) | |
| self.log_dir = None | |
| self._initialized = True | |
| def setup_logging(self, level=logging.INFO, log_to_console=False, log_path=None): | |
| self.logger.handlers.clear() | |
| self.logger.setLevel(level) | |
| if log_to_console: | |
| ch = RichHandler( | |
| show_time=False, | |
| show_path=False, | |
| markup=True, | |
| keywords=(RichHandler.KEYWORDS or []) + ["STREAM"], | |
| rich_tracebacks=True, | |
| ) | |
| ch.setFormatter(logging.Formatter("{message}", style="{", datefmt="[%X]")) | |
| self.logger.addHandler(ch) | |
| if log_path: | |
| self.log_dir = Path(log_path) | |
| self.ensure_log_dir_exists(self.log_dir) | |
| log_file_name = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S.log") | |
| log_file = self.log_dir.joinpath(log_file_name) | |
| fh = TimedRotatingFileHandler( | |
| log_file, when="midnight", interval=1, backupCount=99, encoding="utf-8" | |
| ) | |
| fh.setFormatter( | |
| logging.Formatter( | |
| "%(asctime)s - %(name)s - %(levelname)s - %(message)s" | |
| ) | |
| ) | |
| self.logger.addHandler(fh) | |
| def ensure_log_dir_exists(log_path: Path): | |
| log_path.mkdir(parents=True, exist_ok=True) | |
| def clean_logs(self, keep_last_n=10): | |
| """保留最近的n个日志文件并删除其他文件""" | |
| if not self.log_dir: | |
| return | |
| # self.shutdown() | |
| all_logs = sorted(self.log_dir.glob("*.log")) | |
| if keep_last_n == 0: | |
| files_to_delete = all_logs | |
| else: | |
| files_to_delete = all_logs[:-keep_last_n] | |
| for log_file in files_to_delete: | |
| try: | |
| log_file.unlink() | |
| except PermissionError: | |
| self.logger.warning( | |
| f"无法删除日志文件 {log_file}, 它正被另一个进程使用" | |
| ) | |
| def shutdown(self): | |
| for handler in self.logger.handlers: | |
| handler.close() | |
| self.logger.removeHandler(handler) | |
| self.logger.handlers.clear() | |
| time.sleep(1) # 确保文件被释放 | |
| def log_setup(log_to_console=True): | |
| logger = logging.getLogger("Douyin_TikTok_Download_API_Crawlers") | |
| if logger.hasHandlers(): | |
| # logger已经被设置,不做任何操作 | |
| return logger | |
| # 创建临时的日志目录 | |
| temp_log_dir = Path("./logs") | |
| temp_log_dir.mkdir(exist_ok=True) | |
| # 初始化日志管理器 | |
| log_manager = LogManager() | |
| log_manager.setup_logging( | |
| level=logging.INFO, log_to_console=log_to_console, log_path=temp_log_dir | |
| ) | |
| # 只保留1000个日志文件 | |
| log_manager.clean_logs(1000) | |
| return logger | |
| logger = log_setup() | |