logger refactor
This commit is contained in:
@@ -1,235 +1,174 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import pymysql
|
import pymysql
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.parse import unquote, parse_qs
|
from urllib.parse import unquote, parse_qs
|
||||||
import pytz
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.conf import settings
|
|
||||||
import chardet
|
|
||||||
import html
|
import html
|
||||||
from hotels.models import Room, Hotel
|
from hotels.models import Room, Hotel
|
||||||
from .models import UserActivityLog, ExternalDBSettings
|
from .models import UserActivityLog, ExternalDBSettings
|
||||||
|
from touchh.utils.log import CustomLogger
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, TimeoutError
|
||||||
|
from decouple import config
|
||||||
|
|
||||||
class DatabaseConnector:
|
class DatabaseConnector:
|
||||||
"""
|
|
||||||
Класс для подключения к внешней базе данных.
|
|
||||||
"""
|
|
||||||
def __init__(self, db_settings_id):
|
def __init__(self, db_settings_id):
|
||||||
self.db_settings_id = db_settings_id
|
self.db_settings_id = db_settings_id
|
||||||
|
self.logger = CustomLogger(name="DatabaseConnector", log_level="DEBUG").get_logger()
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self.logger = self.setup_logger()
|
self.db_settings = self.get_db_settings()
|
||||||
self.db_settings = None
|
|
||||||
|
|
||||||
def setup_logger(self):
|
def get_db_settings(self):
|
||||||
default_level = logging.INFO # Уровень по умолчанию
|
try:
|
||||||
level_name = getattr(settings, "DATA_SYNC_LOG_LEVEL", "INFO").upper()
|
settings = ExternalDBSettings.objects.get(id=self.db_settings_id)
|
||||||
log_level = getattr(logging, level_name, default_level)
|
self.logger.info(f"Retrieved DB settings: {settings}")
|
||||||
logger.setLevel(log_level)
|
return {
|
||||||
|
"host": settings.host,
|
||||||
# Настройка обработчика для файла
|
"port": settings.port,
|
||||||
handler = logging.FileHandler("data_sync.log")
|
"user": settings.user,
|
||||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
"password": settings.password,
|
||||||
handler.setLevel(log_level)
|
"database": settings.database,
|
||||||
|
"table_name": settings.table_name
|
||||||
# Удаляем старые обработчики, чтобы избежать дублирования
|
|
||||||
if logger.hasHandlers():
|
}
|
||||||
logger.handlers.clear()
|
except ExternalDBSettings.DoesNotExist:
|
||||||
logger.addHandler(handler)
|
self.logger.error(f"Settings with ID {self.db_settings_id} not found.")
|
||||||
|
raise ValueError("Invalid db_settings_id")
|
||||||
# Сообщение о текущем уровне логирования
|
except Exception as e:
|
||||||
logger.info(f"Уровень логирования установлен: {logging.getLevelName(log_level)}")
|
self.logger.error(f"Error retrieving settings: {e}")
|
||||||
|
raise
|
||||||
logger.addHandler(handler)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Подключение к базе данных."""
|
|
||||||
try:
|
try:
|
||||||
self.db_settings = ExternalDBSettings.objects.get(id=self.db_settings_id)
|
self.logger.info(f"Connecting to DB with settings: {self.db_settings}")
|
||||||
self.connection = pymysql.connect(
|
self.connection = pymysql.connect(
|
||||||
host=self.db_settings.host,
|
host=self.db_settings["host"],
|
||||||
port=self.db_settings.port,
|
port=self.db_settings["port"],
|
||||||
user=self.db_settings.user,
|
user=self.db_settings["user"],
|
||||||
password=self.db_settings.password,
|
password=self.db_settings["password"],
|
||||||
database=self.db_settings.database,
|
database=self.db_settings["database"],
|
||||||
charset="utf8mb4",
|
charset="utf8mb4",
|
||||||
cursorclass=pymysql.cursors.DictCursor,
|
cursorclass=pymysql.cursors.DictCursor,
|
||||||
)
|
)
|
||||||
self.logger.info("Подключение к базе данных успешно установлено.")
|
self.logger.info("Database connection established successfully.")
|
||||||
|
except pymysql.err.OperationalError as e:
|
||||||
|
self.logger.error(f"Operational error during DB connection: {e}")
|
||||||
|
raise ConnectionError(f"Operational error: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка подключения к БД: {e}")
|
self.logger.error(f"Unexpected database connection error: {e}")
|
||||||
raise ConnectionError(e)
|
raise ConnectionError(e)
|
||||||
|
|
||||||
|
def execute_query(self, query):
|
||||||
|
try:
|
||||||
|
with self.connection.cursor() as cursor:
|
||||||
|
cursor.execute(query)
|
||||||
|
return cursor.fetchall()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Query execution error: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Закрывает соединение с базой данных."""
|
|
||||||
if self.connection:
|
if self.connection:
|
||||||
self.connection.close()
|
self.connection.close()
|
||||||
self.logger.info("Соединение с базой данных закрыто.")
|
self.logger.info("Database connection closed.")
|
||||||
|
|
||||||
def execute_query(self, query):
|
|
||||||
"""Выполнение запроса и возврат результатов."""
|
|
||||||
with self.connection.cursor() as cursor:
|
|
||||||
cursor.execute(query)
|
|
||||||
return cursor.fetchall()
|
|
||||||
|
|
||||||
|
|
||||||
class DataProcessor:
|
class DataProcessor:
|
||||||
"""
|
|
||||||
Обрабатывает и сохраняет данные.
|
|
||||||
"""
|
|
||||||
def __init__(self, logger):
|
def __init__(self, logger):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
def decode_html_entities(self, text):
|
def decode_html_entities(self, text):
|
||||||
"""Декодирует URL и HTML-сущности."""
|
|
||||||
if text and isinstance(text, str):
|
if text and isinstance(text, str):
|
||||||
text = unquote(text)
|
return html.unescape(unquote(text))
|
||||||
text = html.unescape(text)
|
|
||||||
try:
|
|
||||||
detected = chardet.detect(text.encode())
|
|
||||||
encoding = detected['encoding']
|
|
||||||
if encoding and encoding != 'utf-8':
|
|
||||||
text = text.encode(encoding).decode('utf-8', errors='ignore')
|
|
||||||
except Exception as e:
|
|
||||||
self.logger.error(f"Ошибка кодировки: {e}")
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def parse_datetime(self, dt_str):
|
def parse_datetime(self, dt_str):
|
||||||
"""Преобразует строку даты в aware datetime."""
|
|
||||||
try:
|
try:
|
||||||
if isinstance(dt_str, datetime):
|
if isinstance(dt_str, datetime):
|
||||||
return timezone.make_aware(dt_str) if timezone.is_naive(dt_str) else dt_str
|
return timezone.make_aware(dt_str) if timezone.is_naive(dt_str) else dt_str
|
||||||
if dt_str:
|
if dt_str:
|
||||||
return timezone.make_aware(datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S"))
|
return timezone.make_aware(datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка парсинга даты: {e}")
|
self.logger.error(f"Datetime parsing error: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def url_parameters_parser(self, url_parameters):
|
def url_parameters_parser(self, url_parameters):
|
||||||
"""
|
|
||||||
Парсит строку URL-параметров и возвращает словарь с ключами и значениями.
|
|
||||||
|
|
||||||
Пример входа:
|
|
||||||
url_parameters = "utm_medium=qr-3&utm_content=chisto-pozhit&utm_term=101"
|
|
||||||
|
|
||||||
Возвращает:
|
|
||||||
{
|
|
||||||
"utm_medium": "qr-3",
|
|
||||||
"utm_content": "chisto-pozhit",
|
|
||||||
"utm_term": "101"
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
if not url_parameters:
|
if not url_parameters:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# Декодируем параметры URL
|
|
||||||
decoded_params = unquote(url_parameters)
|
decoded_params = unquote(url_parameters)
|
||||||
parsed_params = parse_qs(decoded_params)
|
parsed_params = parse_qs(decoded_params)
|
||||||
|
return {key: value[0] for key, value in parsed_params.items()}
|
||||||
# Преобразуем список значений в строку
|
|
||||||
result = {key: value[0] for key, value in parsed_params.items()}
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка парсинга URL-параметров: {e}")
|
self.logger.error(f"URL parameters parsing error: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class HotelRoomManager:
|
class HotelRoomManager:
|
||||||
"""
|
|
||||||
Управляет созданием отелей и номеров.
|
|
||||||
"""
|
|
||||||
def __init__(self, logger):
|
def __init__(self, logger):
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
def get_or_create_hotel(self, hotel_id, page_title):
|
def get_or_create_hotel(self, hotel_id, page_title):
|
||||||
"""
|
|
||||||
Создает или получает отель.
|
|
||||||
|
|
||||||
:param hotel_id: Значение из utm_content (индекс отеля)
|
|
||||||
:param page_title: Название отеля из поля page_title
|
|
||||||
"""
|
|
||||||
if not hotel_id:
|
if not hotel_id:
|
||||||
self.logger.warning("Пропущено создание отеля: отсутствует hotel_id.")
|
self.logger.warning("Hotel creation skipped: missing hotel_id.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Создаем или получаем отель с hotel_id и устанавливаем name по page_title
|
|
||||||
hotel, created = Hotel.objects.get_or_create(
|
hotel, created = Hotel.objects.get_or_create(
|
||||||
hotel_id=hotel_id,
|
hotel_id=hotel_id,
|
||||||
defaults={
|
defaults={
|
||||||
"name": html.unescape(page_title) or f"Отель {hotel_id}",
|
"name": html.unescape(page_title) or f"Отель {hotel_id}",
|
||||||
"description": "Автоматически добавленный отель"
|
"description": "Автоматически созданный отель",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if created:
|
if created:
|
||||||
self.logger.info(f"Создан отель '{hotel.name}' с hotel_id: {hotel_id}")
|
self.logger.info(f"Hotel '{hotel.name}' created with hotel_id: {hotel_id}")
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"Отель '{hotel.name}' уже существует с hotel_id: {hotel_id}")
|
self.logger.info(f"Hotel '{hotel.name}' already exists with hotel_id: {hotel_id}")
|
||||||
return hotel
|
return hotel
|
||||||
|
|
||||||
def get_or_create_room(self, hotel, room_number):
|
def get_or_create_room(self, hotel, room_number):
|
||||||
"""
|
|
||||||
Создает или получает номер отеля.
|
|
||||||
|
|
||||||
:param hotel: Экземпляр модели Hotel
|
|
||||||
:param room_number: Номер комнаты из utm_term
|
|
||||||
"""
|
|
||||||
if not hotel:
|
if not hotel:
|
||||||
self.logger.warning("Пропущено создание номера: отсутствует отель.")
|
self.logger.warning("Room creation skipped: missing hotel.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not room_number:
|
if not room_number:
|
||||||
self.logger.warning(f"Пропущено создание номера: отсутствует room_number для отеля {hotel.name}.")
|
self.logger.warning(f"Room creation skipped: missing room_number for hotel {hotel.name}.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Генерация уникального external_id на основе hotel_id и room_number
|
|
||||||
external_id = f"{hotel.hotel_id}_{room_number}".lower()
|
external_id = f"{hotel.hotel_id}_{room_number}".lower()
|
||||||
|
|
||||||
# Создаем или получаем номер
|
|
||||||
room, created = Room.objects.get_or_create(
|
room, created = Room.objects.get_or_create(
|
||||||
hotel=hotel,
|
hotel=hotel,
|
||||||
number=room_number,
|
number=room_number,
|
||||||
defaults={
|
defaults={
|
||||||
"number": room_number, # Используем room_number как название номера
|
|
||||||
"external_id": external_id,
|
"external_id": external_id,
|
||||||
"description": "Автоматически добавленный номер"
|
"description": "Автоматически созданная комната",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if created:
|
if created:
|
||||||
self.logger.info(f"Создан номер '{room.number}' (external_id: {external_id}) в отеле '{hotel.name}'")
|
self.logger.info(f"Room '{room.number}' (external_id: {external_id}) created in hotel '{hotel.name}'")
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"Номер '{room.number}' уже существует в отеле '{hotel.name}'")
|
self.logger.info(f"Room '{room.number}' already exists in hotel '{hotel.name}'")
|
||||||
|
|
||||||
return room
|
return room
|
||||||
|
|
||||||
|
|
||||||
class DataSyncManager:
|
class DataSyncManager:
|
||||||
"""
|
|
||||||
Главный класс для синхронизации данных.
|
|
||||||
"""
|
|
||||||
def __init__(self, db_settings_id):
|
def __init__(self, db_settings_id):
|
||||||
self.logger = self.setup_logger()
|
self.logger = CustomLogger(name="DataSyncManager", log_level="DEBUG").get_logger()
|
||||||
self.db_connector = DatabaseConnector(db_settings_id)
|
self.db_connector = DatabaseConnector(db_settings_id)
|
||||||
|
self.db_settings = self.db_connector.db_settings # Сохраняем настройки базы данных
|
||||||
self.data_processor = DataProcessor(self.logger)
|
self.data_processor = DataProcessor(self.logger)
|
||||||
self.hotel_manager = HotelRoomManager(self.logger)
|
self.hotel_manager = HotelRoomManager(self.logger)
|
||||||
|
|
||||||
def setup_logger(self):
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
handler = logging.FileHandler("data_sync.log")
|
|
||||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
||||||
logger.addHandler(handler)
|
|
||||||
return logger
|
|
||||||
|
|
||||||
def get_last_saved_record(self):
|
def get_last_saved_record(self):
|
||||||
"""Получает ID последней записи."""
|
|
||||||
record = UserActivityLog.objects.order_by("-id").first()
|
record = UserActivityLog.objects.order_by("-id").first()
|
||||||
return record.id if record else 0
|
return record.id if record else 0
|
||||||
|
|
||||||
def fetch_new_data(self, last_id):
|
def fetch_new_data(self, last_id):
|
||||||
"""Получает новые данные из БД."""
|
|
||||||
query = f"""
|
query = f"""
|
||||||
SELECT * FROM `{self.db_connector.db_settings.table_name}`
|
SELECT * FROM `{self.db_settings.get('table_name')}`
|
||||||
WHERE id > {last_id}
|
WHERE id > {last_id}
|
||||||
AND url_parameters IS NOT NULL
|
AND url_parameters IS NOT NULL
|
||||||
AND url_parameters LIKE '%utm_medium%'
|
AND url_parameters LIKE '%utm_medium%'
|
||||||
@@ -237,100 +176,85 @@ class DataSyncManager:
|
|||||||
ORDER BY id ASC
|
ORDER BY id ASC
|
||||||
LIMIT 1000;
|
LIMIT 1000;
|
||||||
"""
|
"""
|
||||||
self.logger.info(f"Запрос на получение новых данных отправлен. \n Содержание запроса: {query}")
|
|
||||||
|
self.logger.info(f"Fetching new data with query: {query}")
|
||||||
return self.db_connector.execute_query(query)
|
return self.db_connector.execute_query(query)
|
||||||
|
|
||||||
def process_and_save_data(self, rows):
|
def process_and_save_data(self, rows):
|
||||||
"""
|
|
||||||
Обрабатывает и сохраняет данные из внешней базы данных.
|
|
||||||
"""
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
try:
|
try:
|
||||||
# Декодирование URL-параметров
|
|
||||||
url_params = self.data_processor.decode_html_entities(row.get("url_parameters", ""))
|
url_params = self.data_processor.decode_html_entities(row.get("url_parameters", ""))
|
||||||
params = self.data_processor.url_parameters_parser(url_params)
|
params = self.data_processor.url_parameters_parser(url_params)
|
||||||
timestamp = params.get("timestamp")
|
|
||||||
date_time = params.get("date_time")
|
|
||||||
# Извлечение данных
|
|
||||||
hotel_id = params.get("utm_content")
|
hotel_id = params.get("utm_content")
|
||||||
room_number = params.get("utm_term")
|
room_number = params.get("utm_term")
|
||||||
page_title = row.get("page_title") # Название отеля из page_title
|
page_title = row.get("page_title")
|
||||||
external_id = row.get("id")
|
external_id = row.get("id")
|
||||||
|
hits = row.get("hits") or 0
|
||||||
|
|
||||||
# Создание отеля и комнаты
|
|
||||||
hotel = self.hotel_manager.get_or_create_hotel(hotel_id, page_title)
|
hotel = self.hotel_manager.get_or_create_hotel(hotel_id, page_title)
|
||||||
room = self.hotel_manager.get_or_create_room(hotel, room_number)
|
room = self.hotel_manager.get_or_create_room(hotel, room_number)
|
||||||
page_url = row.get("page_url")
|
page_url = row.get("page_url")
|
||||||
# Заполнение записи
|
|
||||||
UserActivityLog.objects.update_or_create(
|
if hits != 0 and page_title is not None:
|
||||||
external_id=external_id,
|
UserActivityLog.objects.update_or_create(
|
||||||
defaults={
|
external_id=external_id,
|
||||||
"user_id": row.get("user_id") or 0,
|
defaults={
|
||||||
"timestamp": row.get("timestamp"),
|
"user_id": row.get("user_id") or 0,
|
||||||
"date_time": row.get("date_time"),
|
"timestamp": row.get("timestamp"),
|
||||||
"ip": row.get("ip") or "0.0.0.0",
|
"date_time": row.get("date_time"),
|
||||||
"created": self.data_processor.parse_datetime(row.get("created")) or timezone.now(),
|
"ip": row.get("ip") or "0.0.0.0",
|
||||||
"url_parameters": url_params,
|
"created": self.data_processor.parse_datetime(row.get("created")) or timezone.now(),
|
||||||
"page_id": room.id if room else None,
|
"url_parameters": url_params,
|
||||||
"page_title": html.unescape(page_title),
|
"page_id": room.id if room else None,
|
||||||
"hits": row.get("hits") or 0,
|
"page_title": html.unescape(page_title),
|
||||||
"page_url": html.unescape(page_url),
|
"hits": hits,
|
||||||
}
|
"page_url": html.unescape(page_url),
|
||||||
)
|
}
|
||||||
self.logger.info(f"Запись ID {external_id} успешно обработана.")
|
)
|
||||||
|
else:
|
||||||
|
self.logger.warning("Invalid data for UserActivityLog.")
|
||||||
|
self.logger.info(f"Record ID {external_id} processed successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Ошибка при обработке записи ID {row.get('id')}: {e}")
|
self.logger.error(f"Error processing record ID {row.get('id')}: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
"""Запускает процесс синхронизации."""
|
|
||||||
self.db_connector.connect()
|
self.db_connector.connect()
|
||||||
try:
|
try:
|
||||||
last_id = self.get_last_saved_record()
|
last_id = self.get_last_saved_record()
|
||||||
rows = self.fetch_new_data(last_id)
|
rows = self.fetch_new_data(last_id)
|
||||||
self.process_and_save_data(rows)
|
self.process_and_save_data(rows)
|
||||||
self.logger.info("Синхронизация завершена.")
|
self.logger.info("Sync completed.")
|
||||||
finally:
|
finally:
|
||||||
self.db_connector.close()
|
self.db_connector.close()
|
||||||
|
|
||||||
|
|
||||||
import logging
|
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
from .models import ExternalDBSettings
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
logger.setLevel(logging.DEBUG)
|
|
||||||
def scheduled_sync():
|
def scheduled_sync():
|
||||||
"""
|
logger = CustomLogger(name="DatabaseSyncScheduler", log_level="DEBUG").get_logger()
|
||||||
Планировщик синхронизации для всех активных подключений.
|
logger.info("Starting scheduled sync.")
|
||||||
Каждое подключение обрабатывается отдельно.
|
|
||||||
"""
|
|
||||||
logger.info("Запуск планировщика синхронизации.")
|
|
||||||
|
|
||||||
# Получаем все активные настройки подключения
|
|
||||||
active_db_settings = ExternalDBSettings.objects.filter(is_active=True)
|
active_db_settings = ExternalDBSettings.objects.filter(is_active=True)
|
||||||
if not active_db_settings.exists():
|
if not active_db_settings.exists():
|
||||||
logger.warning("Не найдено активных подключений для синхронизации.")
|
logger.warning("No active database connections found.")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Найдено активных подключений: {len(active_db_settings)}")
|
logger.info(f"Found {len(active_db_settings)} active database connections.")
|
||||||
|
|
||||||
def sync_task(db_settings):
|
def sync_task(db_settings):
|
||||||
"""
|
|
||||||
Выполняет синхронизацию для одного подключения.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
logger.info(f"Начало синхронизации для подключения: {db_settings.name} (ID={db_settings.id})")
|
logger.info(f"Syncing connection: {db_settings.name} (ID={db_settings.id})")
|
||||||
sync_manager = DataSyncManager(db_settings.id)
|
sync_manager = DataSyncManager(db_settings.id)
|
||||||
sync_manager.sync()
|
sync_manager.sync()
|
||||||
logger.info(f"Синхронизация успешно завершена для подключения: {db_settings.name}")
|
logger.info(f"Sync completed for connection: {db_settings}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка синхронизации для подключения {db_settings.name}: {e}")
|
logger.error(f"Error syncing connection {db_settings}: {e}")
|
||||||
|
|
||||||
# Параллельное выполнение задач синхронизации
|
with ThreadPoolExecutor(max_workers=5) as executor:
|
||||||
with ThreadPoolExecutor(max_workers=5) as executor: # Максимальное количество потоков = 5
|
futures = [executor.submit(sync_task, db_settings) for db_settings in active_db_settings]
|
||||||
for db_settings in active_db_settings:
|
for future in futures:
|
||||||
executor.submit(sync_task, db_settings)
|
try:
|
||||||
|
future.result(timeout=300)
|
||||||
|
except TimeoutError:
|
||||||
|
logger.error("Sync task timed out.")
|
||||||
|
|
||||||
logger.info("Планировщик синхронизации завершил работу.")
|
logger.info("Scheduled sync completed.")
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from telegram.ext import Application
|
|||||||
from bot.utils.bot_setup import setup_bot
|
from bot.utils.bot_setup import setup_bot
|
||||||
from scheduler.tasks import load_tasks_to_scheduler
|
from scheduler.tasks import load_tasks_to_scheduler
|
||||||
from settings.models import TelegramSettings
|
from settings.models import TelegramSettings
|
||||||
|
from touchh.utils.log import CustomLogger
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Запуск Telegram бота и планировщика"
|
help = "Запуск Telegram бота и планировщика"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import logging
|
|||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO, # Уровень логирования (можно DEBUG для полной информации)
|
level=logging.ERROR, # Уровень логирования (можно DEBUG для полной информации)
|
||||||
format="%(asctime)s - %(levelname)s - %(message)s",
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||||
handlers=[
|
handlers=[
|
||||||
logging.FileHandler("bot.log"), # Логи будут записываться в файл bot.log
|
logging.FileHandler("bot.log"), # Логи будут записываться в файл bot.log
|
||||||
|
|||||||
@@ -66,4 +66,4 @@ user-agents==2.2.0
|
|||||||
yarl==1.18.3
|
yarl==1.18.3
|
||||||
mysqlclient
|
mysqlclient
|
||||||
chardet
|
chardet
|
||||||
decouple
|
python-decouple
|
||||||
109
requirements.txt
109
requirements.txt
@@ -1,109 +0,0 @@
|
|||||||
ace_tools==0.0
|
|
||||||
aiohappyeyeballs==2.4.4
|
|
||||||
aiohttp==3.11.10
|
|
||||||
aiosignal==1.3.1
|
|
||||||
anyio==4.6.2.post1
|
|
||||||
APScheduler==3.11.0
|
|
||||||
asgiref==3.8.1
|
|
||||||
async-timeout==5.0.1
|
|
||||||
attrs==24.2.0
|
|
||||||
certifi==2024.8.30
|
|
||||||
charset-normalizer==3.4.0
|
|
||||||
Django==5.1.4
|
|
||||||
django-filter==24.3
|
|
||||||
django-jazzmin==3.0.1
|
|
||||||
django-jet==1.0.8
|
|
||||||
et_xmlfile==2.0.0
|
|
||||||
exceptiongroup==1.2.2
|
|
||||||
fpdf==1.7.2
|
|
||||||
frozenlist==1.5.0
|
|
||||||
geoip2==4.8.1
|
|
||||||
h11==0.14.0
|
|
||||||
httpcore==1.0.7
|
|
||||||
httpx==0.28.0
|
|
||||||
idna==3.10
|
|
||||||
jsonschema==4.23.0
|
|
||||||
jsonschema-specifications==2024.10.1
|
|
||||||
maxminddb==2.6.2
|
|
||||||
multidict==6.1.0
|
|
||||||
numpy==2.1.3
|
|
||||||
openpyxl==3.1.5
|
|
||||||
pandas==2.2.3
|
|
||||||
pathspec==0.12.1
|
|
||||||
pillow==11.0.0
|
|
||||||
propcache==0.2.1
|
|
||||||
PyMySQL==1.1.1
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
python-dotenv==1.0.1
|
|
||||||
python-telegram-bot==21.8
|
|
||||||
pytz==2024.2
|
|
||||||
PyYAML==6.0.2
|
|
||||||
referencing==0.35.1
|
|
||||||
requests==2.32.3
|
|
||||||
rpds-py==0.22.3
|
|
||||||
six==1.17.0
|
|
||||||
sniffio==1.3.1
|
|
||||||
sqlparse==0.5.2
|
|
||||||
typing_extensions==4.12.2
|
|
||||||
tzdata==2024.2
|
|
||||||
tzlocal==5.2
|
|
||||||
ua-parser==1.0.0
|
|
||||||
ua-parser-builtins==0.18.0.post1
|
|
||||||
urllib3==2.2.3
|
|
||||||
user-agents==2.2.0
|
|
||||||
yarl==1.18.3
|
|
||||||
ace_tools==0.0
|
|
||||||
aiohappyeyeballs==2.4.4
|
|
||||||
aiohttp==3.11.10
|
|
||||||
aiosignal==1.3.1
|
|
||||||
anyio==4.6.2.post1
|
|
||||||
APScheduler==3.11.0
|
|
||||||
asgiref==3.8.1
|
|
||||||
async-timeout==5.0.1
|
|
||||||
attrs==24.2.0
|
|
||||||
certifi==2024.8.30
|
|
||||||
charset-normalizer==3.4.0
|
|
||||||
Django==5.1.4
|
|
||||||
django-filter==24.3
|
|
||||||
django-health-check==3.18.3
|
|
||||||
django-jazzmin==3.0.1
|
|
||||||
django-jet==1.0.8
|
|
||||||
et_xmlfile==2.0.0
|
|
||||||
exceptiongroup==1.2.2
|
|
||||||
fpdf==1.7.2
|
|
||||||
frozenlist==1.5.0
|
|
||||||
geoip2==4.8.1
|
|
||||||
h11==0.14.0
|
|
||||||
httpcore==1.0.7
|
|
||||||
httpx==0.28.0
|
|
||||||
idna==3.10
|
|
||||||
jsonschema==4.23.0
|
|
||||||
jsonschema-specifications==2024.10.1
|
|
||||||
maxminddb==2.6.2
|
|
||||||
multidict==6.1.0
|
|
||||||
numpy==2.1.3
|
|
||||||
openpyxl==3.1.5
|
|
||||||
pandas==2.2.3
|
|
||||||
pathspec==0.12.1
|
|
||||||
pillow==11.0.0
|
|
||||||
propcache==0.2.1
|
|
||||||
PyMySQL==1.1.1
|
|
||||||
python-dateutil==2.9.0.post0
|
|
||||||
python-dotenv==1.0.1
|
|
||||||
python-telegram-bot==21.8
|
|
||||||
pytz==2024.2
|
|
||||||
PyYAML==6.0.2
|
|
||||||
referencing==0.35.1
|
|
||||||
requests==2.32.3
|
|
||||||
rpds-py==0.22.3
|
|
||||||
six==1.17.0
|
|
||||||
sniffio==1.3.1
|
|
||||||
sqlparse==0.5.2
|
|
||||||
typing_extensions==4.12.2
|
|
||||||
tzdata==2024.2
|
|
||||||
tzlocal==5.2
|
|
||||||
ua-parser==1.0.0
|
|
||||||
ua-parser-builtins==0.18.0.post1
|
|
||||||
urllib3==2.2.3
|
|
||||||
user-agents==2.2.0
|
|
||||||
yarl==1.18.3
|
|
||||||
@@ -131,7 +131,7 @@ LOGGING = {
|
|||||||
'disable_existing_loggers': False,
|
'disable_existing_loggers': False,
|
||||||
'handlers': {
|
'handlers': {
|
||||||
'file': {
|
'file': {
|
||||||
'level': 'WARNING',
|
'level': os.getenv("LOG_LEVEL"),
|
||||||
'class': 'logging.FileHandler',
|
'class': 'logging.FileHandler',
|
||||||
'filename': 'import_hotels.log', # Лог будет записываться в этот файл
|
'filename': 'import_hotels.log', # Лог будет записываться в этот файл
|
||||||
},
|
},
|
||||||
@@ -139,12 +139,12 @@ LOGGING = {
|
|||||||
'loggers': {
|
'loggers': {
|
||||||
'django': {
|
'django': {
|
||||||
'handlers': ['file'],
|
'handlers': ['file'],
|
||||||
'level': 'WARNING',
|
'level': os.getenv("LOG_LEVEL"),
|
||||||
'propagate': True,
|
'propagate': True,
|
||||||
},
|
},
|
||||||
'antifroud': {
|
'antifroud': {
|
||||||
'handlers': ['file'],
|
'handlers': ['file'],
|
||||||
'level': 'WARNING',
|
'level': os.getenv("LOG_LEVEL"),
|
||||||
'propagate': True,
|
'propagate': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
0
touchh/utils/__init__.py
Normal file
0
touchh/utils/__init__.py
Normal file
55
touchh/utils/log.py
Normal file
55
touchh/utils/log.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# Загрузка переменных из .env
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
class CustomLogger:
|
||||||
|
"""
|
||||||
|
Универсальный логгер для использования в проекте.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, log_level=None):
|
||||||
|
"""
|
||||||
|
Инициализирует логгер.
|
||||||
|
|
||||||
|
:param name: Имя логгера (обычно имя функции или класса)
|
||||||
|
:param log_level: Уровень логирования (по умолчанию из .env)
|
||||||
|
"""
|
||||||
|
self.logger = logging.getLogger(name)
|
||||||
|
|
||||||
|
# Уровень логирования по умолчанию из .env
|
||||||
|
default_level = os.getenv("LOG_LEVEL", "INFO").upper()
|
||||||
|
self.log_level = getattr(logging, log_level.upper(), getattr(logging, default_level, logging.INFO))
|
||||||
|
|
||||||
|
self.setup_logger()
|
||||||
|
|
||||||
|
def setup_logger(self):
|
||||||
|
"""
|
||||||
|
Настраивает логгер с обработчиком и форматом.
|
||||||
|
"""
|
||||||
|
self.logger.setLevel(self.log_level)
|
||||||
|
|
||||||
|
# Удаляем старые обработчики, чтобы избежать дублирования
|
||||||
|
if self.logger.hasHandlers():
|
||||||
|
self.logger.handlers.clear()
|
||||||
|
|
||||||
|
# Добавляем обработчик для файла
|
||||||
|
file_handler = logging.FileHandler("project.log")
|
||||||
|
file_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
|
||||||
|
file_handler.setLevel(self.log_level)
|
||||||
|
|
||||||
|
# Добавляем обработчик для консоли
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s"))
|
||||||
|
console_handler.setLevel(self.log_level)
|
||||||
|
|
||||||
|
self.logger.addHandler(file_handler)
|
||||||
|
self.logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
def get_logger(self):
|
||||||
|
"""
|
||||||
|
Возвращает настроенный логгер.
|
||||||
|
"""
|
||||||
|
return self.logger
|
||||||
Reference in New Issue
Block a user