Files
BanGUI/backend/app/tasks/history_sync.py
Lukas 0133489920 Update observability docs and task utilities
- Add Observability.md documentation
- Standardize task logging with correlation_id support
- Add log_sanitizer utility for PII masking
- Update Tasks.md tracking
- Update geo_cache tasks and other task modules with correlation_id

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-03 11:52:09 +02:00

94 lines
2.8 KiB
Python

"""History sync background task.
Periodically copies new records from the fail2ban sqlite database into the
BanGUI application archive table to prevent gaps when fail2ban purges old rows.
Correlation IDs are propagated through the task using :mod:`app.utils.correlation`
so that task logs can be correlated across runs.
"""
from __future__ import annotations
import datetime
import uuid
from typing import TYPE_CHECKING
import structlog
from app.services import history_service
from app.tasks.db import task_db
from app.tasks.timeout_utils import run_with_timeout
from app.utils.correlation import get_correlation_id, reset_correlation_id, set_correlation_id
from app.utils.runtime_state import get_effective_settings
if TYPE_CHECKING:
from fastapi import FastAPI
from app.config import Settings
log: structlog.stdlib.BoundLogger = structlog.get_logger()
#: Stable APScheduler job id.
JOB_ID: str = "history_sync"
#: Interval in seconds between sync runs.
HISTORY_SYNC_INTERVAL: int = 300
#: Backfill window when archive is empty (seconds).
BACKFILL_WINDOW: int = 648000
#: Maximum seconds to allow for history sync to complete.
TASK_TIMEOUT_SECONDS: int = 60
async def _run_sync_with_settings(
settings: Settings,
correlation_id: str | None = None,
) -> None:
"""Run the history sync with correlation ID context."""
if correlation_id is None:
correlation_id = str(uuid.uuid4())
token = set_correlation_id(correlation_id)
try:
await _do_sync_with_settings(settings)
finally:
reset_correlation_id(token)
async def _do_sync_with_settings(settings: Settings) -> None:
"""Inner sync logic that runs with correlation context set."""
socket_path: str = settings.fail2ban_socket
async def _do_sync() -> None:
try:
async with task_db(settings) as db:
synced = await history_service.sync_from_fail2ban_db(db, socket_path)
log.info("history_sync_complete", correlation_id=get_correlation_id(), synced=synced)
except Exception:
log.exception("history_sync_failed", correlation_id=get_correlation_id())
await run_with_timeout("history_sync", _do_sync(), TASK_TIMEOUT_SECONDS)
async def _run_sync(app: FastAPI) -> None:
await _run_sync_with_settings(get_effective_settings(app))
def register(app: FastAPI) -> None:
"""Register the history sync periodic job.
Should be called after scheduler startup, from the lifespan handler.
"""
settings = get_effective_settings(app)
app.state.scheduler.add_job(
_run_sync_with_settings,
trigger="interval",
seconds=HISTORY_SYNC_INTERVAL,
kwargs={"settings": settings},
id=JOB_ID,
replace_existing=True,
next_run_time=datetime.datetime.now(tz=datetime.UTC),
)
log.info("history_sync_scheduled", interval_seconds=HISTORY_SYNC_INTERVAL)