Overview
The Audit Trail module provides complete system activity tracking for regulatory compliance (21 CFR Part 11, EU Annex 11, ICH E6 R2), with automatic logging of all data changes, user actions, and system events.Architecture
Audit Log Structure
All audit entries follow a standardized schema:AuditLog Model (backend/app/models/audit.py)
The
extra field stores arbitrary JSON data like HTTP status codes, request parameters, or custom metadata.Logging Strategies
Manual Logging (Explicit)
Direct audit log creation:Manual Audit Entry
Decorator-Based Logging
Automatic logging with the@audit decorator:
Audit Decorator (backend/app/services/audit.py:162)
Action identifier (e.g., “ICSR_CREATE”, “USER_LOGIN”)
Module name (e.g., “icsr”, “training”, “reports”)
Callable that generates detail string from result and kwargs
Callable that generates extra metadata dict
Whether to log failed operations (creates
{action}_ERROR entry)Auto-Audit (Router Level)
Automatic auditing for entire routers:Auto-Audit Router (backend/app/services/audit.py:259)
- Automatically wraps all routes in the router
- Skips routes with explicit
@auditdecorator (no double logging) - Skips OPTIONS and HEAD requests
- Uses HTTP method to infer action (GET→READ, POST→CREATE, etc.)
- Logs both successful responses and errors
Actor Resolution
The audit system automatically identifies the user performing actions:Actor Resolution Logic (backend/app/services/audit.py:74)
| Actor Pattern | Description | Example |
|---|---|---|
[email protected] | Regular user | [email protected] |
system@vigia | System-initiated action | Scheduler, migrations |
system+job_name@vigia | Background job | system+email_poller@vigia |
Custom (via x-actor) | External integration | api_client_pharma_co@external |
Filtering & Skip Patterns
Environment Configuration
Audit Configuration
Runtime Filtering
Skip audit for specific requests:Request-Level Skip
Filter Priority
Querying Audit Logs
List with Filters
Query Audit Logs (GET /api/v1/security/audit)
Full-text search across
who_email, action, module, detailFilter by exact or partial email (case-insensitive)
Filter by exact action name
Start date/time (ISO 8601 format)
End date/time (ISO 8601 format)
Max results (1-10000)
Pagination offset
Export to CSV
Export Audit Logs (GET /api/v1/security/audit/export)
Compliance Features
21 CFR Part 11 Requirements
VIGIA Audit Trail Compliance:§11.10(e) - Audit Trail:
- ✅ Records creation, modification, deletion of data
- ✅ Timestamps in UTC (unambiguous)
- ✅ User identification (email-based)
- ✅ Reason for change (captured in
detailfield)
- ✅ Validates user identity (via auth module)
- ✅ Logs all access attempts (including failures)
- ✅ Human-readable format (detail field)
- ✅ CSV export for regulatory submission
- ✅ Cannot be modified (append-only table)
- ✅ No delete functionality exposed
- ✅ Database-level constraints (no UPDATE/DELETE permissions for app user)
EU Annex 11 Compliance
Principle 9 - Audit Trail:
- ✅ Records who, what, when, why
- ✅ Protects against unauthorized changes
- ✅ Regular review capability (query API)
- ✅ Retention aligned with data retention policy
ICH E6 (R2) GCP Guidelines
Section 5.5.3(e) - Record Keeping:
- ✅ Permits reconstruction of trial conduct
- ✅ Source data verification support
- ✅ Audit trail for all ICSR modifications
- ✅ Change justification (detail field)
Database Safety
Append-Only Design
Audit logs useadd() + flush() without commit():
Safe Audit Write (backend/app/services/audit.py:148)
- Parent transaction may still rollback (if business logic fails)
- Audit should succeed even if parent transaction fails
- Flush writes to DB but keeps transaction open
- Rollback cleans up without affecting parent
JSON Serialization
The_jsonable() function ensures safe JSON storage:
Safe JSON Conversion (backend/app/services/audit.py:44)
Common Action Naming
Standardized action names across modules:- ICSR
- Training
- System
- Reports
ICSR_CREATE- New case createdICSR_UPDATE- Case modifiedICSR_DELETE- Case deleted (soft delete)ICSR_STATUS_CHANGE- Workflow state transitionICSR_EVALUATE- Causality assessmentICSR_EXPORT- Report generationICSR_APPROVE- QA approvalICSR_REJECT- QA rejection
Monitoring & Alerts
Suspicious Activity Detection
Audit Alert Rules
Performance Metrics
Audit Log Performance
Best Practices
Action Naming
- Use
{MODULE}_{VERB}pattern - Uppercase for consistency
- Append
_ERRORfor failed operations - Be specific:
ICSR_APPROVEnotICSR_ACTION
Detail Messages
- Include entity ID:
"ICSR #1024" - Show old → new for updates:
"Leve → Grave" - Keep under 255 chars (detail field limit)
- Use
extrafield for verbose data
Performance
- Don’t log high-frequency operations (e.g., heartbeats)
- Use
AUDIT_IGNORE_PATHS_REfor health checks - Index
created_atcolumn for date queries - Partition table by date for large datasets
Security
- Never log passwords or tokens
- Sanitize PII in detail field
- Use
extrafield for sensitive data (encrypted storage) - Restrict audit query API to admin/auditor roles
Troubleshooting
Audit Logs Not Created
Audit Logs Not Created
Checklist:
-
AUDIT_ENABLED=true? - Request has
x-audit: 0header? - Path matches
AUDIT_IGNORE_PATHS_RE? - Actor in
AUDIT_IGNORE_ACTORS? - Database connection healthy?
Duplicate Audit Entries
Duplicate Audit Entries
Cause: Both
@audit decorator and auto_audit() appliedFix: Remove one:Query Timeout on Large Dataset
Query Timeout on Large Dataset
Solution 1: Add indexSolution 2: Partition table (PostgreSQL)Solution 3: Archive old data
Related Documentation
Authentication
User identity and role-based access control
Compliance
Regulatory requirements and validation