This commit is contained in:
2026-01-31 19:57:09 -08:00
parent 6513a3ad04
commit 1dbaabac73
7 changed files with 125 additions and 88 deletions

View File

@@ -12,6 +12,8 @@ from __future__ import annotations
from typing import Any, Callable, Dict, Optional, Set from typing import Any, Callable, Dict, Optional, Set
from SYS.logger import log, debug from SYS.logger import log, debug
import logging
logger = logging.getLogger(__name__)
class BackgroundNotifier: class BackgroundNotifier:
@@ -103,7 +105,7 @@ class BackgroundNotifier:
try: try:
self.output(line) self.output(line)
except Exception: except Exception:
pass logger.exception("BackgroundNotifier failed to output overlay line for worker %s", worker_id)
self._last_state.pop(worker_id, None) self._last_state.pop(worker_id, None)
self.session_worker_ids.discard(worker_id) self.session_worker_ids.discard(worker_id)
@@ -120,7 +122,7 @@ class BackgroundNotifier:
try: try:
self.output(line) self.output(line)
except Exception: except Exception:
pass logger.exception("BackgroundNotifier failed to output terminal-only line for worker %s", worker_id)
# Stop tracking this worker after terminal notification # Stop tracking this worker after terminal notification
self.session_worker_ids.discard(worker_id) self.session_worker_ids.discard(worker_id)
continue continue
@@ -148,7 +150,7 @@ class BackgroundNotifier:
try: try:
self.output(line) self.output(line)
except Exception: except Exception:
pass logger.exception("BackgroundNotifier failed to output line for worker %s", worker_id)
if self.overlay_mode: if self.overlay_mode:
try: try:
@@ -156,7 +158,7 @@ class BackgroundNotifier:
if overlay_active_workers == 0: if overlay_active_workers == 0:
self.output("") self.output("")
except Exception: except Exception:
pass logger.exception("BackgroundNotifier failed to clear overlay for session")
def ensure_background_notifier( def ensure_background_notifier(
@@ -201,6 +203,6 @@ def ensure_background_notifier(
) )
try: try:
manager._background_notifier = notifier # type: ignore[attr-defined] manager._background_notifier = notifier # type: ignore[attr-defined]
except Exception: except Exception as exc:
pass logger.exception("Failed to attach background notifier to manager: %s", exc)
return notifier return notifier

View File

@@ -8,6 +8,7 @@ from __future__ import annotations
import re import re
from typing import Any, Callable, Dict, List, Optional, Set, Tuple from typing import Any, Callable, Dict, List, Optional, Set, Tuple
from SYS.logger import debug
# Prompt-toolkit lexer types are optional at import time; fall back to lightweight # Prompt-toolkit lexer types are optional at import time; fall back to lightweight
# stubs if prompt_toolkit is not available so imports remain safe for testing. # stubs if prompt_toolkit is not available so imports remain safe for testing.
@@ -130,8 +131,8 @@ class SelectionFilterSyntax:
try: try:
if SelectionSyntax.parse(str(token)) is not None: if SelectionSyntax.parse(str(token)) is not None:
return None return None
except Exception: except Exception as exc:
pass debug("SelectionSyntax.parse failed during filter detection: %s", exc, exc_info=True)
raw = str(token)[1:].strip() raw = str(token)[1:].strip()
if not raw: if not raw:
@@ -211,8 +212,8 @@ class SelectionFilterSyntax:
for k in ("title", "path", "detail", "provider", "store", "table"): for k in ("title", "path", "detail", "provider", "store", "table"):
try: try:
_set(k, getattr(item, k, None)) _set(k, getattr(item, k, None))
except Exception: except Exception as exc:
pass debug("SelectionFilterSyntax: failed to _set attribute %s on item: %s", k, exc, exc_info=True)
return out return out

View File

@@ -3,6 +3,8 @@ from __future__ import annotations
from importlib import import_module from importlib import import_module
from types import ModuleType from types import ModuleType
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import logging
logger = logging.getLogger(__name__)
try: try:
from .config import get_local_storage_path from .config import get_local_storage_path
@@ -31,7 +33,8 @@ def _get_cmdlet_package() -> Optional[ModuleType]:
return _cmdlet_pkg return _cmdlet_pkg
try: try:
_cmdlet_pkg = import_module("cmdlet") _cmdlet_pkg = import_module("cmdlet")
except Exception: except Exception as exc:
logger.exception("Failed to import cmdlet package: %s", exc)
_cmdlet_pkg = None _cmdlet_pkg = None
return _cmdlet_pkg return _cmdlet_pkg
@@ -52,8 +55,8 @@ def ensure_registry_loaded(force: bool = False) -> None:
if callable(ensure_fn): if callable(ensure_fn):
try: try:
ensure_fn(force=force) ensure_fn(force=force)
except Exception: except Exception as exc:
pass logger.exception("ensure_registry_loaded: ensure_cmdlet_modules_loaded failed: %s", exc)
def _normalize_mod_name(mod_name: str) -> str: def _normalize_mod_name(mod_name: str) -> str:
@@ -76,7 +79,8 @@ def import_cmd_module(mod_name: str):
return import_module(qualified) return import_module(qualified)
except ModuleNotFoundError: except ModuleNotFoundError:
continue continue
except Exception: except Exception as exc:
logger.exception("Unexpected error importing module %s: %s", qualified, exc)
continue continue
return None return None
@@ -127,7 +131,8 @@ def get_cmdlet_metadata(
if owner_mod: if owner_mod:
owner = import_module(owner_mod) owner = import_module(owner_mod)
data = getattr(owner, "CMDLET", None) data = getattr(owner, "CMDLET", None)
except Exception: except Exception as exc:
logger.exception("Registry fallback failed while resolving cmdlet %s: %s", cmd_name, exc)
data = None data = None
if not data: if not data:
@@ -303,14 +308,16 @@ def get_cmdlet_arg_choices(
from SYS.config import load_config from SYS.config import load_config
config = load_config() config = load_config()
except Exception: except Exception as exc:
logger.exception("Failed to load config for matrix default choices: %s", exc)
config = config or {} config = config or {}
matrix_conf = {} matrix_conf = {}
try: try:
providers = config.get("provider") or {} providers = config.get("provider") or {}
matrix_conf = providers.get("matrix") or {} matrix_conf = providers.get("matrix") or {}
except Exception: except Exception as exc:
logger.exception("Failed to read matrix provider config: %s", exc)
matrix_conf = {} matrix_conf = {}
raw = None raw = None
@@ -328,7 +335,8 @@ def get_cmdlet_arg_choices(
import re import re
ids = [p.strip() for p in re.split(r"[,\s]+", text) if p and p.strip()] ids = [p.strip() for p in re.split(r"[,\s]+", text) if p and p.strip()]
except Exception: except Exception as exc:
logger.exception("Failed to parse matrix room ids from config: %r", raw)
ids = [] ids = []
if ids: if ids:
@@ -350,12 +358,12 @@ def get_cmdlet_arg_choices(
choices.append(name or rid) choices.append(name or rid)
if choices: if choices:
return choices return choices
except Exception: except Exception as exc:
pass logger.exception("Matrix provider failed while listing rooms: %s", exc)
except Exception: except Exception as exc:
pass logger.exception("Failed to import Matrix provider or initialize: %s", exc)
except Exception: except Exception as exc:
pass logger.exception("Failed to resolve matrix rooms: %s", exc)
# Fallback: return raw ids as choices # Fallback: return raw ids as choices
return ids return ids

View File

@@ -15,6 +15,8 @@ from copy import deepcopy
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from SYS.logger import log from SYS.logger import log
import logging
logger = logging.getLogger(__name__)
from SYS.utils import expand_path from SYS.utils import expand_path
from SYS.database import db, get_config_all, save_config_value, rows_to_config from SYS.database import db, get_config_all, save_config_value, rows_to_config
@@ -186,16 +188,16 @@ def resolve_output_dir(config: Dict[str, Any]) -> Path:
# Verify we can access it (not a system directory with permission issues) # Verify we can access it (not a system directory with permission issues)
if path.exists() or path.parent.exists(): if path.exists() or path.parent.exists():
return path return path
except Exception: except Exception as exc:
pass logger.debug("resolve_output_dir: failed to expand temp value %r: %s", temp_value, exc, exc_info=True)
# Then try outfile setting # Then try outfile setting
outfile_value = config.get("outfile") outfile_value = config.get("outfile")
if outfile_value: if outfile_value:
try: try:
return expand_path(outfile_value) return expand_path(outfile_value)
except Exception: except Exception as exc:
pass logger.debug("resolve_output_dir: failed to expand outfile value %r: %s", outfile_value, exc, exc_info=True)
# Fallback to system temp directory # Fallback to system temp directory
return Path(tempfile.gettempdir()) return Path(tempfile.gettempdir())
@@ -313,8 +315,8 @@ def resolve_cookies_path(
values: list[Any] = [] values: list[Any] = []
try: try:
values.append(config.get("cookies")) values.append(config.get("cookies"))
except Exception: except Exception as exc:
pass logger.debug("resolve_cookies_path: failed to read top-level cookies: %s", exc, exc_info=True)
try: try:
tool = config.get("tool") tool = config.get("tool")
@@ -323,16 +325,16 @@ def resolve_cookies_path(
if isinstance(ytdlp, dict): if isinstance(ytdlp, dict):
values.append(ytdlp.get("cookies")) values.append(ytdlp.get("cookies"))
values.append(ytdlp.get("cookiefile")) values.append(ytdlp.get("cookiefile"))
except Exception: except Exception as exc:
pass logger.debug("resolve_cookies_path: failed to read tool.ytdlp cookies: %s", exc, exc_info=True)
try: try:
ytdlp_block = config.get("ytdlp") ytdlp_block = config.get("ytdlp")
if isinstance(ytdlp_block, dict): if isinstance(ytdlp_block, dict):
values.append(ytdlp_block.get("cookies")) values.append(ytdlp_block.get("cookies"))
values.append(ytdlp_block.get("cookiefile")) values.append(ytdlp_block.get("cookiefile"))
except Exception: except Exception as exc:
pass logger.debug("resolve_cookies_path: failed to read ytdlp cookies block: %s", exc, exc_info=True)
base_dir = script_dir or SCRIPT_DIR base_dir = script_dir or SCRIPT_DIR
for value in values: for value in values:
@@ -488,7 +490,7 @@ def load_config() -> Dict[str, Any]:
# Forensics disabled: audit/mismatch/backup detection removed to simplify code. # Forensics disabled: audit/mismatch/backup detection removed to simplify code.
except Exception: except Exception:
pass logger.exception("Failed to build config load summary from %s", db.db_path)
return db_config return db_config
_LAST_SAVED_CONFIG = {} _LAST_SAVED_CONFIG = {}
@@ -518,8 +520,8 @@ def _acquire_save_lock(timeout: float = _SAVE_LOCK_TIMEOUT):
"ts": time.time(), "ts": time.time(),
"cmdline": " ".join(sys.argv), "cmdline": " ".join(sys.argv),
})) }))
except Exception: except Exception as exc:
pass logger.exception("Failed to write save lock owner metadata %s: %s", lock_dir, exc)
return lock_dir return lock_dir
except FileExistsError: except FileExistsError:
# Check for stale lock # Check for stale lock
@@ -533,8 +535,8 @@ def _acquire_save_lock(timeout: float = _SAVE_LOCK_TIMEOUT):
import shutil import shutil
shutil.rmtree(lock_dir) shutil.rmtree(lock_dir)
continue continue
except Exception: except Exception as exc:
pass logger.exception("Failed to remove stale save lock dir %s", lock_dir)
else: else:
# No owner file; if directory is old enough consider it stale # No owner file; if directory is old enough consider it stale
try: try:
@@ -542,10 +544,10 @@ def _acquire_save_lock(timeout: float = _SAVE_LOCK_TIMEOUT):
import shutil import shutil
shutil.rmtree(lock_dir) shutil.rmtree(lock_dir)
continue continue
except Exception: except Exception as exc:
pass logger.exception("Failed to stat/remove stale save lock dir %s", lock_dir)
except Exception: except Exception as exc:
pass logger.exception("Failed to inspect save lock directory %s: %s", lock_dir, exc)
if time.time() - start > timeout: if time.time() - start > timeout:
raise ConfigSaveConflict("Save lock busy; could not acquire in time") raise ConfigSaveConflict("Save lock busy; could not acquire in time")
time.sleep(0.1) time.sleep(0.1)
@@ -558,10 +560,10 @@ def _release_save_lock(lock_dir: Path) -> None:
if owner.exists(): if owner.exists():
owner.unlink() owner.unlink()
except Exception: except Exception:
pass logger.exception("Failed to remove save lock owner file %s", owner)
lock_dir.rmdir() lock_dir.rmdir()
except Exception: except Exception:
pass logger.exception("Failed to release save lock directory %s", lock_dir)
def save_config(config: Dict[str, Any]) -> int: def save_config(config: Dict[str, Any]) -> int:
@@ -681,8 +683,8 @@ def save_config(config: Dict[str, Any]) -> int:
try: try:
if lock_dir is not None and lock_dir.exists(): if lock_dir is not None and lock_dir.exists():
_release_save_lock(lock_dir) _release_save_lock(lock_dir)
except Exception: except Exception as exc:
pass logger.exception("Failed to release save lock during save flow: %s", exc)
break break
except sqlite3.OperationalError as exc: except sqlite3.OperationalError as exc:
@@ -694,8 +696,8 @@ def save_config(config: Dict[str, Any]) -> int:
try: try:
if lock_dir is not None and lock_dir.exists(): if lock_dir is not None and lock_dir.exists():
_release_save_lock(lock_dir) _release_save_lock(lock_dir)
except Exception: except Exception as exc:
pass logger.exception("Failed to release save lock after DB write failure: %s", exc)
raise raise
delay = _CONFIG_SAVE_RETRY_DELAY * attempts delay = _CONFIG_SAVE_RETRY_DELAY * attempts
log(f"Database locked; retry {attempts}/{_CONFIG_SAVE_MAX_RETRIES} in {delay:.2f}s") log(f"Database locked; retry {attempts}/{_CONFIG_SAVE_MAX_RETRIES} in {delay:.2f}s")
@@ -705,8 +707,8 @@ def save_config(config: Dict[str, Any]) -> int:
try: try:
if lock_dir is not None and lock_dir.exists(): if lock_dir is not None and lock_dir.exists():
_release_save_lock(lock_dir) _release_save_lock(lock_dir)
except Exception: except Exception as exc:
pass logger.exception("Failed to release save lock after CRITICAL configuration save failure: %s", exc)
raise raise
clear_config_cache() clear_config_cache()
@@ -762,7 +764,8 @@ def save_config_and_verify(config: Dict[str, Any], retries: int = 3, delay: floa
break break
elif isinstance(srv, str) and srv.strip(): elif isinstance(srv, str) and srv.strip():
expected_key = srv.strip() expected_key = srv.strip()
except Exception: except Exception as exc:
logger.debug("Failed to determine expected key for save verification: %s", exc, exc_info=True)
expected_key = None expected_key = None
last_exc: Exception | None = None last_exc: Exception | None = None

View File

@@ -12,6 +12,8 @@ from contextlib import contextmanager
import time import time
import datetime import datetime
from SYS.logger import debug, log from SYS.logger import debug, log
import logging
logger = logging.getLogger(__name__)
# DB execute retry settings (for transient 'database is locked' errors) # DB execute retry settings (for transient 'database is locked' errors)
_DB_EXEC_RETRY_MAX = 5 _DB_EXEC_RETRY_MAX = 5
@@ -29,8 +31,8 @@ def _resolve_root_dir() -> Path:
candidate = Path(env_root).expanduser().resolve() candidate = Path(env_root).expanduser().resolve()
if candidate.exists(): if candidate.exists():
return candidate return candidate
except Exception: except Exception as exc:
pass logger.debug("_resolve_root_dir: failed to resolve env_root %r: %s", env_root, exc, exc_info=True)
cwd = Path.cwd().resolve() cwd = Path.cwd().resolve()
for base in [cwd, *cwd.parents]: for base in [cwd, *cwd.parents]:
@@ -173,23 +175,24 @@ class Database:
try: try:
if not self.conn.in_transaction: if not self.conn.in_transaction:
self.conn.rollback() self.conn.rollback()
except Exception: except Exception as exc:
pass logger.exception("Rollback failed while retrying locked execute: %s", exc)
time.sleep(delay) time.sleep(delay)
continue continue
# Not recoverable or out of retries # Not recoverable or out of retries
if not self.conn.in_transaction: if not self.conn.in_transaction:
try: try:
self.conn.rollback() self.conn.rollback()
except Exception: except Exception as exc:
pass logger.exception("Rollback failed in non-recoverable execute path: %s", exc)
raise raise
except Exception: except Exception as exc:
if not self.conn.in_transaction: if not self.conn.in_transaction:
try: try:
self.conn.rollback() self.conn.rollback()
except Exception: except Exception as rb_exc:
pass logger.exception("Rollback failed during unexpected execute exception: %s", rb_exc)
logger.exception("Unexpected exception during DB execute: %s", exc)
raise raise
def executemany(self, query: str, param_list: List[tuple]): def executemany(self, query: str, param_list: List[tuple]):
@@ -211,22 +214,23 @@ class Database:
try: try:
if not self.conn.in_transaction: if not self.conn.in_transaction:
self.conn.rollback() self.conn.rollback()
except Exception: except Exception as exc:
pass logger.exception("Rollback failed while retrying locked executemany: %s", exc)
time.sleep(delay) time.sleep(delay)
continue continue
if not self.conn.in_transaction: if not self.conn.in_transaction:
try: try:
self.conn.rollback() self.conn.rollback()
except Exception: except Exception as exc:
pass logger.exception("Rollback failed in non-recoverable executemany path: %s", exc)
raise raise
except Exception: except Exception as exc:
if not self.conn.in_transaction: if not self.conn.in_transaction:
try: try:
self.conn.rollback() self.conn.rollback()
except Exception: except Exception as rb_exc:
pass logger.exception("Rollback failed during unexpected executemany exception: %s", rb_exc)
logger.exception("Unexpected exception during DB executemany: %s", exc)
raise raise
@contextmanager @contextmanager
@@ -255,7 +259,7 @@ class Database:
try: try:
self._conn_lock.release() self._conn_lock.release()
except Exception: except Exception:
pass logger.exception("Failed to release DB connection lock")
def fetchall(self, query: str, params: tuple = ()): def fetchall(self, query: str, params: tuple = ()):
with self._conn_lock: with self._conn_lock:
@@ -321,15 +325,26 @@ def _log_worker_loop() -> None:
fallback_file = fallback_dir / "log_fallback.txt" fallback_file = fallback_dir / "log_fallback.txt"
with fallback_file.open("a", encoding="utf-8") as fh: with fallback_file.open("a", encoding="utf-8") as fh:
fh.write(f"{datetime.datetime.utcnow().isoformat()}Z [{level}] {module}: {message}\n") fh.write(f"{datetime.datetime.utcnow().isoformat()}Z [{level}] {module}: {message}\n")
except Exception: except Exception as exc:
# Last resort: print to stderr # Last resort: print to stderr
try: try:
log(f"ERROR: Could not persist log message: {level} {module} {message}") log(f"ERROR: Could not persist log message: {level} {module} {message}")
except Exception: except Exception:
pass pass
try:
import sys as _sys, traceback as _tb
_sys.stderr.write(f"CRITICAL: Could not persist log message to fallback file: {exc}\n")
_tb.print_exc(file=_sys.stderr)
except Exception:
pass
finally: finally:
try: try:
_LOG_QUEUE.task_done() _LOG_QUEUE.task_done()
except Exception as exc:
try:
import sys as _sys, traceback as _tb
_sys.stderr.write(f"CRITICAL: Failed to mark log task done: {exc}\n")
_tb.print_exc(file=_sys.stderr)
except Exception: except Exception:
pass pass
@@ -433,7 +448,8 @@ def rows_to_config(rows) -> Dict[str, Any]:
parsed_val = val parsed_val = val
except Exception: except Exception:
parsed_val = val parsed_val = val
except Exception: except Exception as exc:
logger.debug("rows_to_config: failed to parse value for key %s; using raw value", key, exc_info=True)
parsed_val = val parsed_val = val
if cat == 'global': if cat == 'global':
@@ -469,7 +485,8 @@ def insert_worker(worker_id: str, worker_type: str, title: str = "", description
(worker_id, worker_type, title, description) (worker_id, worker_type, title, description)
) )
return True return True
except Exception: except Exception as exc:
logger.exception("Failed to insert worker %s: %s", worker_id, exc)
return False return False
def update_worker(worker_id: str, **kwargs) -> bool: def update_worker(worker_id: str, **kwargs) -> bool:
@@ -495,7 +512,8 @@ def update_worker(worker_id: str, **kwargs) -> bool:
try: try:
db.execute(query, tuple(vals)) db.execute(query, tuple(vals))
return True return True
except Exception: except Exception as exc:
logger.exception("Failed to update worker %s: %s", worker_id, exc)
return False return False
def append_worker_stdout(worker_id: str, content: str, channel: str = 'stdout'): def append_worker_stdout(worker_id: str, content: str, channel: str = 'stdout'):
@@ -504,8 +522,8 @@ def append_worker_stdout(worker_id: str, content: str, channel: str = 'stdout'):
"INSERT INTO worker_stdout (worker_id, channel, content) VALUES (?, ?, ?)", "INSERT INTO worker_stdout (worker_id, channel, content) VALUES (?, ?, ?)",
(worker_id, channel, content) (worker_id, channel, content)
) )
except Exception: except Exception as exc:
pass logger.exception("Failed to append worker stdout for %s", worker_id)
def get_worker_stdout(worker_id: str, channel: Optional[str] = None) -> str: def get_worker_stdout(worker_id: str, channel: Optional[str] = None) -> str:
query = "SELECT content FROM worker_stdout WHERE worker_id = ?" query = "SELECT content FROM worker_stdout WHERE worker_id = ?"

