30 lines
1.0 KiB
Python
30 lines
1.0 KiB
Python
"""Helpers shared by collectors: failure isolation and timing."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import time
|
|
from collections.abc import Callable
|
|
|
|
from us2kr.logging_setup import mask_secrets
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def run_safely[T](name: str, fn: Callable[[], T], default: T, gaps: list[str]) -> T:
|
|
"""Run a collector; on any exception log it, append a gap note and return `default`.
|
|
|
|
The pipeline must never die because one data source is down.
|
|
"""
|
|
t0 = time.monotonic()
|
|
try:
|
|
result = fn()
|
|
log.info("collector %s ok in %.1fs", name, time.monotonic() - t0)
|
|
return result
|
|
except Exception as exc: # deliberate: isolate every failure
|
|
log.exception("collector %s failed", name)
|
|
# Gap notes end up in the briefing / Telegram / LLM prompt: never let a URL with a key through.
|
|
detail = mask_secrets(str(exc)).split(" for url: ", 1)[0][:120]
|
|
gaps.append(f"{name}: {type(exc).__name__}: {detail}")
|
|
return default
|