@@ -0,0 +1,138 @@
|
||||
"""Фильтрация и человекочитаемые сообщения об ошибках синхронизации (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)
|
||||
Reference in New Issue
Block a user