Audit log
Purpose and who can read it
Super AdminHotel ManagerSecurityManagers and Security see the trail; nobody edits it.
The audit log answers “who changed this and when?”. Every write to the system leaves a row: bookings, payments, guest edits, room status, staff records, settings, role changes, sign-ins, successful, failed and denied. Rows are written by the server as part of the action itself, so they cannot be skipped by the browser.
Reading it is limited to the admin tier: Super Admin, Hotel Manager, or any role whose matrix has been raised to Settings = Full, plus Security, whose job is access logs. Anyone else gets a 403 and does not see the sidebar entry. Nothing on the page writes; the only action is Export CSV.
Screen anatomy
Four KPI tiles, a filter bar with date range and extra filters, the event table and a detail drawer.
- 1Page head“Every staff action across the hotel, with who / what / when / from where.”
- 8Export CSVDownloads audit-log-2026-09-04.csv with the current filters applied (up to 5,000 rows, newest first).
- 7KPI tilesEvents today (with the hotel date), Last 7 days, People active today, Denied this week. Today and Last 7 days set the date range; Denied this week sets Outcome = Denied for the week.
- 2Filter bar“Search actor, action, code, IP…” plus Person (name · role), Role, Module and Action dropdowns.
- 3Date range and More filtersFrom and To date pickers (hotel-local days). More filters reveals HTTP method (POST / PATCH / PUT / DELETE) and Outcome (Succeeded / Denied / Error).
- 4Event rowsWhen · Person (avatar, name, role) · Action (with a Denied or Error badge when it failed) · Record (module badge and record id) · Detail (a three-field summary of the diff) · From (IP). On phones the rows become cards.
- 6Pager and headersFifty rows a page (25, 50 or 100), numbered pages, and the page, sort and filters live in the address bar. Tap a heading to sort; the Person, Action, Record and Outcome headings carry filter popovers. A Cards view is one click away.
Click a row for the drawer: the person with role and email, then Action (raw name such as booking.checkedin), Module, Record, Outcome with the HTTP status, When, IP address, Request (method and path), Browser (user agent) and a What changed panel that lists every field as before → after (red to green), the person card with browser and IP, a link to the record and the other events on that record, or “No extra detail was stored for this event.”
What is recorded
Named domain events with diffs, plus a generic row for every other write, sign-ins and denials.
| Field | Type | Notes |
|---|---|---|
| Actor | The user id plus a snapshot of the name and role at the time. If the account is later renamed or deleted the row still reads correctly; rows without a snapshot fall back to the current account, or “Unknown”. | |
| Action | A dotted name: booking.checkedin, payment.refunded, settings.updated, auth.login.failed, access.denied, night_audit.closed… Screen labels translate the common ones (“Checked in”, “Refunded payment”, “Access denied”). | |
| Module and Record | The entity type (booking, invoice, user, hotel, sequence…) and the record’s code or id, BK-1042, INV-…, an email for user events, a slug for settings. | |
| What changed | Before → after per field for edits (bookings, rooms, guests, staff, accounts, settings, roles, housekeeping, maintenance, payments). Creates and older rows show the record that was sent; a night audit stores its snapshot. Secrets are masked: a logo change stores “(image updated)”, a PIN change “(updated)”, hashes read “(changed)”. | |
| Request metadata | IP (first address in X-Forwarded-For), browser user agent (240 chars), HTTP method and path (200 chars), and the status code. | |
| Outcome | Derived from the status code: below 400 → OK; 401 or 403 → Denied; any other 4xx/5xx → Error. | |
| When | Server time, displayed in the hotel’s timezone to the second. |
- Named events are written inside the same transaction as the change, so a booking cannot be checked in without its row.
- Generic rows: any other POST, PATCH, PUT or DELETE that did not write a named event gets
http.post-style rows: Module is the first path segment, Record is any code or id in the path, and the diff holds the path and status. GET requests, health checks, sign-in endpoints, notifications, the audit log itself, demo sandboxes and search are deliberately not logged as actions. - Sign-ins and denials: successful and failed email or PIN sign-ins, sign-outs and every request refused by the permission guard are recorded with the same IP and browser details.
Outcomes
Every row is OK, Denied or Error, derived from the HTTP status, not stored.
- Request handled → OK (< 400)
- Request handled → Denied (401 / 403)
- Request handled → Error (other 4xx / 5xx)
Denied is the security view: a role trying a screen or action it does not have, or a wrong password. Error is a business refusal, a check-in on the wrong day, an overbooked room, a validation failure, which is useful when a colleague says “it would not let me”.
Tracing a change
Search the record code, narrow by person or action, open the drawer, read the diff.
- 1Type the record code in the search boxBK-1042, INV-2210, a guest code, an email, an IP address or part of an action name. The search matches action, record id, module, actor name, request path and IP.
- 2Narrow with the dropdownsPerson lists every account with its role; Role matches the role snapshot on the row (or the account’s current role); Module and Action list the values that exist in the log.
- 3Set the datesFrom / To are hotel-local calendar days: From starts at 00:00 hotel time, To includes the whole day. Events today and Last 7 days fill them for you.
- 4Open the rowThe drawer shows who (name, role, browser, IP), the outcome, the request line, a link straight to the record, What changed as before → after pairs, and the other events on the same record so you can walk its history. Raw record opens the stored document for anything older.
- 5Export if you need a recordExport CSV honours every filter and writes at, actor, role, action, entity_type, entity_id, outcome, method, path, ip and diff (as JSON text).
Permissions and retention
Admin tier plus Security may read; nobody may edit or delete; rows are kept indefinitely.
| Role | Settings |
|---|---|
| Super Admin | Full |
| Hotel Manager | Edit |
| Security | None |
| HR Manager | View |
- A Hotel Manager reads the log even at Settings = Edit, because the role is admin tier by name. A custom role becomes admin tier the moment its matrix is set to Settings = Full.
- Security always reads it, whatever the rest of its matrix says.
- There is no write API: rows are appended by the server and never edited. Nothing in the system prunes them, so the trail is complete for the life of the hotel; use Export CSV for off-site copies.
- Demo sandboxes get their own log, discarded with the sandbox.
Tips
Wildcards, denied spikes and the hotel clock.
Errors you may see
One server refusal and one client-side export failure.
The audit log is limited to Super Admin, Hotel Manager and SecurityCould not export the audit log- Admin tier
- Super Admin, Hotel Manager, or any role with Settings = Full, the roles that can manage users, roles and the PIN.
- Actor snapshot
- The name and role copied onto the row when it was written, so history survives renames and deletions.
- Diff
- The JSON stored with an event describing what was sent or what changed.