refactor(backend): external logging metrics, required mode, health checks

- Add external_logging_init_failures counter
- Add external_log_required flag, raise if init fails and required
- Health endpoint: add external_logging status check
- Blocklist service: enrich with metadata fields, update import logic
- Health check task: add runtime_state dependency, fix return typing
- Metrics: add Histogram for request latencies
- Frontend: align BlocklistImportLogSection props
- Docs: update deployment guide, remove stale tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-04 03:45:13 +02:00
parent 42e177e6ea
commit 0a3f9c6c16
15 changed files with 172 additions and 131 deletions

View File

@@ -122,6 +122,10 @@ async def create_source(
at source creation time. The application's HTTP connector performs additional
runtime validation at connection time to prevent DNS-rebinding attacks.
Validation and insert run inside a single DB transaction. If validation
fails or a duplicate URL is detected, the transaction is rolled back and
no row is left behind.
Args:
db: Active application database connection.
name: Human-readable display name.
@@ -133,12 +137,30 @@ async def create_source(
Raises:
ValueError: If the URL fails SSRF validation.
BlocklistSourceAlreadyExistsError: If a source with the same URL already exists.
"""
from app.exceptions import BlocklistSourceAlreadyExistsError
from app.utils.ip_utils import validate_blocklist_url
await validate_blocklist_url(url)
try:
await db.execute("BEGIN IMMEDIATE")
await validate_blocklist_url(url)
try:
new_id = await blocklist_repo.create_source_in_tx(db, name, url, enabled=enabled)
except aiosqlite.IntegrityError as exc:
if "UNIQUE constraint failed" in str(exc):
await db.rollback()
raise BlocklistSourceAlreadyExistsError(url) from exc
raise
await db.commit()
except Exception:
await db.rollback()
raise
new_id = await blocklist_repo.create_source(db, name, url, enabled=enabled)
source = await get_source(db, new_id)
assert source is not None # noqa: S101
log.info("blocklist_source_created", id=new_id, name=name, url=url)