diff --git a/migrations/Version20260320071724.php b/migrations/Version20260320071724.php new file mode 100644 index 0000000..6b5171a --- /dev/null +++ b/migrations/Version20260320071724.php @@ -0,0 +1,28 @@ +addSql('ALTER TABLE log_entry ADD error_code VARCHAR(12) DEFAULT NULL'); + $this->addSql('CREATE INDEX IDX_LOG_ENTRY_ERROR_CODE ON log_entry (error_code)'); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP INDEX IDX_LOG_ENTRY_ERROR_CODE ON log_entry'); + $this->addSql('ALTER TABLE log_entry DROP error_code'); + } +} diff --git a/src/Command/LogEntryBackfillErrorCodesCommand.php b/src/Command/LogEntryBackfillErrorCodesCommand.php new file mode 100644 index 0000000..8e2c8ff --- /dev/null +++ b/src/Command/LogEntryBackfillErrorCodesCommand.php @@ -0,0 +1,75 @@ +info('Starting backfill of error codes...'); + + $totalProcessed = 0; + $totalUpdated = 0; + + while (true) { + $entries = $this->logEntryRepository->createQueryBuilder('l') + ->where('l.errorCode IS NULL') + ->andWhere('l.extra LIKE :pattern') + ->setParameter('pattern', '%error_code%') + ->orderBy('l.id', 'ASC') + ->setMaxResults(self::BATCH_SIZE) + ->getQuery() + ->getResult(); + + if ([] === $entries) { + break; + } + + foreach ($entries as $entry) { + /** @var LogEntry $entry */ + $extra = $entry->getExtra(); + if (isset($extra['error_code']) && null === $entry->getErrorCode()) { + $entry->setErrorCode($extra['error_code']); + ++$totalUpdated; + } + ++$totalProcessed; + } + + $this->entityManager->flush(); + $this->entityManager->clear(); + + $io->writeln(sprintf('Processed %d entries, %d updated so far', $totalProcessed, $totalUpdated)); + } + + $message = sprintf('Backfill complete. Processed %d entries, updated %d with error codes.', $totalProcessed, $totalUpdated); + $io->success($message); + + return Command::SUCCESS; + } +} diff --git a/src/Controller/Admin/LogEntryCrudController.php b/src/Controller/Admin/LogEntryCrudController.php index e7b417a..2ba2928 100644 --- a/src/Controller/Admin/LogEntryCrudController.php +++ b/src/Controller/Admin/LogEntryCrudController.php @@ -4,16 +4,27 @@ declare(strict_types=1); namespace App\Controller\Admin; +use App\Admin\Field\JsonDataField; use App\Entity\LogEntry; +use App\Repository\LogEntryRepository; +use App\Service\XmlDumpService; use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController; use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; +use League\Flysystem\FilesystemException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; class LogEntryCrudController extends AbstractCrudController { + public function __construct( + private readonly LogEntryRepository $logEntryRepository, + ) { + } + public static function getEntityFqcn(): string { return LogEntry::class; @@ -22,7 +33,7 @@ class LogEntryCrudController extends AbstractCrudController public function configureActions(Actions $actions): Actions { $viewDumps = Action::new('viewDumps', 'XML Dumps', 'fa fa-file-code') - ->linkToRoute('admin_xml_dump_list', static fn (LogEntry $entity): array => ['id' => $entity->getId()]) + ->linkToCrudAction('xmlDumps') ->displayIf(static fn (LogEntry $entity): bool => 'bpn' === $entity->getChannel()); return $actions @@ -42,18 +53,45 @@ class LogEntryCrudController extends AbstractCrudController return $crud ->setEntityLabelInSingular('Logeintrag') ->setEntityLabelInPlural('Logeinträge') - ->setDefaultSort(['createdAt' => 'DESC']); + ->setDefaultSort(['createdAt' => 'DESC']) + ->setSearchFields(['errorCode', 'message', 'channel', 'extra']); } public function configureFields(string $pageName): iterable { - return [ - DateTimeField::new('createdAt', 'Zeitstempel'), - TextField::new('errorCode', 'Fehlercode'), - TextField::new('username', 'Benutzer'), - TextField::new('message', 'Aktion'), - TextField::new('requestId', 'Request ID'), - TextField::new('uri', 'URI'), - ]; + yield DateTimeField::new('createdAt', 'Zeitstempel'); + yield TextField::new('errorCode', 'Fehlercode') + ->formatValue(static fn (?string $value): string => $value ?? '-'); + yield TextField::new('username', 'Benutzer'); + yield TextField::new('message', 'Aktion'); + yield TextField::new('requestId', 'Request ID'); + yield TextField::new('uri', 'URI'); + yield JsonDataField::new('context', 'Kontext')->onlyOnDetail(); + yield JsonDataField::new('extra', 'Extra')->onlyOnDetail(); + } + + public function xmlDumps(Request $request, XmlDumpService $xmlDumpService): Response + { + $entityId = $request->query->getInt('entityId'); + if ($entityId <= 0) { + throw $this->createNotFoundException('Log entry not found.'); + } + + $entity = $this->logEntryRepository->find($entityId); + if (!$entity instanceof LogEntry) { + throw $this->createNotFoundException('Log entry not found.'); + } + + $dumps = []; + try { + $dumps = $xmlDumpService->findDumpsForRequestId($entity->getRequestId()); + } catch (FilesystemException) { + } + + return $this->render('admin/xml_dump/list.html.twig', [ + 'logEntry' => $entity, + 'requestId' => $entity->getRequestId(), + 'dumps' => $dumps, + ]); } } diff --git a/src/Entity/LogEntry.php b/src/Entity/LogEntry.php index 4601eae..adc88aa 100644 --- a/src/Entity/LogEntry.php +++ b/src/Entity/LogEntry.php @@ -8,6 +8,7 @@ use App\Repository\LogEntryRepository; use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: LogEntryRepository::class)] +#[ORM\Table(indexes: [new ORM\Index(name: 'IDX_LOG_ENTRY_ERROR_CODE', columns: ['error_code'])])] class LogEntry { #[ORM\Id] @@ -30,6 +31,9 @@ class LogEntry #[ORM\Column(type: 'datetime_immutable')] private \DateTimeImmutable $createdAt; + #[ORM\Column(type: 'string', length: 12, nullable: true)] + private ?string $errorCode = null; + public function __construct(string $channel, string $message) { $this->channel = $channel; @@ -112,9 +116,16 @@ class LogEntry return $this->getExtra()['request_id'] ?? '-'; } - public function getErrorCode(): string + public function getErrorCode(): ?string { - return $this->getExtra()['error_code'] ?? '-'; + return $this->errorCode; + } + + public function setErrorCode(?string $errorCode): self + { + $this->errorCode = $errorCode; + + return $this; } public function getUri(): string diff --git a/src/Logger/DatabaseHandler.php b/src/Logger/DatabaseHandler.php index 8e1232d..367b023 100644 --- a/src/Logger/DatabaseHandler.php +++ b/src/Logger/DatabaseHandler.php @@ -32,6 +32,7 @@ class DatabaseHandler extends AbstractProcessingHandler $logEntry ->setContext($record->context) ->setExtra($record->extra) + ->setErrorCode($record->extra['error_code'] ?? null) ; $this->entityManager->persist($logEntry); diff --git a/src/Repository/LogEntryRepository.php b/src/Repository/LogEntryRepository.php index bd80722..a459c3e 100644 --- a/src/Repository/LogEntryRepository.php +++ b/src/Repository/LogEntryRepository.php @@ -27,4 +27,13 @@ class LogEntryRepository extends ServiceEntityRepository ->getQuery() ->execute(); } + + public function findWithErrorCodes(): array + { + return $this->createQueryBuilder('l') + ->where('l.errorCode IS NOT NULL') + ->orderBy('l.createdAt', 'DESC') + ->getQuery() + ->getResult(); + } } diff --git a/templates/admin/xml_dump/list.html.twig b/templates/admin/xml_dump/list.html.twig index 4e4676b..1393a00 100644 --- a/templates/admin/xml_dump/list.html.twig +++ b/templates/admin/xml_dump/list.html.twig @@ -23,7 +23,7 @@