139 lines
5.0 KiB
Python
139 lines
5.0 KiB
Python
"""Фильтрация и человекочитаемые сообщения об ошибках синхронизации (UI и логи)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from urllib.parse import urlparse
|
|
|
|
# Фрагменты типичных сообщений urllib3/requests и ОС о недоступности сервера / DNS / таймаутах.
|
|
_SERVER_CONNECTION_MARKERS = (
|
|
"connection refused",
|
|
"connection reset",
|
|
"connection aborted",
|
|
"broken pipe",
|
|
"server has gone away",
|
|
"remote end closed connection",
|
|
"connection timed out",
|
|
"timed out",
|
|
"timeout",
|
|
"read timed out",
|
|
"connect timeout",
|
|
"name or service not known",
|
|
"nodename nor servname",
|
|
"getaddrinfo failed",
|
|
"temporary failure in name resolution",
|
|
"failed to resolve",
|
|
"network is unreachable",
|
|
"no route to host",
|
|
"could not connect",
|
|
"unable to connect",
|
|
"failed to establish a new connection",
|
|
"max retries exceeded",
|
|
"newconnectionerror",
|
|
"connecttimeouterror",
|
|
"connectionerror",
|
|
"httpconnectionpool",
|
|
"httpsconnectionpool",
|
|
"errno 111",
|
|
"errno 110",
|
|
"errno 113",
|
|
"econnrefused",
|
|
"etimedout",
|
|
"ehostunreach",
|
|
"enotfound",
|
|
"winerror 10061",
|
|
"10060", # Windows WSAETIMEDOUT
|
|
"10065", # WSAEHOSTUNREACH
|
|
# Русские формулировки (системные / локализованные)
|
|
"отказ в подключении",
|
|
"время ожидания истекло",
|
|
"нет маршрута до узла",
|
|
"сеть недоступна",
|
|
)
|
|
|
|
|
|
def is_server_connection_error_message(message: str | None) -> bool:
|
|
"""True, если текст похож на ошибку доставки до сервера (TCP/DNS/таймаут), а не на логику sync."""
|
|
if message is None:
|
|
return False
|
|
s = str(message).strip().lower()
|
|
if not s:
|
|
return False
|
|
return any(marker in s for marker in _SERVER_CONNECTION_MARKERS)
|
|
|
|
|
|
def sync_error_text_for_display(message: str | None) -> str | None:
|
|
"""Текст ошибки для показа оператору или None, если скрываем (ошибка подключения к серверу)."""
|
|
if message is None:
|
|
return None
|
|
raw = str(message).strip()
|
|
if not raw:
|
|
return None
|
|
if is_server_connection_error_message(raw):
|
|
return None
|
|
return raw
|
|
|
|
|
|
def _host_from_server_url(server_url: str) -> str:
|
|
try:
|
|
host = (urlparse(server_url or "").hostname or "").strip()
|
|
return host or "?"
|
|
except Exception:
|
|
return "?"
|
|
|
|
|
|
def is_transient_sync_transport_error(exc: BaseException) -> bool:
|
|
"""Сетевые сбои: DNS, таймаут, отказ соединения — фоновый sync может повторить позже."""
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
requests = None # type: ignore
|
|
|
|
if requests is not None and isinstance(exc, (requests.Timeout, requests.ConnectionError)):
|
|
return True
|
|
if requests is not None and isinstance(exc, requests.RequestException):
|
|
return is_server_connection_error_message(str(exc))
|
|
return is_server_connection_error_message(str(exc))
|
|
|
|
|
|
def friendly_sync_transport_message(
|
|
exc: BaseException,
|
|
*,
|
|
server_url: str = "",
|
|
) -> str:
|
|
"""Короткое сообщение для логов (без дампа urllib3)."""
|
|
host = _host_from_server_url(server_url)
|
|
raw = str(exc).strip()
|
|
lower = raw.lower()
|
|
|
|
if "failed to resolve" in lower or "getaddrinfo" in lower or "name resolution" in lower:
|
|
return (
|
|
f"Сервер синхронизации не найден в сети ({host}). "
|
|
"mDNS (.local) в этой сети может не работать — укажите IP сервера в настройках sync."
|
|
)
|
|
if "connection refused" in lower or "errno 111" in lower:
|
|
return (
|
|
f"Сервер {host} недоступен (отказ в подключении). "
|
|
"Убедитесь, что центральный узел запущен и порт верный."
|
|
)
|
|
if "timed out" in lower or "timeout" in lower:
|
|
return f"Таймаут при обращении к серверу синхронизации ({host})."
|
|
if is_server_connection_error_message(raw):
|
|
return f"Нет связи с сервером синхронизации ({host})."
|
|
return raw[:500] if raw else exc.__class__.__name__
|
|
|
|
|
|
def log_sync_transport_problem(
|
|
logger: logging.Logger,
|
|
context: str,
|
|
exc: BaseException,
|
|
*,
|
|
server_url: str = "",
|
|
) -> None:
|
|
"""WARNING для ожидаемых сетевых сбоев, ERROR + traceback для прочего."""
|
|
msg = friendly_sync_transport_message(exc, server_url=server_url)
|
|
if is_transient_sync_transport_error(exc):
|
|
logger.warning("sync: %s: %s", context, msg)
|
|
else:
|
|
logger.error("sync: %s: %s", context, msg, exc_info=True)
|