Audit & logs
Every platform has to answer three questions: who changed the setup?, what changed on this record?, and what happened inside this transaction? Salesforce answers them with five disconnected tools — Setup Audit Trail, Field History Tracking, debug logs, Event Monitoring, and Field Audit Trail — several of them paid (Event Monitoring and Field Audit Trail require Shield, priced at roughly 10% of total spend), one of them off by default (debug logs), and each with its own retention and its own access path. CAOS answers all three with one model, three always-on streams, correlated by a single id, and nothing behind a paywall.
The model
Section titled “The model”Every log entry, in every stream, shares one envelope — actor, correlationId, timestamp, tenant — so the three streams are one dataset seen three ways, not three products. The streams differ only by subject:
| Stream | Answers | Subject | Salesforce equivalent(s) |
|---|---|---|---|
| Metadata audit | Who changed the platform’s configuration, and to what | A metadata component change | Setup Audit Trail |
| Data history | What changed on a record, field by field | A field value change | Field History Tracking + Field Audit Trail (paid) |
| Execution trace | What a transaction did — queries, effects, budgets, timing | A save/read/deploy execution | Debug logs + Event Monitoring (paid) |
Because all three carry the same correlationId, a single failure can be walked end to end: an error → the execution trace of the transaction that raised it → the field changes that transaction wrote → the metadata change that made it behave that way. Salesforce’s five tools cannot be joined like this; each is its own silo.
What’s logged
Section titled “What’s logged”Metadata audit — every change to a canonical component, whatever the source: a deploy, a click-made change captured by reverse-integration, or an API edit. Because components are content-addressed and diffable, an audit entry is the before → after diff, not a one-line string.
- Component
key, change type (create/update/retire/purge), and the structural diff of itsbody. - Actor, source (
deploy/ui/api), and the deploy’s generation id. - Permission and root events are first-class and flagged — a
platform.rootassignment, a permission-set grant, an FLS change: this is what backs “every root action is logged.”
Data history — every field change on every record, written to the separate append-only history table (so it never consumes an object’s column budget). No 20-field cap, no paid tier.
- Record id, field
key, old value → new value, actor,correlationId. - Create and delete are entries too (null → value, value → null / tombstone).
Execution trace — captured at two levels so that “always on” and “affordable” can both be true:
-
A summary for every transaction, always: actor,
correlationId, object, operation, the save-order phases entered, budgets consumed (queries, writes, CPU, time), and outcome. You are never blind the way a Salesforce org is with debug logs off.The stream’s shape reserves room for non-save login / API / export events too (
event_type), but as of 23 August 2026 only the save path actually writes one — a sign-in does not (yet) land here; wiring it in is tracked separately (CAOS-1666). A sign-in is captured today on its own table (caos.login_event), not this stream — see the next section. -
A verbose per-statement trace — every query, effect, calc call, and budget line — sampled by default and targetable on demand (by user, object, or
correlationId), because verbose capture is genuinely expensive. Raising a verbose trace never disables anything; it widens sampling for a scope and a window.
Always-on, retention, and limits
Section titled “Always-on, retention, and limits”Nothing here is off by default, and nothing is a paid add-on. The only thing metered is the volume of the verbose execution trace, and it is metered by sampling, never by silently dropping the always-on streams.
| Stream | Always on? | Default retention | Salesforce, for contrast |
|---|---|---|---|
| Metadata audit | Yes, cannot be disabled | 2 years hot, then archived (never hard-deleted) | 180 days, then permanently deleted; UI shows only the latest 20 |
| Data history | Yes, every field | 7 years hot + archive, configurable per plan | 18 months / 20 fields free; 10 years / 60 fields only on Shield |
| Execution summary | Yes, every transaction | 90 days hot + archive (aligned with the error-trace store) | debug logs are off until a trace flag is set |
| Execution verbose | Sampled / on-demand | 30 days (bulky; sampled) | each log ≤ 20 MB, purged in 24 h; trace flags auto-disable at 1000 MB / 15 min |
| Sign-in history | Yes, every attempt | 1 year hot, then archived (never hard-deleted) | — |
Retention is a per-tenant / per-plan policy, and expired data is archived, not destroyed — the opposite of Salesforce’s hard 180-day and 24-hour cliffs. There is no equivalent of Salesforce’s silent “we turned your logging off because you produced too much” footgun: the always-on streams are bounded by retention windows and archival tiering, and only the verbose trace is sampled.
In the UI
Section titled “In the UI”The Logs surface is a package-delivered surface — it lives in the Setup app, which a package provides and the shell renders. The three streams themselves are kernel data, equally reachable from the CLI and SQL paths below; what follows is how one such package presents them. A single Logs area (in Setup) with three tabs, plus the same data surfaced in context:
- Setup audit — a filterable feed of configuration changes; each row expands to the before → after diff. Filter by actor, component, type, and time. No 20-row ceiling.
- Data history — searchable field changes, and the same timeline rendered as a History panel on each record page and per-field in Object Manager.
- Execution — per-transaction traces: the save-order phase timeline, budgets consumed, the queries and effects run, and the
correlationId— deep-linked from an error’s reference id on the Errors surface, so “what went wrong” jumps straight to “what happened.”
Every entry cross-links by correlationId to the related entries in the other tabs, so any row is a pivot point into the full story of one transaction.
At the terminal
Section titled “At the terminal”All three streams are first-class in the CLI — they are queryable tables, so the commands are thin views over them:
caos audit list --since 2026-08-01T00:00:00Z # metadata changes since a time boundcaos audit --follow # tail config changes livecaos history list --object invoice --record-id 8f3a… # a record's field timelinecaos logs list --correlation-id 5f3c… # one transaction, by correlationIdcaos logs --follow # tail executions livecaos trace show 5f3c… # the verbose per-statement trace for one transactionEvery command takes --since / --until, --format json, and pipes cleanly; because the streams are tables, anything the CLI shows is also a plain SQL query for ad-hoc analysis or export.
Access, and respect for FLS
Section titled “Access, and respect for FLS”Reading logs is gated by system permissions: execution traces and health by diagnostics.view, a sensitive raw trace by diagnostics.decode, and configuration/audit history by the security-admin surface. One rule matters most: the logs honor field-level security. A user viewing data history sees the change record of a field only if they can read that field — the history stream is not an FLS bypass. The sole exception is platform.root, which sees everything — and whose every access is itself an audit entry.
The record matters as well as the field. A record’s history is readable only by someone who can read the record itself, through the same object permission and sharing that decide whether they can open it; the permission to view logs does not widen that. A deleted record in the Recycle Bin keeps its history: whoever could read the record before it was deleted can still read every entry for it, including the deletion, with who deleted it and when, and nobody else can. A purged record has no row left to read, so its history follows the rule for a record the reader cannot see.
How Salesforce does it
Section titled “How Salesforce does it”Five mechanisms, three of them constrained by cost or default-off:
- Setup Audit Trail — always on and undisableable, but retained only 180 days then permanently deleted, and the UI shows just the 20 most recent entries; the rest require a CSV export (Gearset audit-trail guide).
- Debug logs — off by default (require a trace flag); each log ≤ 20 MB; system logs purged in 24 hours, monitoring logs in 7 days; generating > 1000 MB in 15 minutes disables your trace flags (Debug Logs — Salesforce Help).
- Field History Tracking — free, but 18 months and 20 fields per object.
- Field Audit Trail — 10 years and 60 fields, but only with a Shield license (Field Audit Trail — Salesforce Help).
- Event Monitoring — 50+ event types, but the full set and extended retention require the paid Event Monitoring add-on / Shield (~10% of total spend); the free tier is a few event types at ~1 day retention (Shield pricing).
(The specific figures above are drawn from Salesforce Help plus widely-corroborated secondary guides; the Shield percentage is a commonly-cited commercial figure, not a fixed published list price, and should be confirmed with Salesforce for an exact price.)
Where CAOS is better:
- Nothing is paywalled or off. The capabilities Salesforce charges Shield for — long field-history retention and rich execution/event logging — are always-on defaults here. Cost: CAOS owns that storage and its retention economics, which Salesforce offloads to a Shield SKU.
- One correlated dataset, not five silos. A single
correlationIdjoins config, data, and execution, so root-cause analysis is a pivot, not a cross-tool reconstruction. Cost: the id must be threaded through every surface — a standing discipline the whole platform already depends on. - Structured and SQL-queryable. Every stream is a table; the UI and CLI are views over it, versus Salesforce’s 20-rows-then-CSV audit trail and download-and-grep text debug logs. Cost: schema and index design for high-volume append-only logs is real work.
- Diff-based metadata audit. An audit entry is the component’s before → after diff, not a one-line summary, because components are content-addressed. Cost: storing diffs is heavier than storing a line of text.
- No silent loss. Retention windows + archival replace Salesforce’s 24-hour purge, 180-day cliff, and auto-disabling trace flags; the always-on streams are never dropped, only the verbose trace is sampled. Cost: always-on capture at scale needs tiering and archival that must be built and operated.
Parity (table stakes): an undisableable configuration audit trail, per-field data history, transaction-level execution logging, and login/API event capture. Salesforce has all of these somewhere; matching them is the floor. The win is that CAOS makes them one always-on, unpaywalled, correlated model rather than five tools with five retention rules and three price tags.
Sources
Section titled “Sources”- Setup Audit Trail — retention 180 days, 20 rows in UI, always on (Gearset guide) (secondary, corroborated)
- Debug Logs — Salesforce Help (20 MB/log, 24 h / 7 d retention, 1000 MB/15 min trace-flag disable)
- Field Audit Trail — Salesforce Help (10 years / 60 fields, Shield)
- Field History Tracking — 18 months / 20 fields (Gearset guide) (secondary, corroborated)
- Salesforce Shield pricing / Event Monitoring add-on (~10% of spend)
- An Architect’s Guide to Event Monitoring — Salesforce