Audit Logs
Every data change in Metrognome is automatically recorded — who made it, when, and what changed. Audit logs are append-only and cannot be modified or deleted, providing a tamper-proof history for compliance and troubleshooting.
Viewing Audit Logs
Go to Admin → Audit Logs to see the full activity log.
Each entry shows:
| Column | What It Shows |
|---|---|
| Timestamp | When the change happened (displayed in your browser's timezone) |
| Table | Which data table was affected (e.g., reservations, users) |
| Record | The ID of the specific record that changed |
| Action | What happened — INSERT (created), UPDATE (modified), or DELETE (removed) |
| User | Who made the change, or "system" for automated actions |
| Source | Where the change came from — staff dashboard, member app, or API |
If a change was made during an impersonation session, the User column shows an (imp) indicator.
Filtering
Use the controls at the top of the page to narrow down results:
- Table dropdown — Filter to a specific data table (e.g., show only
reservationschanges) - Action dropdown — Filter by INSERT, UPDATE, or DELETE
- Search by Record ID — Find all changes to a specific record
Pagination defaults to 25 entries per page.
Entry Details
Click any row to see the full details of that change:
- Audit Log ID — Unique identifier for this log entry
- Table and Record ID — What was changed
- Action — INSERT, UPDATE, or DELETE
- User and Impersonator — Who made the change (and who was impersonating, if applicable)
- Source — staff, member, or api
- Request ID — Correlates multiple changes from the same request
- Timestamp — When the change occurred
- Previous Data — For updates, shows only the fields that changed and their previous values. Null for inserts and deletes
- Data — Full snapshot of the record after the change
What's Tracked
Audit logs capture changes across all core tables including users, reservations, locations, resources, payments, credits, access codes, assets, organizations, and migrations — 43 tables in total.
Changes that don't actually modify any data (e.g., saving a form without making edits) are automatically skipped.
Sources
The Source field tells you how a change was made:
| Source | Meaning |
|---|---|
| staff | Change made through the staff dashboard |
| member | Change made by a member through the customer app |
| api | Change made by an API client or webhook |
Common Lookups
Track changes to a specific record: Use the Record ID search to find all modifications to a user, reservation, or other entity over time.
Investigate impersonation activity: Look for entries with the (imp) indicator in the User column to see what changes were made during impersonation sessions.
Audit a specific table: Use the Table filter to review all changes to a particular data type — useful for auditing sensitive tables like user_roles.
Correlate related changes: On the detail page, the Request ID groups changes that happened in the same API request, so you can see everything that was modified together.
Known Limitation: Record-Scoped Feeds
Audit logs are keyed to the exact record that changed (table_name + record_id). A record-scoped view — such as the Logs feed on a staff User detail page (tableName=users, recordId=<userId>) — therefore shows only changes to that user row itself (profile edits, approval, role changes).
Changes to child/related entities owned by that user do not appear, because the trigger records them against their own table and id. The clearest example: granting or adjusting pre-paid credit balance writes an audit row with table_name=user_credit_balances and record_id=<balance id> (the trigger uses to_jsonb(NEW), whose owner key is data->>'user_id'). That row never matches a tableName=users query, so credit-balance changes are not visible in the user's Logs feed today.
The audit rows exist — they're just not reachable by the user-scoped query.
Deferred enhancement (scoped, not built): add a relatedUserId filter to GET /admin/audit-logs that OR-includes user_credit_balances rows where data->>'user_id' matches, and have the user-page Logs feed pass it. The summary renderer (Activity.tsx) would also need a branch on row.tableName to phrase credit changes. The same per-record-scoping limitation applies to other parent→child relationships (e.g. a reservation's child records), so a generalized relatedUserId/related-entity filter is the longer-term fix.
Source files: trigger apps/api/prisma/migrations/20260221115427_audit_logs/migration.sql; read endpoint apps/api/src/app/api/admin/audit-logs/route.ts; feed apps/web/src/components/molecules/detail/Activity.tsx.