Installation
composer require dualmedia/es-log-bundle
Add to config/bundles.php:
DualMedia\EsLogBundle\EsLogBundle::class => ['all' => true],
Configure Elasticsearch Client
Ensure you have an Elasticsearch client service (e.g., fos_elastica.client.default) and update dm_es_logs.yaml:
dm_es_logs:
client_service: 'fos_elastica.client.default'
index_name: 'your_app_logs'
Annotate an Entity
Mark an entity with #[AsLoggedEntity] and properties with #[AsTrackedProperty]:
#[AsLoggedEntity]
class Product
{
#[AsTrackedProperty]
private string $name;
#[AsIgnoredProperty] // Optional: Exclude from tracking
private string $secretKey;
}
Trigger a Log Perform CRUD operations on the entity. The bundle auto-captures changes to tracked properties and logs them to Elasticsearch.
Annotate the User entity to track changes to sensitive fields (e.g., email, role):
#[AsLoggedEntity(includeByDefault: true)]
class User
{
#[AsIgnoredProperty]
private string $password;
private string $email;
private string $role;
}
After updating a user’s email, query Elasticsearch for their change history:
$esClient = $this->get('fos_elastica.client.default');
$results = $esClient->find('user', ['query' => ['match' => ['entityId' => $user->id]]]);
Entity Annotations
Use #[AsLoggedEntity] to enable logging for an entity. Configure includeByDefault to track all properties unless explicitly ignored.
#[AsLoggedEntity(includeByDefault: true)]
class Order
{
#[AsIgnoredProperty]
private string $internalNotes;
}
Property-Level Control
Exclude sensitive or non-critical fields with #[AsIgnoredProperty]:
#[AsTrackedProperty]
private string $customerName;
#[AsIgnoredProperty]
private string $paymentToken;
Bulk Operations
For batch updates (e.g., via repositories), ensure the bundle’s event listeners are triggered. Use EntityManager directly:
$em->persist($entity);
$em->flush(); // Triggers logging
Custom Log Fields
Extend the log payload by implementing DualMedia\EsLogBundle\Event\LogEventSubscriber:
public function onPreLog(LogEvent $event) {
$event->setCustomData(['custom_field' => 'value']);
}
Elasticsearch Indexing
Pre-create the index with a mapping for structured logs (e.g., dm_entity_logs):
{
"mappings": {
"properties": {
"entityId": {"type": "keyword"},
"changedProperties": {"type": "nested"},
"timestamp": {"type": "date"}
}
}
}
Symfony Events
Listen to kernel.request to log API changes or pre_update/pre_remove for manual control:
$dispatcher->addListener('preUpdate', function (PreUpdateEventArgs $args) {
if ($args->getObject() instanceof LoggedEntityInterface) {
// Custom logic before logging
}
});
Testing Mock the Elasticsearch client in tests:
$this->container->set('fos_elastica.client.default', $this->createMock(Client::class));
Missing Configuration
Forgetting to define dm_es_logs.yaml or misconfiguring client_service will silently fail. Verify the client service ID matches your container.
No Logs for Untracked Updates
If an entity is updated but no #[AsTrackedProperty] fields change, no log is created. Use includeByDefault: true cautiously.
Performance Overhead
Logging every property change can bloat Elasticsearch. Exclude non-critical fields with #[AsIgnoredProperty].
Elasticsearch Connection Issues If the client service is unavailable, logs will fail. Implement a fallback (e.g., queue delayed logs):
try {
$this->esClient->index($logData);
} catch (ConnectionException $e) {
$this->queueLogForRetry($logData);
}
Check Logs Enable debug mode to see if the bundle’s listeners are triggered:
bin/console debug:event-dispatcher | grep es_log
Verify Annotations
Use the doctrine:schema:validate command to ensure annotations are parsed:
bin/console doctrine:schema:validate
Inspect Elasticsearch Query the index directly to confirm logs:
curl -XGET 'http://localhost:9200/your_app_logs/_search?pretty'
Custom Log Format
Override the default log structure by extending DualMedia\EsLogBundle\Logger\ElasticsearchLogger:
class CustomLogger extends ElasticsearchLogger {
protected function getLogData(): array {
return array_merge(parent::getLogData(), ['custom_field' => 'value']);
}
}
Register it in services.yaml:
DualMedia\EsLogBundle\Logger\ElasticsearchLogger: '@App\Logger\CustomLogger'
Dynamic Property Tracking
Use a #[AsTrackedProperty] attribute with logic to conditionally track properties:
#[AsTrackedProperty(condition: 'strlen($this->name) > 5')]
private string $name;
Implement ConditionInterface for custom conditions.
Batch Processing For high-volume logs, batch inserts into Elasticsearch:
$bulkData = [];
foreach ($logs as $log) {
$bulkData[] = ['index' => ['_index' => 'logs', '_id' => $log->getId()]];
$bulkData[] = $log->toArray();
}
$this->esClient->bulk($bulkData);
How can I help you explore Laravel packages today?