Documentation
Getting Started
Data Grid
Modeling
Business Rules
Approvals
Users, Roles & Security
Integration & API
Installation
Migrating from MDS
Architecture

Audit Log

The audit log is the full trail of data changes and security events. It is append-only: entries are never edited, only deleted by the retention cleanup.

Access: Settings → Log ViewerAudit Log tab

The audit log with filters and expandable entries
The audit log with filters and expandable entries(click to enlarge)

Or watch it end to end — a save traced from the record's own panel to the log, the difference an edit stores against the full snapshot a delete keeps, failed logins and a lockout, a permission move, the export, and the retention window that eventually deletes all of it:

The audit log in Primentra — who changed what, and when

How many entries you are looking at

Above the table is the count for the current selection: 60 entries match *of 61 in the log*. With no filter on, it is simply the size of the log.

Every filter is applied by the database to the whole log, not to the page on screen, so that number and the rows beneath it always describe the same set. It has not always been so — the filters once ran in the browser over the fifty rows that had been fetched, which hid matching entries on every other page and still reported the unfiltered page count.

Filtering

  • Search across records, users, fields and values
  • All users — who made the change. Matched on the account, so two people with the same display name stay apart
  • All models and All entities dropdowns. Authentication and user-management events use entity Authentication and model System
  • All actions, grouped into Data, Security, Permissions and Approval
  • A from and to date pair. The to date includes the whole of that day

Click Clear filters to reset everything at once.

Sorting and page size

Click Time, User, Action or Entity to sort. Clicking again reverses it, and a third click returns to newest-first. The sort is applied to every matching entry, not to the page you happen to be on.

Rows per page offers 10, 50, 100, 1000 and Show all. Show all fetches everything that matches and then draws it in slices as you scroll, so a log of forty thousand entries does not freeze the tab. The indicator reads *Showing 200 of 40,000* while it fills in.

Action groups

GroupActions
DataEdit, Create, Delete, Import, Purge
SecurityLogin, Failed login, Account locked, Logout, User created, User updated, User deleted, Welcome email, Password changed, Password set (forced), First setup
PermissionsPermission changed
ApprovalSubmitted for approval, Approved, Force approved, Rejected, Sent back

API user and API key events use the same security badges: API user created, API user updated, API user deleted, API key regenerated.

Reading an entry

Every row shows the time, the acting user's email address, a color-coded action badge, the entity, and a one-line summary. Click a row to expand it.

  • Data entries show a before-and-after table of every changed field
  • Permission entries show the role, the scope, and the exact flags that changed
  • Security entries show details such as the email address, the IP address, or the remaining login attempts

If a user is deleted, the numeric user ID appears instead of the email address.

Export

Click Export and choose JSON, CSV or XLSX. The export covers the filtered result set and includes the full field-level change data.


Technical reference

Database table: `AuditLog`

ColumnTypeDescription
IdBIGINTPrimary key, auto-increment
EntityIdINTForeign key → Entities.Id
EntityNameNVARCHAR(200)Entity name at the time of the action
ModelNameNVARCHAR(200)Model name at the time of the action
ActionNVARCHAR(50)See the action groups above
RecordCountINTNumber of rows affected
UserIdINTForeign key → Users.Id. Resolved to an email address in the UI
CommentNVARCHAR(1000)Optional save comment
DetailsNVARCHAR(MAX)JSON with the field-level change data
CreatedAtDATETIME2Timestamp in UTC

When an entry is written

The stored procedure responsible for each operation writes its own audit entry, inside the same transaction as the change. If the save rolls back, the audit entry rolls back with it — there are never phantom entries for changes that did not happen.

EventWritten by
Rows saved (new, edited or mixed)usp_Data_SaveChanges — one entry per save batch
Rows deletedusp_Data_Delete — one entry per batch
Data imported by the migration wizardThe import procedure — one entry per entity
Permissions changed in bulkusp_Permission_BulkSaveone entry per changed permission
A single permission upsertedusp_Permission_Upsert — one entry if anything changed
Login success or failure, account lockoutThe authentication handler in the API
User created, updated or deletedThe user-management routes
Password changed or resetThe password-change route
First user setup completedThe setup route

The `Details` JSON

For data actions, Details is an array with one element per affected row.

Create — every field value of the new row:

[{"code":"5","name":"Amsterdam","_savedId":130,"Region":"Noord-Holland","Country":"{NL} Netherlands"}]

Edit — only the fields that changed:

[{"code":"5","name":"Amsterdam","_savedId":130,"Region":"Zuid-Holland"}]

Delete — a full server-side snapshot of every deleted row, always saved automatically:

[{"code":"5","name":"Amsterdam","Region":"Noord-Holland","Country":"{NL} Netherlands"}]

Three keys are internal: code and name identify the row, and _savedId is the EntityRows.Id used by the audit panel to match entries to rows. _savedId is not displayed.

Domain attributes are resolved at write time. Instead of the raw ID 131, the log stores {NL} Netherlands, so the entry stays readable after the referenced record is changed or deleted.

Only changed fields are stored. The grid sends a changedFields array per row and the backend filters the details with it, so unchanged fields never clutter the trail.

Permission change entries

usp_Permission_BulkSave writes one entry per individual change, with Action = permission_change and RecordCount = 1. Here Details is an object, not an array:

{
  "scope": "entity",
  "modelId": 3,
  "modelName": "Customer",
  "entityId": 12,
  "entityName": "Branch",
  "roleId": 2,
  "roleName": "DataSteward",
  "changes": { "canRead": { "from": false, "to": true } }
}

Attribute scope adds attributeId and attributeName, and changes carries level. Only flags that actually changed appear.

Attribute-level level values are the effective levels, not the raw stored value. Setting an attribute to Inherit removes the permission row, so the log resolves what the inherited level actually is — from the entity permission, then the model permission for the same role — and records that. The entry reads "to": "read" rather than a meaningless "to": "none".

Batch saves

One save operation produces one entry, with RecordCount equal to the number of rows and one Details element per row. Action is:

  • create — every row is new
  • edit — every row already existed
  • create/edit — the batch contains both

Stored procedures

ProcedurePurpose
usp_AuditLog_WriteInserts an entry
usp_AuditLog_GetPaginatedPaginated retrieval for this screen
usp_AuditLog_GetByRowEntries for one row, used by the audit panel
usp_AuditLog_GetDashboardThe last 200 data actions, for the dashboard
usp_AuditLog_ResolveAttributesMaps uda_* column names to display names
usp_AuditLog_CleanupDeletes entries past the retention window

Automatic cleanup

usp_Maintenance_Cleanup reads auditRetentionDays (default 90) and calls usp_AuditLog_Cleanup:

DELETE FROM AuditLog
WHERE CreatedAt < DATEADD(DAY, -@RetentionDays, GETUTCDATE())

The cutoff moves forward one day for every day that passes, so the log always covers exactly the configured window. See Data Retention.

Ready to get started?

Start managing your master data with Primentra today.

View Pricing
Audit Log | Administration | Docs | Primentra