diff --git a/tg_publish.py b/tg_publish.py index 44aa858..eb59861 100644 --- a/tg_publish.py +++ b/tg_publish.py @@ -260,26 +260,25 @@ async def publish_to_tg(vk_post_id): write_timeout = max(120.0, 60.0 + (file_size_mb * 10.0)) logger.info(f"Размер файла: {file_size_mb:.2f} МБ, таймаут на запись: {write_timeout:.1f} секунд") - # Для больших файлов используем синхронный клиент, чтобы избежать проблем с event loop - # Синхронный клиент более стабилен при работе в отдельном потоке с gunicorn - use_sync_client = file_size_mb > 0.5 # Для файлов больше 0.5 МБ используем синхронный клиент + # Используем синхронный httpx клиент для отправки файлов + # Это необходимо для стабильной работы с gunicorn, который использует синхронные воркеры + # Синхронный клиент не зависит от event loop и работает стабильно в отдельном потоке + logger.info(f"Используем синхронный httpx клиент для файла размером {file_size_mb:.2f} МБ") # Логируем время начала запроса request_start_time = time.time() logger.info(f"Начало запроса отправки фото в {datetime.now(timezone.utc).isoformat()}") - if use_sync_client: - logger.info(f"Используем синхронный httpx клиент для файла размером {file_size_mb:.2f} МБ") - - # Используем синхронный httpx клиент для больших файлов - import httpx as httpx_sync - upload_timeout_config = httpx_sync.Timeout( - connect=10.0, - read=120.0, - write=write_timeout, # Динамический таймаут на запись - pool=10.0 - ) - + # Используем синхронный httpx клиент + import httpx as httpx_sync + upload_timeout_config = httpx_sync.Timeout( + connect=10.0, + read=120.0, + write=write_timeout, # Динамический таймаут на запись + pool=10.0 + ) + + try: with httpx_sync.Client( http2=False, timeout=upload_timeout_config, @@ -298,38 +297,21 @@ async def publish_to_tg(vk_post_id): } photo_response = sync_client.post(send_photo_url, files=files, data=data) - else: - # Для маленьких файлов используем асинхронный клиент - # Создаем клиент с увеличенным таймаутом на запись для больших файлов - upload_timeout_config = httpx.Timeout( - connect=10.0, - read=120.0, - write=write_timeout, # Динамический таймаут на запись - pool=10.0 - ) - upload_client = httpx.AsyncClient( - http2=False, - timeout=upload_timeout_config, - follow_redirects=True - ) - - try: - # Отправляем изображение как файл через multipart/form-data - # Создаем BytesIO объект для файла - image_file = io.BytesIO(image_data) - files = { - 'photo': ('image.jpg', image_file, 'image/jpeg') - } - data = { - 'chat_id': CHANNEL_ID, - 'caption': text, - 'parse_mode': 'HTML', - 'disable_notification': str(PUBLISH_SILENTLY).lower() - } - - photo_response = await upload_client.post(send_photo_url, files=files, data=data) - finally: - await upload_client.aclose() + except (httpx_sync.TimeoutException, httpx_sync.WriteTimeout, httpx_sync.ReadTimeout) as sync_timeout: + # Обработка таймаутов от синхронного клиента + timeout_type = type(sync_timeout).__name__ + request_duration = time.time() - request_start_time + logger.error(f"ТАЙМАУТ ({timeout_type}) при отправке изображения (синхронный клиент) для записи VK ID {vk_post_id}") + logger.error(f"Время до таймаута: {request_duration:.2f} секунд") + logger.error(f"Тип исключения: {timeout_type}") + logger.error(f"Сообщение об ошибке: {str(sync_timeout)}") + logger.error(f"Полная информация об исключении: {repr(sync_timeout)}") + logger.error(f"URL изображения: {image_url}") + logger.error(f"Размер файла: {file_size_mb:.2f} МБ") + if hasattr(sync_timeout, 'request'): + logger.error(f"Запрос, вызвавший таймаут: {sync_timeout.request.method} {sync_timeout.request.url if hasattr(sync_timeout.request, 'url') else 'N/A'}") + logger.error(f"Трассировка стека:\n{traceback.format_exc()}") + raise # Пробрасываем таймаут наверх request_duration = time.time() - request_start_time logger.info(f"Запрос отправки фото завершен за {request_duration:.2f} секунд") @@ -362,19 +344,33 @@ async def publish_to_tg(vk_post_id): raise Exception(f"HTTP {photo_response.status_code}: {photo_response.text}") except RetryAfterException: raise - except (httpx.TimeoutException, httpx.WriteTimeout, httpx.ReadTimeout) as e: - timeout_type = type(e).__name__ - logger.error(f"ТАЙМАУТ ({timeout_type}) при отправке изображения для записи VK ID {vk_post_id}") - logger.error(f"Тип исключения: {timeout_type}") - logger.error(f"Сообщение об ошибке: {str(e)}") - logger.error(f"Полная информация об исключении: {repr(e)}") - if hasattr(e, 'request'): - logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}") - # Для WriteTimeout логируем размер файла - if isinstance(e, httpx.WriteTimeout) and image_data is not None: - logger.error(f"Размер файла, вызвавшего таймаут записи: {len(image_data) / (1024 * 1024):.2f} МБ") - logger.error(f"Трассировка стека:\n{traceback.format_exc()}") - raise # Пробрасываем таймаут наверх + except Exception as e: + # Проверяем, является ли это таймаутом (может быть от синхронного или асинхронного клиента) + import httpx as httpx_sync + is_timeout = ( + isinstance(e, (httpx.TimeoutException, httpx.WriteTimeout, httpx.ReadTimeout)) or + isinstance(e, (httpx_sync.TimeoutException, httpx_sync.WriteTimeout, httpx_sync.ReadTimeout)) + ) + + if is_timeout: + timeout_type = type(e).__name__ + request_duration = time.time() - request_start_time if 'request_start_time' in locals() else 0 + logger.error(f"ТАЙМАУТ ({timeout_type}) при отправке изображения для записи VK ID {vk_post_id}") + if request_duration > 0: + logger.error(f"Время до таймаута: {request_duration:.2f} секунд") + logger.error(f"Тип исключения: {timeout_type}") + logger.error(f"Сообщение об ошибке: {str(e)}") + logger.error(f"Полная информация об исключении: {repr(e)}") + logger.error(f"URL изображения: {image_url}") + if image_data is not None: + logger.error(f"Размер файла: {len(image_data) / (1024 * 1024):.2f} МБ") + if hasattr(e, 'request'): + logger.error(f"Запрос, вызвавший таймаут: {e.request.method} {e.request.url if hasattr(e.request, 'url') else 'N/A'}") + logger.error(f"Трассировка стека:\n{traceback.format_exc()}") + raise # Пробрасываем таймаут наверх + + # Если это не таймаут, пробрасываем дальше для обработки как обычная ошибка + raise except Exception as e: logger.warning(f"Не удалось отправить изображение для записи VK ID {vk_post_id}: {e}. Отправляем текстовое сообщение.") message_has_image = False # Отправляем текстовое сообщение вместо изображения