mirror of
https://github.com/Evil0ctal/Douyin_TikTok_Download_API.git
synced 2025-04-08 09:43:44 +08:00
170 lines
5.8 KiB
Python
170 lines
5.8 KiB
Python
# ==============================================================================
|
||
# 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]
|
||
|
||
@classmethod
|
||
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)
|
||
|
||
@staticmethod
|
||
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()
|