data-dog/audit-bundle
Doctrine ORM audit logging bundle for Symfony that records inserts, updates, diffs, and many-to-many relation changes in the same transaction as flush, links entries to the token storage user, and supports per-entity and per-field include/exclude config.
Installation:
composer require data-dog/audit-bundle
Add to config/bundles.php:
DataDog\AuditBundle\DataDogAuditBundle::class => ['all' => true],
Database Setup: Run migrations or schema update:
php bin/console doctrine:migrations:diff && php bin/console doctrine:migrations:migrate
(or doctrine:schema:update --force)
First Audit:
Perform a CRUD operation on any Doctrine entity (e.g., User):
$user = new User();
$user->setName('Test');
$em->persist($user);
$em->flush(); // Audit log auto-generated
Verify audit logs in the database (audit_entry table) after modifying an entity. Check fields like:
action (e.g., INSERT, UPDATE)entity_class (e.g., App\Entity\User)changes (JSON diff of modified fields)user_id (if authenticated via TokenStorage)Before going further, decide your coverage model: audits are generated for all Doctrine entities by default, so either accept that or declare your allow/deny lists up front (see audited_entities / unaudited_entities below) — retrofitting exclusions later is easier than cleaning up noise rows.
Automatic Auditing:
persist(), merge(), remove()) trigger audits on flush().Controlling Which Entities Are Audited (v1.3.2):
The bundle now honours the audited_entities and unaudited_entities configuration keys. Use an allowlist when you only care about a handful of domain entities, or a denylist when you want default-on coverage with exceptions.
Allowlist — audit only these:
# config/packages/data_dog_audit.yaml
data_dog_audit:
audited_entities:
- 'App\Entity\User'
- 'App\Entity\Order'
Denylist — audit everything except these:
data_dog_audit:
unaudited_entities:
- 'App\Entity\UnimportantEntity'
- 'App\Entity\SessionLog'
Pick one strategy per application: prefer audited_entities for large legacy schemas (a denylist is easy to get wrong and quietly audit dozens of join/log tables), and unaudited_entities for new apps where the domain is small and mostly auditable.
Customizing Audit Fields:
Extend the AuditEntry entity (e.g., add ip_address):
// src/Entity/AuditEntry.php
use DataDog\AuditBundle\Entity\AuditEntry as BaseAuditEntry;
class AuditEntry extends BaseAuditEntry {
/**
* @ORM\Column(type="string", nullable=true)
*/
private $ipAddress;
}
Linking Users to Audits:
Ensure TokenStorage is configured (e.g., Symfony’s security.token_storage). The bundle auto-links the current user to audits.
Querying Audits: Use Doctrine queries to filter audits:
$audits = $em->getRepository(AuditEntry::class)
->findBy(['entityClass' => User::class, 'action' => 'UPDATE']);
@AuditIgnore-style logic through entities — it keeps the audit surface visible in one file and reviewable in PRs.AuditEntry in tests to avoid DB writes:
$this->entityManager->getConnection()->getConfiguration()->setSQLLogger(null);
audited_entities, add a test that flushes a representative entity and asserts an audit_entry row exists, plus one asserting a non-audited entity produces none. This is the cheapest guard against config typos silently disabling auditing.Config Keys Were Previously Ignored — Re-verify After Upgrading (v1.3.2):
audited_entities / unaudited_entities were effectively not honoured, meaning auditing ran on everything regardless of what you had configured.entity_class values present in audit_entry over a deploy window against the previous period. Gaps mean an entity is now excluded.Direct SQL/DQL Bypasses Audits:
entityManager->createNativeQuery()) or DQL UPDATE/DELETE are not audited.Circular References in Diffs:
data_dog_audit:
entities:
ignore_fields:
- 'App\Entity\Post#comments' # Skip comments collection
User Linking Failures:
TokenStorage is empty (e.g., API requests without auth), audits will lack user_id.$auditEntry->setUser($this->getAnonymousUser());
Schema Updates:
AuditEntry, run migrations after disabling the bundle temporarily:
php bin/console config:dump --env=prod | grep -v data_dog_audit
php bin/console debug:config data_dog_audit
php bin/console cache:clear
debug: true in config to log audit events to Symfony’s logger:
data_dog_audit:
debug: true
changes field may contain malformed JSON if entity getters/setters throw exceptions. Validate with:
json_decode($auditEntry->getChanges(), true);
Custom Actions: Trigger audits manually for non-ORM changes:
$auditManager = $this->container->get('data_dog_audit.manager.audit');
$auditManager->audit($entity, 'CUSTOM_ACTION', ['custom_data' => 'value']);
Event Listeners:
Subscribe to data_dog_audit.pre_audit or data_dog_audit.post_audit events to modify audit data:
// src/EventListener/AuditListener.php
public function onPreAudit(AuditEvent $event) {
$event->getAuditEntry()->setIpAddress($this->getClientIp());
}
Async Processing: Offload audit writes to a queue (e.g., Symfony Messenger) to reduce transaction time:
# config/packages/messenger.yaml
framework:
messenger:
transports:
audit: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'DataDog\AuditBundle\Message\AuditMessage': audit
Config as a Feature Flag: because the audited-entity lists are plain container config, you can bind them from env vars per environment (e.g., broader audit coverage in prod, narrow in dev for log hygiene) rather than branching application code.
How can I help you explore Laravel packages today?