Flagsmith / Flagsmith/flagsmith
Audit log shows a blank user for API key, system, and deleted-user changes
- Dominant language
- Python
- Stars
- 6.6k
- Forks
- 567
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 121
Description
### Is your feature request related to a problem?
Audit log entries frequently show an empty **User** column, with no indication of who or what made the change. A customer raised this on SaaS: "Sometimes no user is listed in the user section in the audit log. Do you know why this happens?"
There are three ways an entry ends up with `author = null`:
1. **The change was made with a Master API Key** (Terraform provider, CI scripts, direct Admin API calls). `AuditLog` has a separate `master_api_key` FK alongside `author`, and it is populated correctly - we just never return it. `AuditLogListSerializer` and `AuditLogRetrieveSerializer` only expose `author`, so the API response has no attribution at all for these entries.
2. **System events** (`is_system_event=True`), e.g. a scheduled Change Request going live via `create_feature_state_went_live_audit_log`. These legitimately have no human author, but the UI gives no signal that this is why the field is blank.
3. **The user was deleted.** `author` is `on_delete=models.SET_NULL`, so removing a member from the organisation retroactively blanks their name on every historical entry they created.
In the UI this is worse than in the API, because `AuditLog.tsx` renders `{author?.first_name} {author?.last_name}` with no fallback - so all three cases render as an empty cell that looks like missing data rather than a known state.
The impact is that the audit log stops being a reliable answer to "who changed this flag", which is its primary job. For customers using it for compliance or for tracing an unexpected production change, a blank cell is indistinguishable from a bug. Note that `is_system_event` *is* already returned by the API and unused by the row rendering, and `master_api_key` attribution already exists in the database - so in both cases the information is present and simply not surfaced.
### Describe the solution you'd like.
Never render an empty User cell. Specifically:
1. Add the Master API Key to the audit log serializers (at minimum `{id, name}` from `MasterAPIKey`) so API consumers can attribute API-driven changes.
2. In `AuditLog.tsx`, fall back through `author` -> master API key name (badged as an API key, e.g. `🔑 terraform-prod`) -> `System` when `is_system_event` is true.
3. For deleted users, show something explicit such as `Deleted user` rather than nothing. This one needs a data decision - see alternatives.
Item 1 and 2 are small and independent of item 3, and together cover the majority of reported blanks.
### Describe alternatives you've considered
- **Denormalise the author's email onto `AuditLog` at write time.** This solves case 3 properly (the historical record keeps the identity of whoever made the change, even after the user is deleted) but is a larger change and has GDPR implications for erasure requests, so it deserves its own discussion. Rendering `Deleted user` is the cheaper option and at least distinguishes "we don't know" from "no human did this".
- **Leave the API as-is and fix only the frontend.** Rejected: customers query the audit log API directly for SIEM and compliance pipelines, and the attribution gap exists there too.
- **Document the behaviour instead of changing it.** Doesn't help - the reason a user asks about this is that they need to know who made a specific change, and documentation doesn't give them the answer.
### Additional context
Relevant code:
- `api/audit/models.py` - `AuditLog.author` (`SET_NULL`), `AuditLog.master_api_key`, `AuditLog.is_system_event`
- `api/audit/serializers.py` - `AuditLogListSerializer` / `AuditLogRetrieveSerializer` field lists, neither includes `master_api_key`
- `api/audit/tasks.py` - `_create_feature_state_audit_log_for_change_request` creates `is_system_event=True` entries with no author
- `frontend/web/components/AuditLog.tsx` - renders `{author?.first_name} {author?.last_name}` with no fallback
Worth noting that the flag change webhook already handles this correctly: `trigger_feature_state_change_webhooks` in `api/features/tasks.py` falls back to `master_api_key.name` for `changed_by`. So a customer receiving webhooks currently gets *better* attribution than the same customer reading the audit log UI. Aligning the audit log with that existing behaviour seems like the obvious baseline.
Possibly relevant to #6304 (Epic: Audit Log Enhancements).
Raised from a SaaS customer support conversation.
Contributor guide
Research direction
Start with api/audit/serializers.py and frontend/web/components/AuditLog.tsx, then compare attribution handling in trigger_feature_state_change_webhooks in api/features/tasks.py. Done means API responses expose master API key attribution and the UI distinguishes author, API key, system events, and deleted users without a blank User cell; the deleted-user behavior still needs a data decision.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, react
- Domain
- api, backend, frontend
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100