Choose how audits leave the app.
AuditTrailBundle can store audits in the local database, write database rows through Messenger, publish audit messages to a queue, or send JSON to an HTTP endpoint.
Install Matrix
Messenger is used for two different jobs: async database persistence writes local audit rows in a worker, while queue transport publishes audit messages to an external consumer. They use different message types and transport names, so both can be enabled in the same application.
| Transport | Extra Package | Extra Symfony Config |
|---|---|---|
| Database | None | None |
| Async database | symfony/messenger |
Messenger transport named audit_trail_database |
| Queue | symfony/messenger |
Messenger transport named audit_trail |
| HTTP | symfony/http-client |
Endpoint, headers, and timeout |
Database
The database transport stores audit rows locally. During onFlush, ORM-safe rows join
the current UnitOfWork. During deferred phases, the bundle writes rows without calling a nested
Doctrine flush().
audit_trail:
transports:
database:
enabled: true
async: false
Choose synchronous database delivery when the application and audit history should stay close together and you do not want another runtime dependency.
Async Database
Async database mode sends audit rows to a Messenger worker that persists them later. This is useful when write latency matters and local database persistence is still the destination.
# config/packages/audit_trail.yaml
audit_trail:
transports:
database:
enabled: true
async: true
# config/packages/messenger.yaml
framework:
messenger:
transports:
audit_trail_database: '%env(MESSENGER_TRANSPORT_DSN)%'
Run a Messenger worker for the audit_trail_database transport in production.
php bin/console messenger:consume audit_trail_database -vv
- Async database delivery uses an internal delivery identifier so Messenger retries are idempotent instead of creating duplicate audit rows.
- The worker preserves the original audit-log UUID so keyset pagination and latest-first reads stay aligned with audit creation order.
- If integrity signing is enabled, the worker verifies the carried entity signature before inserting the row.
Queue
The queue transport publishes AuditLogMessage through Symfony Messenger for an
external consumer or worker pipeline. The message class is already marked with
#[AsMessage(transport: 'audit_trail')]; define the matching Messenger transport.
# config/packages/audit_trail.yaml
audit_trail:
transports:
queue:
enabled: true
bus: 'messenger.bus.default'
# Optional: adds an ApiKeyStamp for downstream consumers.
api_key: '%env(AUDIT_QUEUE_API_KEY)%'
# config/packages/messenger.yaml
framework:
messenger:
transports:
audit_trail: '%env(MESSENGER_TRANSPORT_DSN)%'
Messenger Stamps
The bundle adds ApiKeyStamp when api_key is configured and
SignatureStamp when integrity signing is enabled. Use
AuditMessageStampEvent for application-specific Messenger stamps.
<?php
declare(strict_types=1);
namespace App\EventSubscriber;
use Rcsofttech\AuditTrailBundle\Event\AuditMessageStampEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Stamp\DelayStamp;
final class AuditMessengerSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [AuditMessageStampEvent::class => 'onAuditMessageStamp'];
}
public function onAuditMessageStamp(AuditMessageStampEvent $event): void
{
$event->addStamp(new DelayStamp(5000));
}
}
HTTP
The HTTP transport sends audit logs to an external API endpoint, logging service, SIEM, ELK, or
Splunk-style pipeline. It supports deferred phases such as postFlush and
postLoad.
audit_trail:
transports:
http:
enabled: true
endpoint: 'https://audit-service.internal/api/logs'
headers:
Authorization: 'Bearer %env(AUDIT_HTTP_TOKEN)%'
timeout: 10
When integrity is enabled, the bundle adds an X-Signature header containing the HMAC
signature of the JSON body. Plain HTTP endpoints are accepted only in the dev
environment; production endpoints must use HTTPS.
Chain
The package contains chain and null transport helpers internally. For normal application config, think of chain-style delivery as enabling the destinations you want and setting failure behavior deliberately.
audit_trail:
fallback_to_database: true
fail_on_transport_error: false
transports:
database:
enabled: true
async: false
http:
enabled: true
endpoint: 'https://audit-service.internal/api/logs'
timeout: 5
With this shape, the external transport can be attempted while database-backed fallback remains available. Use stricter settings when a failed audit delivery should fail the application write.
- When multiple transports are enabled, the bundle runs them in fixed order:
database, thenhttp, thenqueue. ChainAuditTransportis fail-fast: when one child throws, later child transports do not run.- Fail-fast does not roll back side effects already produced by earlier transports.
- Database-backed delivery uses internal delivery IDs for idempotency across fallback and retry paths.
Custom Transport Contract
<?php
declare(strict_types=1);
namespace App\Audit;
use Rcsofttech\AuditTrailBundle\Contract\AuditTransportInterface;
use Rcsofttech\AuditTrailBundle\Transport\AuditDeliveryResult;
use Rcsofttech\AuditTrailBundle\Transport\AuditTransportContext;
final class AppAuditTransport implements AuditTransportInterface
{
public function send(AuditTransportContext $context): AuditDeliveryResult
{
// Send $context->audit to your platform.
return AuditDeliveryResult::delivered();
}
public function supports(AuditTransportContext $context): bool
{
return $context->phase->isAsyncDispatchPhase()
&& $context->audit->hasResolvedEntityId();
}
}
The transport context exposes the current audit phase, entity manager, audit log, optional
UnitOfWork, and source entity. Treat it as read-only. Return
AuditDeliveryResult::delivered() on success, or
AuditDeliveryResult::partiallyDelivered() when a multi-step transport has already
completed some child deliveries before failing.