feat(logging): implement log pruning functionality

- Add LogPruneService to handle the deletion of old logs based on retention settings.
- Create PruneLogsCommand to provide a console command for log pruning.
- Introduce PruneLogsMessage and PruneLogsHandler for message handling related to log pruning.
- Update the AST cache with new classes and their relationships.
This commit is contained in:
hamed
2026-07-01 21:50:51 +03:30
parent a31e8b4314
commit 7814bcc0de
27 changed files with 1667 additions and 802 deletions
@@ -1989,4 +1989,12 @@ class AdminApiController extends BaseController
'created_at' => (int) $l['createdAt'],
], $rows), (int) $total, $page, $limit);
}
#[Route('/api/v1/admin/logs', methods: ['DELETE'])]
public function clearLogs(): JsonResponse
{
$deleted = $this->em->getRepository(AppLog::class)->deleteAll();
return $this->success(['deleted' => $deleted]);
}
}
@@ -28,6 +28,7 @@ class SiteConfigController extends BaseController
'support_phone',
'max_cancel_hours_before',
'appointment_reminder_hours',
'log_retention_days',
// payment gateways
'payment_test_mode',
'payment_allowed_frontend_hosts',
@@ -22,6 +22,8 @@ class SiteConfigRepository extends ServiceEntityRepository
'support_phone' => '',
'max_cancel_hours_before' => '24',
'appointment_reminder_hours' => '2',
// logging (0 = نگهداری نامحدود)
'log_retention_days' => '90',
// payment gateways
'payment_test_mode' => '0',
'mellat_terminal_id' => '',
+4
View File
@@ -3,6 +3,7 @@
namespace App;
use App\Appointment\Message\ExpireAppointmentsMessage;
use App\Shared\Logging\Message\PruneLogsMessage;
use Symfony\Component\Scheduler\Attribute\AsSchedule;
use Symfony\Component\Scheduler\RecurringMessage;
use Symfony\Component\Scheduler\Schedule as SymfonySchedule;
@@ -24,6 +25,9 @@ class Schedule implements ScheduleProviderInterface
->processOnlyLastMissedRun(true) // ensure only last missed task is run
->add(
RecurringMessage::every('1 minute', new ExpireAppointmentsMessage())
)
->add(
RecurringMessage::every('1 day', new PruneLogsMessage())
);
}
}
+20
View File
@@ -8,4 +8,24 @@ use Doctrine\Persistence\ManagerRegistry;
class AppLogRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry) { parent::__construct($registry, AppLog::class); }
/** حذف همه‌ی لاگ‌ها؛ تعداد ردیف‌های حذف‌شده را برمی‌گرداند. */
public function deleteAll(): int
{
return (int) $this->createQueryBuilder('l')
->delete()
->getQuery()
->execute();
}
/** حذف لاگ‌های قدیمی‌تر از timestamp داده‌شده؛ تعداد حذف‌شده را برمی‌گرداند. */
public function deleteOlderThan(int $timestamp): int
{
return (int) $this->createQueryBuilder('l')
->delete()
->where('l.createdAt < :ts')
->setParameter('ts', $timestamp)
->getQuery()
->execute();
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Shared\Logging\Command;
use App\Shared\Logging\LogPruneService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
#[AsCommand(name: 'app:prune-logs', description: 'حذف لاگ‌های قدیمی‌تر از مدت نگهداری تنظیم‌شده')]
final class PruneLogsCommand extends Command
{
public function __construct(private readonly LogPruneService $pruneService)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$deleted = $this->pruneService->pruneByRetention();
$output->writeln(sprintf('%d لاگ حذف شد.', $deleted));
return Command::SUCCESS;
}
}
+30
View File
@@ -0,0 +1,30 @@
<?php
namespace App\Shared\Logging;
use App\Config\Repository\SiteConfigRepository;
/**
* حذف لاگ‌های قدیمی‌تر از مدت نگهداری تنظیم‌شده (`log_retention_days`).
* مقدار ۰ یا نامعتبر یعنی نگهداری نامحدود — چیزی حذف نمی‌شود.
*/
class LogPruneService
{
public function __construct(
private readonly AppLogRepository $logRepo,
private readonly SiteConfigRepository $configRepo,
) {}
/** تعداد لاگ‌های حذف‌شده را برمی‌گرداند. */
public function pruneByRetention(): int
{
$days = (int) ($this->configRepo->get('log_retention_days') ?? 0);
if ($days <= 0) {
return 0;
}
$cutoff = time() - ($days * 86400);
return $this->logRepo->deleteOlderThan($cutoff);
}
}
@@ -0,0 +1,7 @@
<?php
namespace App\Shared\Logging\Message;
class PruneLogsMessage
{
}
@@ -0,0 +1,18 @@
<?php
namespace App\Shared\Logging\MessageHandler;
use App\Shared\Logging\LogPruneService;
use App\Shared\Logging\Message\PruneLogsMessage;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
final class PruneLogsHandler
{
public function __construct(private readonly LogPruneService $pruneService) {}
public function __invoke(PruneLogsMessage $message): void
{
$this->pruneService->pruneByRetention();
}
}