2024-04-30 18:43:47 -07:00

170 lines
5.8 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# ==============================================================================
# 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()