Telezab/utilities/log_manager.py
2025-03-17 15:30:58 +05:00

205 lines
8.1 KiB
Python

import logging
import os
import zipfile
from datetime import datetime, timedelta
from logging.config import dictConfig
from logging.handlers import TimedRotatingFileHandler
class UTF8StreamHandler(logging.StreamHandler):
def __init__(self, stream=None):
super().__init__(stream)
self.setStream(stream)
def setStream(self, stream):
super().setStream(stream)
if hasattr(stream, 'reconfigure'):
stream.reconfigure(encoding='utf-8')
class FilterByMessage(logging.Filter):
def filter(self, record):
# Фильтруем сообщения, содержащие 'Received 1 new updates'
return 'Received ' not in record.getMessage()
class LogManager:
def __init__(self, log_dir='logs', retention_days=30):
self.log_dir = log_dir
self.retention_days = retention_days
self.log_files = {
'flask': os.path.join(self.log_dir, 'flask.log'),
'flask_error': os.path.join(self.log_dir, 'flask_error.log'),
'app': os.path.join(self.log_dir, 'app.log'),
'app_error': os.path.join(self.log_dir, 'app_error.log'),
'debug': os.path.join(self.log_dir, 'debug.log'),
}
# Ensure the log directory exists
if not os.path.exists(self.log_dir):
os.makedirs(self.log_dir)
# Setup logging configuration
self.setup_logging()
def setup_logging(self):
dictConfig({
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s',
},
'error': {
'format': '[%(asctime)s] %(levelname)s %(module)s: %(message)s',
},
'werkzeug': {
'format': '[%(asctime)s] %(levelname)s %(message)s'
},
'debug': {
'format': '[%(asctime)s] %(levelname)s %(module)s [%(funcName)s:%(lineno)d]: %(message)s'
}
},
'handlers': {
'telebot_console': {
'class': 'utilities.log_manager.UTF8StreamHandler',
'stream': 'ext://sys.stdout',
'formatter': 'default',
},
'flask_console': {
'class': 'utilities.log_manager.UTF8StreamHandler',
'stream': 'ext://sys.stdout',
'formatter': 'werkzeug',
},
'flask_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['flask'],
'when': 'midnight',
'backupCount': self.retention_days,
'formatter': 'werkzeug',
'encoding': 'utf-8',
},
'flask_error_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['flask_error'],
'when': 'midnight',
'backupCount': self.retention_days,
'formatter': 'werkzeug',
'encoding': 'utf-8',
'level': 'ERROR',
},
'app_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['app'],
'when': 'midnight',
'backupCount': self.retention_days,
'formatter': 'default',
'encoding': 'utf-8',
},
'app_error_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['app_error'],
'when': 'midnight',
'backupCount': self.retention_days,
'formatter': 'error',
'encoding': 'utf-8',
'level': 'ERROR',
},
'debug_file': {
'class': 'logging.handlers.TimedRotatingFileHandler',
'filename': self.log_files['debug'],
'when': 'midnight',
'backupCount': self.retention_days,
'formatter': 'debug',
'encoding': 'utf-8',
'level': 'DEBUG',
},
},
'loggers': {
'flask': {
'level': 'INFO',
'handlers': ['flask_file', 'flask_error_file', 'flask_console'],
'propagate': False,
},
'telebot': {
'level': 'INFO',
'handlers': ['app_file', 'app_error_file', 'telebot_console'],
'propagate': False,
},
'werkzeug': {
'level': 'INFO',
'handlers': ['flask_file', 'flask_error_file', 'flask_console'],
'propagate': False,
},
'debug': {
'level': 'DEBUG',
'handlers': ['debug_file'],
'propagate': False,
},
}
# 'root': {
# 'level': 'DEBUG',
# 'handlers': ['flask_console', 'telebot_console'],
# }
})
def archive_old_logs(self):
"""Archives old log files and removes logs older than retention_days."""
# Get yesterday's date
yesterday_date = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
for log_name, log_file in self.log_files.items():
if os.path.exists(log_file):
archive_name = f"{log_name}_{yesterday_date}.zip"
archive_path = os.path.join(self.log_dir, archive_name)
# Archive the log file
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
zipf.write(log_file, arcname=os.path.basename(log_file))
# Remove the old log file after archiving
os.remove(log_file)
# Clean up old archives
self.cleanup_old_archives()
def configure_werkzeug_logging(self):
"""Отключаем встроенный логгер Werkzeug и задаём собственные настройки логирования."""
werkzeug_logger = logging.getLogger('werkzeug')
werkzeug_logger.handlers = [] # Удаляем существующие обработчики
# Добавляем кастомный обработчик для форматирования логов
handler = TimedRotatingFileHandler(self.log_files['flask'], when='midnight', backupCount=self.retention_days,
encoding='utf-8')
handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s %(message)s'))
werkzeug_logger.addHandler(handler)
# Отключаем дублирование логов
werkzeug_logger.propagate = False
def cleanup_old_archives(self):
"""Deletes archived logs older than retention_days."""
now = datetime.now()
cutoff = now - timedelta(days=self.retention_days)
for file in os.listdir(self.log_dir):
if file.endswith('.zip'):
file_path = os.path.join(self.log_dir, file)
file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
if file_time < cutoff:
os.remove(file_path)
def schedule_log_rotation(self):
"""Schedules daily log rotation and archiving."""
from threading import Timer
now = datetime.now()
next_midnight = now.replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1)
delay = (next_midnight - now).total_seconds()
Timer(delay, self.rotate_and_archive_logs).start()
def rotate_and_archive_logs(self):
"""Rotates and archives logs."""
self.archive_old_logs()
self.schedule_log_rotation() # Schedule the next rotation