View File

@@ -19,6 +19,7 @@ from typing import Any, Dict, List, Optional, Tuple
from lxml import html as lxml_html from lxml import html as lxml_html
from urllib.parse import urljoin from urllib.parse import urljoin
import re import re
from SYS.logger import debug
# Default xpaths for candidate result containers # Default xpaths for candidate result containers
_DEFAULT_XPATHS = [ _DEFAULT_XPATHS = [
@@ -50,8 +51,8 @@ def _text_or_img_title(el) -> str:
imgs = el.xpath('.//img/@title') imgs = el.xpath('.//img/@title')
if imgs: if imgs:
return str(imgs[0]).strip() return str(imgs[0]).strip()
except Exception: except Exception as exc:
pass debug("Failed to retrieve img title from element: %s", exc, exc_info=True)
return (el.text_content() or "").strip() return (el.text_content() or "").strip()
@@ -66,7 +67,8 @@ def find_candidate_nodes(doc_or_html: Any, xpaths: Optional[List[str]] = None) -
found = doc.xpath(xp) found = doc.xpath(xp)
if found: if found:
return list(found), xp return list(found), xp
except Exception: except Exception as exc:
debug("Failed to execute xpath %s: %s", xp, exc, exc_info=True)
continue continue
return [], None return [], None
@@ -214,7 +216,8 @@ def extract_records(doc_or_html: Any, base_url: Optional[str] = None, xpaths: Op
df = max(dfs, key=lambda d: getattr(d, "shape", (len(getattr(d, 'index', [])), 0))[0]) df = max(dfs, key=lambda d: getattr(d, "shape", (len(getattr(d, 'index', [])), 0))[0])
try: try:
rows = df.to_dict("records") rows = df.to_dict("records")
except Exception: except Exception as exc:
debug("pandas HTML table parse: df.to_dict('records') failed: %s", exc, exc_info=True)
# Some DataFrame-like objects may have slightly different APIs # Some DataFrame-like objects may have slightly different APIs
rows = [dict(r) for r in df] rows = [dict(r) for r in df]
@@ -240,13 +243,13 @@ def extract_records(doc_or_html: Any, base_url: Optional[str] = None, xpaths: Op
href = anchors.get(rec["title"]) href = anchors.get(rec["title"])
if href: if href:
rec["path"] = urljoin(base_url, href) if base_url else href rec["path"] = urljoin(base_url, href) if base_url else href
except Exception: except Exception as exc:
pass debug("pandas: failed to recover anchor hrefs for table rows: %s", exc, exc_info=True)
return records, "pandas" return records, "pandas"
except Exception: except Exception as exc:
# Pandas not present or parsing failed; fall back to node parsing # Pandas not present or parsing failed; fall back to node parsing
pass debug("pandas: not available or parsing failed: %s", exc, exc_info=True)
# Fallback to node-based parsing # Fallback to node-based parsing
nodes, chosen = find_candidate_nodes(doc_or_html, xpaths=xpaths) nodes, chosen = find_candidate_nodes(doc_or_html, xpaths=xpaths)

View File

@@ -52,7 +52,8 @@ def suspend_live_progress():
try: try:
ui.pause() ui.pause()
paused = True paused = True
except Exception: except Exception as exc:
logger.exception("Failed to pause live progress UI: %s", exc)
paused = False paused = False
yield yield
finally: finally:
@@ -1427,7 +1428,8 @@ class PipelineExecutor:
logger.exception("is_known_provider_name predicate failed for key %s; falling back", key) logger.exception("is_known_provider_name predicate failed for key %s; falling back", key)
try: try:
provider = get_provider(key, config) provider = get_provider(key, config)
except Exception: except Exception as exc:
logger.exception("Failed to load provider '%s' during selector resolution: %s", key, exc)
continue continue
selector = getattr(provider, "selector", None) selector = getattr(provider, "selector", None)
if selector is None: if selector is None:
@@ -1439,7 +1441,7 @@ class PipelineExecutor:
stage_is_last=True) stage_is_last=True)
) )
except Exception as exc: except Exception as exc:
print(f"{key} selector failed: {exc}\n") logger.exception("%s selector failed during selection: %s", key, exc)
return True return True
if handled: if handled:
return True return True