Files
BanGUI/backend/app/tasks/history_sync.py
Lukas 7ec80fdeec refactor(logging): replace structlog with stdlib logging compat layer
- Remove structlog dependency from backend/pyproject.toml
- Add app.utils.logging_compat shim for keyword-arg logging API
- Add app.utils.json_formatter for JSON log output with extra fields
- Update all backend modules to use logging_compat.get_logger()
- Update docstrings in log_sanitizer.py and json_formatter.py
- Update test comment in test_async_utils.py
- Record 406 failing tests in Docs/Tasks.md for tracking
2026-05-10 13:37:54 +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
from app.utils.logging_compat import get_logger
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 = get_logger(__name__)
#: 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)