ZuloOne
English
Sign in
Menu

Record change history (`MetaRecordHistory`)

Updated

Tick tables and columns on Record logging (/record-logging, Administrator → Monitor). That page writes a tenant overlay, not the object's model metadata, so you can turn logging on or off for a product table (BankAccount, Item, …) without switching working model. The Data change history journal (/record-history, same ribbon group) and the History drawer on a record card both read the same MetaRecordHistory table — who changed what, from what to what, and when.

← Developer wiki

What isLogged means#

The writer uses the tenant overlay when Administration has saved the object on /record-logging; otherwise it falls back to metadata flags (the model default). Overlay never writes the object's model row — a product table stays read-only, and a later model install does not wipe the tenant switch.

Layer Where Role
Overlay object IsLogged MetaRecordLoggingSettings Tenant master switch; wins over metadata
Overlay field IsLogged MetaRecordLoggingFieldSettings Tenant field tick; missing row inherits metadata
Object isLogged Dictionary, document, table part, link table Model default, used when no overlay
Field isLogged Header fields, table-part columns, link-table columns Model default field allow-list
Object Fields ticked What is written
off ignored nothing
on none nothing
on some Insert/Delete: snapshot of those fields only. Update: a row only if at least one ticked field changed; Changes contains only those fields

An object left isLogged: true from an older stand with no field ticks does not mean “log everything”. With an empty field list the writer writes nothing until you tick at least one column.

Noise columns (Created* / Modified* timestamps and author MetaIds) are never logged even if someone ticks them. Calculated fields are not offered on the settings page and are not diffed.

Where to configure and read#

Surface Route Who Purpose
Record logging /record-logging Administrator Tenant overlay: on/off and field ticks for any table, including other models
Data change history /record-history Administrator (Monitor ribbon) Journal by object: filters, paging, expand old → new
History drawer On the record card Whoever can open the record Per-record slice of the same table

This is not Audit. MetaAuditLog and /audit cover metadata, posting, security, licensing, and similar platform events — not ordinary business field diffs. Do not point users at Audit when they ask “who changed this customer name?”.

For who wrote the row (author columns on the business table), see Who wrote this row: ICurrentUser and the author columns. Record history answers a different question: which fields changed between saves.

Writer path#

RecordHistoryService is the only writer. It runs on the HTTP DataController path for insert, update, and delete — the same saves that come from the web client forms and grids.

Path Journaled?
DataController HTTP CRUD yes, when object + field isLogged match
IDataService / managers from scripts, jobs, imports no (follow-up if you need it)
Database triggers no — not ported from MIQS

A history write failure still never fails the business save.

API (read)#

Table journal (Administrator page):

GET /api/data/{tableName}/history?from=&to=&user=&action=&take=&before=

Per-record (History drawer):

GET /api/data/{tableName}/{recordMetaId}/history

Both require Read on that object (same guard as data access). user is a UserMetaId GUID or a case-insensitive substring of UserName. Paging uses take (default 100, max 500) and before (timestamp exclusive) so a growing log does not skip rows when you load the next page.

The journal stores raw field values. A reference column is a Guid. The expand on /record-history and the History drawer resolve those ids through the same /api/documents/resolve-labels path the grids use, and print Name (guid) — the name a person reads, the id when two rows share a caption or the target has been deleted (then the Guid stays, unresolved).

Index (TableName, Timestamp) supports the journal; (TableName, RecordMetaId) supports the drawer.

Traps#

Empty field list writes nothing. Object isLogged: true with zero field ticks is valid (the settings page keeps the object on when you clear the last field) but produces no rows until you tick a column again.

Leftover object flag ≠ log-all. Workspace objects that already had object-level isLogged: true before opt-in logging did not automatically get field ticks. Until you configure fields on /record-logging, new saves write nothing even if the object switch looks on.

Overlay beats the model. Turning a table off in Administration does not flip the object's isLogged in its model. The designer of a product model stays read-only; the tenant overlay is the switch that actually runs.

Script saves stay out. Code that updates rows through IDataService or document/dictionary managers does not pass through RecordHistoryService. Only HTTP DataController saves are journaled today.

Purgeable. Retention sweeps can delete old MetaRecordHistory rows. Do not treat the journal as the only proof of who changed data for compliance — pair it with author columns and your retention policy.

The Guid in the JSON is the source of truth. The UI label is resolved at read time. A renamed Item group still diffs as the same Guid; a deleted one shows the Guid alone because there is nothing left to name.

Do not confuse with Audit. Field-level business history is /record-history and MetaRecordHistory. Platform audit is /audit and MetaAuditLog.

Where next#

Was this page helpful?