Understanding the audit log

One table, every state change, every Impulse module. How audit entries are shaped, where to filter them, the default retention behaviour, and the SQL fallback for grep-at-scale queries.

Last updated 20 days ago

Understanding the audit log

Every state change across every Impulse module writes to one place: mod_impulsecore_audit_log. Admin clicks, customer self-service actions, cron tasks, system reconciliations β€” all land in the same table with the same shape.

Where entries come from

The log is populated by Core itself plus every module that calls Core's audit helper:

ImpulseCore::audit('impulseminio', 'server.provisioned', [ 'server_id' => 42, 'region' => 'us-central-dallas',]);

Modules are contractually required to audit anything that mutates customer-visible or operationally-significant state. In practice:

  • Provisioning events (server provisioned, service created, service destroyed).
  • Lifecycle transitions (suspended, unsuspended, terminated, restored).
  • Quota and configuration changes (plan upgraded, region added).
  • Customer self-service actions (access keys rotated, custom domains added).
  • Security & Maintenance events (CVE detected, patch scheduled, snapshot taken, rollback triggered).
  • Cron failures β€” if a registered task throws, Core captures the exception and writes an error entry under the module's slug.
  • License events (verified, expired, re-activated).

Read-only operations (rendering a tab, fetching a status) don't audit. Only state changes do.

What's in each entry

ColumnHolds
created_atUTC timestamp at the moment of write.
module_slugWhich module emitted (impulseminio, impulsedb-postgres, impulsecore).
sourceOne of admin / cron / customer / system.
actionDotted name like server.provisioned, plan.updated, patch.succeeded.
severityinfo (default), warn, or error.
service_idFK to tblhosting if scoped to a specific service.
admin_idFK to tbladmins for admin-initiated actions.
client_idFK to tblclients for customer-initiated actions.
ip_addressCaptured for admin and customer actions.
detailHuman-readable one-line description.
contextJSON blob with structured details (changed columns, before/after values, related IDs).

Filtering from the admin tab

Open ImpulseCore β†’ Audit. The default view is the last 100 events across all modules, newest first. Filters on the left:

  • Module β€” narrow to one module's events.
  • Action β€” narrow to a specific dotted action name.
  • Source β€” admin / cron / customer / system.
  • Severity β€” surface only warn and error when triaging.
  • Time range β€” last 24h / 7d / 30d / 90d / custom.
  • Service ID / Admin ID / Client ID β€” trace one customer's full timeline, or one admin's actions for the day.

Each module's own addon also has a Tools β†’ Audit Log sub-tab pre-filtered to that module. Same data, narrower default view.

What writes here automatically vs. what doesn't

Admin-initiated changes through any Impulse module's admin tab are captured automatically β€” the module's facade calls handle the audit write for you.

Webhook handlers and cron-internal flows don't write here unless they explicitly route through ImpulseCore::audit(). If you're writing a custom hook or a one-off reconciliation script, call the helper yourself β€” otherwise the action will happen but won't show up in the log.

Retention

Default retention is 90 days, enforced by Core's audit_purge task running daily. To change it:

  • Increase the cutoff under Settings β†’ Audit β†’ Retention (caps at 365 days from the UI; longer than that, edit mod_impulsecore_settings directly).
  • Set the cutoff to 0 to disable the purge and keep entries indefinitely.
  • Export periodically via the Audit tab's Export CSV button (respects the active filter). Pair with a monthly job that ships the export to your archive of choice.

The audit log is one of the largest tables in any production install. 90 days at moderate volume is typically a few hundred MB; multi-year retention at high volume can run to tens of GB. Plan disk and backup capacity accordingly.

SQL fallback for grep-at-scale

When the admin filters aren't enough β€” compliance reports, post-incident analysis, quarterly reviews β€” query the table directly:

-- Everything one customer triggered in the last 7 daysSELECT created_at, module_slug, source, action, detailFROM mod_impulsecore_audit_logWHERE client_id = 1234 AND created_at > NOW() - INTERVAL 7 DAYORDER BY created_at DESC;-- Every cron error in the last 24h, grouped by module + actionSELECT module_slug, action, COUNT(*) AS n, MAX(created_at) AS last_seenFROM mod_impulsecore_audit_logWHERE source = 'cron' AND severity = 'error' AND created_at > NOW() - INTERVAL 1 DAYGROUP BY module_slug, actionORDER BY n DESC;

Security events are a parallel stream

The Security & Maintenance pipeline also writes to mod_impulsecore_security_events in addition to the main audit log. Same events, but with extra structured fields (CVE id, snapshot id, patch outcome) that make security-focused queries cheaper. Use that table when investigating "what happened during last Tuesday's patch?"; use the main audit log when investigating "what did this admin click last week?"

Related