This page lists what the module records and how it is stored.
Action codes
Every entry has an action of the form <type>.<operation>.
| Type | Operations | Source |
|---|---|---|
item |
create, update, delete |
API events |
media |
create, update, delete |
Entity events (see below) |
item_set |
create, update, delete |
API events |
user |
create, update, delete |
API events |
user |
login, logout |
Login controller events |
site |
create, update, delete |
API events |
site_page |
create, update, delete |
API events |
vocabulary |
create, update, delete |
API events |
resource_template |
create, update, delete |
API events |
property |
create, update, delete |
API events (off by default) |
resource_class |
create, update, delete |
API events (off by default) |
asset |
create, update, delete |
API events |
API events are captured after the operation succeeds (api.create.post, api.update.post, api.delete.post), so a failed or rejected operation is not logged. Everything that goes through the Omeka S API manager is covered: the admin forms, the REST API, batch edits and modules that use the API.
Logins are recorded only when authentication succeeds. Failed login attempts are not recorded. Logins and logouts are always logged while the module is enabled, regardless of the Events to log checkboxes.
Read and search operations are never logged.
How media is captured
Saving an item in Omeka S writes its media as a side effect of the item save, without a separate media API call. To cover this, media is logged from Doctrine entity events (entity.persist.post, entity.update.post, entity.remove.post on Omeka\Entity\Media) instead of API events.
For media entries the label is the media source (usually the original filename) and the link points to the parent item's admin page. When a media is changed through a direct API call, both an entity event and an API event fire; the module records the entity event and skips the API event so the change appears once.
Stored fields
Entries live in the audit_trail table.
| Column | Type | Content |
|---|---|---|
id |
BIGINT | Entry ID |
timestamp |
DATETIME | When the entry was written (server time) |
actor_id |
INT | User ID, or NULL when no user was authenticated |
actor_display |
VARCHAR(255) | User name, or email if the name is empty |
action |
VARCHAR(128) | Action code |
resource_type |
VARCHAR(64) | Type from the table above |
resource_id |
VARCHAR(128) | Resource ID |
resource_label |
VARCHAR(512) | Title, name, label or filename, depending on the resource |
resource_url |
VARCHAR(1024) | Admin URL for the resource |
ip_address |
VARCHAR(45) | Client address, possibly redacted |
user_agent |
TEXT | Browser user agent, up to 1,000 characters |
extra |
JSON | For API events: {"operation": "...", "resource_name": "..."} where resource_name is the API name (items, site_pages, and so on). NULL for other entries. |
Indexes exist on timestamp, actor_id, action and (resource_type, resource_id).
Resource labels and links
The label is the first of these that the resource provides: display title, title, name, or label. The link is the resource's admin URL where available. Site pages link to /admin/site/s/<site-slug>/page/<page-slug>/edit. When no better URL can be derived, the module builds /admin/<type>/<id> for items, media, item sets, users, sites, vocabularies, resource templates and assets. Links are stored as recorded, so a resource that is later deleted or renamed keeps its original link, which may no longer resolve.
IP addresses
The address is read from the first value of the X-Forwarded-For header when present, otherwise from REMOTE_ADDR. Actions performed from the command line have no address.
With Redact IP addresses on:
- IPv4: the last octet becomes
0(203.0.113.42becomes203.0.113.0) - IPv6: the first three groups are kept and the rest replaced with
****(2001:db8:85a3:...becomes2001:db8:85a3:****)
Redaction happens when the entry is written. Changing the setting does not alter existing entries.
Failure handling
All logging code catches its own exceptions and writes a message prefixed [AuditTrail] to the PHP error log. A logging failure never interrupts the action being performed, so a missing table or database error results in missing entries rather than a broken admin.
Querying the table directly
The table is plain SQL, so reports beyond the built-in filters can be run against it. For example, the most active users in the last 30 days:
SELECT actor_display, COUNT(*) AS events
FROM audit_trail
WHERE timestamp >= NOW() - INTERVAL 30 DAY
GROUP BY actor_id, actor_display
ORDER BY events DESC;