benjamin-rqt/data-watcher-bundle
Symfony bundle for database anomaly detection: define SQL or Doctrine checks, run them manually or on a cron schedule, store execution history, and send email notifications when anomalies are detected. Supports Symfony 6.3, 7.0 and 8.0, Messenger, Mailer and Twig.
Symfony bundle for database anomaly detection with email notifications and execution history.
AbstractDoctrineCheck)composer.json{
"repositories": [
{
"type": "vcs",
"url": "https://github.com/BenjaminRqt/data-watcher-bundle"
}
]
}
composer require benjamin-rqt/data-watcher-bundle
// config/bundles.php
return [
// ...
BenjaminRqt\DataWatcherBundle\DataWatcherBundle::class => ['all' => true],
];
# config/packages/data_watcher.yaml
data_watcher:
from_email: '[email protected]'
recipients:
- '[email protected]'
app_name: 'My Application' # Optional, used in email subjects and templates
# config/packages/messenger.yaml
framework:
messenger:
transports:
scheduler_data_watcher:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
BenjaminRqt\DataWatcherBundle\Scheduler\Message\RunCheckMessage: scheduler_data_watcher
.env.local)MAILER_DSN=smtp://user:[email protected]:587
MESSENGER_TRANSPORT_DSN=doctrine://default
This bundle uses a custom table data_watcher_run to store the history of checks. Run the following command to create it:
php bin/console data-watcher:setup
Create a class in your app (not in the bundle):
<?php
namespace App\DataWatcher;
use BenjaminRqt\DataWatcherBundle\Check\AbstractSqlCheck;
final class MyCheck extends AbstractSqlCheck
{
public function getName(): string { return 'my.sql.check'; }
public function getDescription(): string { return 'Detects X'; }
public function getSchedule(): string { return '0 9 * * 1-5'; } // Mon-Fri at 9:00 AM
protected function getSql(): string
{
return "SELECT id, name FROM my_table WHERE problem = 1";
}
}
use BenjaminRqt\DataWatcherBundle\Check\AbstractDoctrineCheck;
use Doctrine\ORM\Query;
final class MyDoctrineCheck extends AbstractDoctrineCheck
{
public function getName(): string { return 'my.doctrine.check'; }
public function getDescription(): string { return 'Detects Y via ORM'; }
public function getSchedule(): string { return '@daily'; }
protected function buildQuery(): Query
{
return $this->em->getRepository(User::class)
->createQueryBuilder('u')
->select('u.id')
->where('u.email LIKE :s')
->setParameter('s', '%admin%')
->getQuery();
}
}
use BenjaminRqt\DataWatcherBundle\Check\AbstractCallableCheck;
final class MyCallableCheck extends AbstractCallableCheck
{
public function __construct(private readonly MyCustomCallableHandler $callableHandler) {}
public function getName(): string { return 'my.callable.check'; }
public function getDescription(): string { return 'Detects Z via callable'; }
public function getSchedule(): string { return '@daily'; }
protected function getCallable(): ArrayCallableInterface
{
return $this->callableHandler;
}
}
Then declare the handler containing the callable and the detection logic:
use BenjaminRqt\DataWatcherBundle\Check\ArrayCallableInterface;
final class CallableHandler implements ArrayCallableInterface
{
public function __invoke(): array
{
return [
['id' => 42],
];
}
}
public function getMaxAnomalies(): int
{
return 5; // 1 by default, here at least 5 results are needed to send an alert
}
public function getRecipients(): array
{
return ['[email protected]']; // overrides global config
}
# Setup the database table for history
php bin/console data-watcher:setup
# List all registered checks
php bin/console data-watcher:run --list
# Test without email or history
php bin/console data-watcher:run my.check --dry-run
# Run a check (email + history)
php bin/console data-watcher:run my.check
# Run all checks
php bin/console data-watcher:run
composer require symfony/scheduler
# Development
php bin/console messenger:consume scheduler_data_watcher -vv
# Production (with Supervisor or systemd)
php bin/console messenger:consume scheduler_data_watcher --time-limit=3600
No configuration is required for the scheduler, it automatically detects CRON expressions defined in checks and executes them at the right time.
| Expression | Meaning |
|---|---|
0 8 * * * |
Every day at 8:00 AM |
*/30 * * * * |
Every 30 minutes |
0 9 * * 1-5 |
Mon–Fri at 9:00 AM |
@daily |
Once a day |
@hourly |
Once an hour |
How can I help you explore Laravel packages today?