Pular para conteúdo

Audit and Change Tracking

Namespace: local_middag\framework\domain\audit.

The framework provides a three-table audit system for tracking changes to items and related entities.


Architecture

Table Purpose Retention
middag_audit_log Who changed what, when Configurable
middag_audit_diff Field-level before/after values Linked to log
middag_audit_snapshot Full state capture at point in time Linked to log

All three tables are relational with fixed schema (not EAV).


Diff Policy

Every mutation of a tracked entity produces an audit log entry with associated diffs:

audit_log (action: update, entity_type: segment, entity_id: 42, user_id: 7)
  └── audit_diff (field: status, old_value: "draft", new_value: "active")
  └── audit_diff (field: name, old_value: "Old Name", new_value: "New Name")

What is tracked:

  • All field-level changes via audit_diff (before/after per field)
  • The actor (user_id), timestamp, and action type (create, update, delete)

What is NOT tracked:

  • Read operations
  • Bulk operations (each item produces its own log entry)
  • Changes to metadata (EAV itemmeta fields) — only core entity fields

Snapshot Policy

Snapshots capture the full entity state at a point in time. They are produced on:

  • Entity creation (initial state)
  • Significant lifecycle transitions (e.g., status changes)
  • Before deletion (final state preservation)

Snapshots are JSON-serialized via JsonSerializable on the entity.


Retention

Audit retention is configurable via admin settings:

Setting Default Description
audit_retention_days 90 Days to keep audit logs before purge

The clean_audit_logs_command runs daily at 03:30 and removes entries older than the configured retention period. Associated diffs and snapshots are cascade-deleted.


Integration

Writing audit entries

Audit writes happen through audit_log_service (available via facade):

use local_middag\facade\audit_log_service;

audit_log_service::log('update', 'segment', $segment->id, $diffs);

Reading audit entries

The audit_query_service provides filtered reads for the admin UI:

use local_middag\extensions\core\admin\audit\audit_filter;

$filter = new audit_filter(
    entity_type: 'segment',
    entity_id: 42,
    from: $start_timestamp,
);
$entries = $query_service->search($filter);

Transaction boundary

When an operation is classified as strong audit (ADR-505), the main write and the audit write share the same transaction via transaction_manager. This guarantees that either both succeed or both roll back.


Cross-references

  • ADR-505 -- Audit trail design and diff/snapshot policy
  • ADR-503 -- Persistence families (audit is relational, not EAV)
  • Contracts -- audit_log_service, audit_query_service
  • Moodle Integration Patterns -- Transaction manager