feat(phase-4): add write_audit_log() async helper (flush-not-commit, never-raises)

- Creates backend/services/audit.py with write_audit_log() function
- Uses session.flush() not session.commit() per D-14 architectural requirement
- Catches and logs all exceptions (never re-raises) so audit failure is non-fatal
- Correct AuditLog ORM attribute metadata_ (not metadata) per models.py
This commit is contained in:
curo1305
2026-05-25 18:33:31 +02:00
parent 0ed514907f
commit 259a1542d8
+58
View File
@@ -0,0 +1,58 @@
"""
Audit log service helper for DocuVault — Phase 4.
Provides write_audit_log(), a fire-and-forget helper called inline by API
handlers after successful operations (D-14).
Key architectural constraints:
- Uses session.flush() NOT session.commit() (D-14: the caller owns the
transaction; the audit entry is flushed within the same transaction but
the commit remains the caller's responsibility).
- NEVER raises — audit failure must not abort the primary operation.
Exceptions are logged at WARNING level and silently swallowed.
"""
from __future__ import annotations
import logging
import uuid
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from db.models import AuditLog
logger = logging.getLogger(__name__)
async def write_audit_log(
session: AsyncSession,
event_type: str,
user_id: Optional[uuid.UUID],
actor_id: Optional[uuid.UUID],
resource_id: Optional[uuid.UUID],
ip_address: Optional[str],
metadata_: Optional[dict] = None,
) -> None:
"""Write an audit log entry within the caller's transaction.
Never raises — audit failure is non-fatal. Exceptions are caught and
logged as warnings so the primary operation is never aborted.
Uses session.flush() not session.commit() (D-14). The caller is responsible
for committing the transaction; this function merely ensures the audit row
is queued in the same unit of work.
"""
try:
entry = AuditLog(
event_type=event_type,
user_id=user_id,
actor_id=actor_id,
resource_id=resource_id,
ip_address=ip_address,
metadata_=metadata_,
)
session.add(entry)
await session.flush() # flush within handler's existing transaction, not commit
except Exception as exc:
logger.warning("audit log write failed: %s", exc)
# Do not re-raise — audit failure must never abort the primary operation