From ab438c6cd00a92fe9567ce773db1d51bd4a33d29 Mon Sep 17 00:00:00 2001 From: Bob den Otter Date: Fri, 3 Jan 2020 16:15:26 +0100 Subject: [PATCH 1/7] Add logging (viewable in backend), next to exisiting Monolog file-based logging --- config/packages/dev/monolog.yaml | 5 + config/services.yaml | 5 + public/theme/leaf | 1 + .../Frontend/HomepageController.php | 9 +- src/Entity/Log.php | 234 ++++++++++++++++++ src/Log/LogHandler.php | 46 ++++ src/Log/RequestProcessor.php | 67 +++++ src/Repository/LogRepository.php | 50 ++++ 8 files changed, 416 insertions(+), 1 deletion(-) create mode 120000 public/theme/leaf create mode 100644 src/Entity/Log.php create mode 100644 src/Log/LogHandler.php create mode 100644 src/Log/RequestProcessor.php create mode 100644 src/Repository/LogRepository.php diff --git a/config/packages/dev/monolog.yaml b/config/packages/dev/monolog.yaml index 0487ef9c..9097062d 100644 --- a/config/packages/dev/monolog.yaml +++ b/config/packages/dev/monolog.yaml @@ -1,4 +1,5 @@ monolog: + channels: ['db'] handlers: main: type: stream @@ -17,3 +18,7 @@ monolog: type: console process_psr_3_messages: false channels: ['!event', '!doctrine', '!console'] + db: + channels: ['db'] + type: service + id: Bolt\Log\LogHandler \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 66b95f91..ecb9699f 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -87,5 +87,10 @@ services: Doctrine\ORM\Query\Expr: ~ + monolog.processor.request: + class: Bolt\Log\RequestProcessor + tags: + - { name: monolog.processor, method: processRecord, handler: db } + Twig\Extension\StringLoaderExtension: ~ diff --git a/public/theme/leaf b/public/theme/leaf new file mode 120000 index 00000000..94662826 --- /dev/null +++ b/public/theme/leaf @@ -0,0 +1 @@ +../../../themedev/bolt-leaf-theme \ No newline at end of file diff --git a/src/Controller/Frontend/HomepageController.php b/src/Controller/Frontend/HomepageController.php index 1c47f739..7936f55c 100644 --- a/src/Controller/Frontend/HomepageController.php +++ b/src/Controller/Frontend/HomepageController.php @@ -7,6 +7,7 @@ namespace Bolt\Controller\Frontend; use Bolt\Controller\TwigAwareController; use Bolt\Repository\ContentRepository; use Bolt\TemplateChooser; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -15,9 +16,13 @@ class HomepageController extends TwigAwareController implements FrontendZone /** @var TemplateChooser */ private $templateChooser; - public function __construct(TemplateChooser $templateChooser) + /** @var LoggerInterface */ + private $logger; + + public function __construct(TemplateChooser $templateChooser, LoggerInterface $dbLogger) { $this->templateChooser = $templateChooser; + $this->logger = $dbLogger; } /** @@ -40,6 +45,8 @@ class HomepageController extends TwigAwareController implements FrontendZone $templates = $this->templateChooser->forHomepage(); + $this->logger->notice('Joehoe!', ['foo' => 'bar']); + return $this->renderTemplate($templates, ['record' => $record]); } } diff --git a/src/Entity/Log.php b/src/Entity/Log.php new file mode 100644 index 00000000..c0da09a9 --- /dev/null +++ b/src/Entity/Log.php @@ -0,0 +1,234 @@ +id; + } + + /** + * @param mixed $id + * @return Log + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * @return mixed + */ + public function getMessage() + { + return $this->message; + } + + /** + * @param mixed $message + * @return Log + */ + public function setMessage($message) + { + $this->message = $message; + return $this; + } + + /** + * @return mixed + */ + public function getContext() + { + return $this->context; + } + + /** + * @param mixed $context + * @return Log + */ + public function setContext($context) + { + $this->context = $context; + return $this; + } + + /** + * @return mixed + */ + public function getLevel() + { + return $this->level; + } + + /** + * @param mixed $level + * @return Log + */ + public function setLevel($level) + { + $this->level = $level; + return $this; + } + + /** + * @return mixed + */ + public function getLevelName() + { + return $this->levelName; + } + + /** + * @param mixed $levelName + * @return Log + */ + public function setLevelName($levelName) + { + $this->levelName = $levelName; + return $this; + } + + /** + * @return mixed + */ + public function getExtra() + { + return $this->extra; + } + + /** + * @param mixed $extra + * @return Log + */ + public function setExtra($extra) + { + dump($extra); + $this->extra = $extra; + return $this; + } + + /** + * @return mixed + */ + public function getCreatedAt() + { + return $this->createdAt; + } + + /** + * @param mixed $createdAt + * @return Log + */ + public function setCreatedAt($createdAt) + { + $this->createdAt = $createdAt; + return $this; + } + + + /** + * @return mixed + */ + public function getLocation() + { + return $this->location; + } + + /** + * @param mixed $location + * @return Log + */ + public function setLocation($location) + { + $this->location = $location; + return $this; + } + + /** + * @return mixed + */ + public function getUser() + { + return $this->user; + } + + /** + * @param mixed $user + * @return Log + */ + public function setUser($user) + { + dump($user); + $this->user = $user; + return $this; + } + + /** + * @ORM\PrePersist + */ + public function onPrePersist() + { + $this->createdAt = new \DateTime(); + } +} \ No newline at end of file diff --git a/src/Log/LogHandler.php b/src/Log/LogHandler.php new file mode 100644 index 00000000..d7349800 --- /dev/null +++ b/src/Log/LogHandler.php @@ -0,0 +1,46 @@ +em = $em; + } + + /** + * Called when writing to our database + * @param array $record + */ + protected function write(array $record) + { + $logEntry = new Log(); + $logEntry->setMessage($record['message']); + $logEntry->setLevel($record['level']); + $logEntry->setLevelName($record['level_name']); + $logEntry->setExtra($record['extra']); + $logEntry->setUser($record['user'] ?? null); + $logEntry->setLocation($record['location'] ?? null); + $logEntry->setContext($record['context']); + + $this->em->persist($logEntry); + $this->em->flush(); + } +} diff --git a/src/Log/RequestProcessor.php b/src/Log/RequestProcessor.php new file mode 100644 index 00000000..d9cd828b --- /dev/null +++ b/src/Log/RequestProcessor.php @@ -0,0 +1,67 @@ +request = $request; + $this->security = $security; + } + + /** + * @param array $record + * @return array + */ + public function processRecord(array $record): array + { + $req = $this->request->getCurrentRequest(); + + /** @var User $user */ + $user = $this->security->getUser(); + + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 7); + + $record['extra'] = [ + 'client_ip' => $req->getClientIp(), + 'client_port' => $req->getPort(), + 'uri' => $req->getUri(), + 'query_string' => $req->getQueryString(), + 'method' => $req->getMethod(), + 'request' => $req->request->all() + ]; + + if ($user) { + $record['user'] = [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + 'roles' => $user->getRoles(), + ]; + } + + $record['location'] = [ + 'file' => $trace[5]['file'], + 'line' => $trace[5]['line'], + 'class' => $trace[6]['class'], + 'type' => $trace[6]['type'], + 'function' => $trace[6]['function'], + + ]; + + return $record; + } +} \ No newline at end of file diff --git a/src/Repository/LogRepository.php b/src/Repository/LogRepository.php new file mode 100644 index 00000000..a2531153 --- /dev/null +++ b/src/Repository/LogRepository.php @@ -0,0 +1,50 @@ +createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('l.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Log + { + return $this->createQueryBuilder('l') + ->andWhere('l.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ +} From 003b1654cb69d2941df7f288c09becca218944c3 Mon Sep 17 00:00:00 2001 From: Bob den Otter Date: Fri, 3 Jan 2020 16:20:57 +0100 Subject: [PATCH 2/7] Working --- src/Entity/Log.php | 117 +++++++------------------------ src/Log/LogHandler.php | 9 +-- src/Log/RequestProcessor.php | 24 +++---- src/Repository/LogRepository.php | 2 + 4 files changed, 39 insertions(+), 113 deletions(-) diff --git a/src/Entity/Log.php b/src/Entity/Log.php index c0da09a9..dbb59e37 100644 --- a/src/Entity/Log.php +++ b/src/Entity/Log.php @@ -1,5 +1,6 @@ createdAt = new \DateTime(); + } + + public function getId(): int { return $this->id; } - /** - * @param mixed $id - * @return Log - */ - public function setId($id) + public function setId(int $id): self { $this->id = $id; return $this; } - /** - * @return mixed - */ - public function getMessage() + public function getMessage(): string { return $this->message; } - /** - * @param mixed $message - * @return Log - */ - public function setMessage($message) + public function setMessage(string $message): self { $this->message = $message; return $this; } - /** - * @return mixed - */ - public function getContext() + public function getContext(): ?array { return $this->context; } - /** - * @param mixed $context - * @return Log - */ - public function setContext($context) + public function setContext(?array $context): self { $this->context = $context; return $this; } - /** - * @return mixed - */ - public function getLevel() + public function getLevel(): int { return $this->level; } - /** - * @param mixed $level - * @return Log - */ - public function setLevel($level) + public function setLevel(int $level): self { $this->level = $level; return $this; } - /** - * @return mixed - */ - public function getLevelName() + public function getLevelName(): string { return $this->levelName; } - /** - * @param mixed $levelName - * @return Log - */ - public function setLevelName($levelName) + public function setLevelName(string $levelName): self { $this->levelName = $levelName; return $this; } - /** - * @return mixed - */ - public function getExtra() + public function getExtra(): ?array { return $this->extra; } - /** - * @param mixed $extra - * @return Log - */ - public function setExtra($extra) + public function setExtra(?array $extra): self { - dump($extra); $this->extra = $extra; return $this; } - /** - * @return mixed - */ - public function getCreatedAt() + public function getCreatedAt(): \DateTime { return $this->createdAt; } - /** - * @param mixed $createdAt - * @return Log - */ - public function setCreatedAt($createdAt) + public function setCreatedAt(\DateTime $createdAt): self { $this->createdAt = $createdAt; return $this; } - - /** - * @return mixed - */ - public function getLocation() + public function getLocation(): ?array { return $this->location; } - /** - * @param mixed $location - * @return Log - */ - public function setLocation($location) + public function setLocation(?array $location): self { $this->location = $location; return $this; } - /** - * @return mixed - */ - public function getUser() + public function getUser(): ?array { return $this->user; } - /** - * @param mixed $user - * @return Log - */ - public function setUser($user) + public function setUser(?array $user): self { - dump($user); $this->user = $user; return $this; } - - /** - * @ORM\PrePersist - */ - public function onPrePersist() - { - $this->createdAt = new \DateTime(); - } -} \ No newline at end of file +} diff --git a/src/Log/LogHandler.php b/src/Log/LogHandler.php index d7349800..794dc91a 100644 --- a/src/Log/LogHandler.php +++ b/src/Log/LogHandler.php @@ -1,9 +1,9 @@ setMessage($record['message']); diff --git a/src/Log/RequestProcessor.php b/src/Log/RequestProcessor.php index d9cd828b..ba00c88c 100644 --- a/src/Log/RequestProcessor.php +++ b/src/Log/RequestProcessor.php @@ -1,11 +1,10 @@ security = $security; } - /** - * @param array $record - * @return array - */ public function processRecord(array $record): array { $req = $this->request->getCurrentRequest(); @@ -37,13 +32,13 @@ class RequestProcessor $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 7); $record['extra'] = [ - 'client_ip' => $req->getClientIp(), - 'client_port' => $req->getPort(), - 'uri' => $req->getUri(), - 'query_string' => $req->getQueryString(), - 'method' => $req->getMethod(), - 'request' => $req->request->all() - ]; + 'client_ip' => $req->getClientIp(), + 'client_port' => $req->getPort(), + 'uri' => $req->getUri(), + 'query_string' => $req->getQueryString(), + 'method' => $req->getMethod(), + 'request' => $req->request->all(), + ]; if ($user) { $record['user'] = [ @@ -59,9 +54,8 @@ class RequestProcessor 'class' => $trace[6]['class'], 'type' => $trace[6]['type'], 'function' => $trace[6]['function'], - ]; return $record; } -} \ No newline at end of file +} diff --git a/src/Repository/LogRepository.php b/src/Repository/LogRepository.php index a2531153..1f74bdba 100644 --- a/src/Repository/LogRepository.php +++ b/src/Repository/LogRepository.php @@ -1,5 +1,7 @@ Date: Fri, 3 Jan 2020 17:39:46 +0100 Subject: [PATCH 3/7] Working on logging --- .../Backend/LogViewerController.php | 34 +++++++++ .../Frontend/HomepageController.php | 2 +- src/Log/LogHandler.php | 2 +- src/Log/RequestProcessor.php | 12 ++- src/Repository/LogRepository.php | 43 +++++------ templates/pages/logviewer.html.twig | 74 +++++++++++++++++++ 6 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 src/Controller/Backend/LogViewerController.php create mode 100644 templates/pages/logviewer.html.twig diff --git a/src/Controller/Backend/LogViewerController.php b/src/Controller/Backend/LogViewerController.php new file mode 100644 index 00000000..40324786 --- /dev/null +++ b/src/Controller/Backend/LogViewerController.php @@ -0,0 +1,34 @@ +config->get('general/log/amount', 10); + $page = (int) $request->get('page', 1); + + /** @var Log $items */ + $items = $log->findLatest($page, $amount); + + return $this->renderTemplate('@bolt/pages/logviewer.html.twig', [ + 'items' => $items, + ]); + } +} diff --git a/src/Controller/Frontend/HomepageController.php b/src/Controller/Frontend/HomepageController.php index 7936f55c..52460d84 100644 --- a/src/Controller/Frontend/HomepageController.php +++ b/src/Controller/Frontend/HomepageController.php @@ -45,7 +45,7 @@ class HomepageController extends TwigAwareController implements FrontendZone $templates = $this->templateChooser->forHomepage(); - $this->logger->notice('Joehoe!', ['foo' => 'bar']); + $this->logger->notice('Huius, Lyco, oratione locuples, rebus ipsis ielunior. Quid autem habent admirationis, cum prope accesseris?!', ['foo' => 'bar']); return $this->renderTemplate($templates, ['record' => $record]); } diff --git a/src/Log/LogHandler.php b/src/Log/LogHandler.php index 794dc91a..26d85919 100644 --- a/src/Log/LogHandler.php +++ b/src/Log/LogHandler.php @@ -32,7 +32,7 @@ class LogHandler extends AbstractProcessingHandler $logEntry->setLevelName($record['level_name']); $logEntry->setExtra($record['extra']); $logEntry->setUser($record['user'] ?? null); - $logEntry->setLocation($record['location'] ?? null); + $logEntry->setLocation($record['location']); $logEntry->setContext($record['context']); $this->em->persist($logEntry); diff --git a/src/Log/RequestProcessor.php b/src/Log/RequestProcessor.php index ba00c88c..49f5667a 100644 --- a/src/Log/RequestProcessor.php +++ b/src/Log/RequestProcessor.php @@ -6,7 +6,9 @@ namespace Bolt\Log; use Bolt\Entity\User; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Security\Core\Security; +use Webmozart\PathUtil\Path; class RequestProcessor { @@ -16,10 +18,14 @@ class RequestProcessor /** @var Security */ private $security; - public function __construct(RequestStack $request, Security $security) + /** @var string */ + private $projectDir; + + public function __construct(RequestStack $request, Security $security, KernelInterface $kernel) { $this->request = $request; $this->security = $security; + $this->projectDir = $kernel->getProjectDir(); } public function processRecord(array $record): array @@ -40,7 +46,7 @@ class RequestProcessor 'request' => $req->request->all(), ]; - if ($user) { + if (! empty($user)) { $record['user'] = [ 'id' => $user->getId(), 'username' => $user->getUsername(), @@ -49,7 +55,7 @@ class RequestProcessor } $record['location'] = [ - 'file' => $trace[5]['file'], + 'file' => '…/' . Path::makeRelative($trace[5]['file'], $this->projectDir), 'line' => $trace[5]['line'], 'class' => $trace[6]['class'], 'type' => $trace[6]['type'], diff --git a/src/Repository/LogRepository.php b/src/Repository/LogRepository.php index 1f74bdba..cf1cfb14 100644 --- a/src/Repository/LogRepository.php +++ b/src/Repository/LogRepository.php @@ -7,6 +7,10 @@ namespace Bolt\Repository; use Bolt\Entity\Log; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Common\Persistence\ManagerRegistry; +use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; +use Pagerfanta\Adapter\DoctrineORMAdapter; +use Pagerfanta\Pagerfanta; /** * @method Log|null find($id, $lockMode = null, $lockVersion = null) @@ -21,32 +25,25 @@ class LogRepository extends ServiceEntityRepository parent::__construct($registry, Log::class); } - // /** - // * @return Log[] Returns an array of Log objects - // */ - /* - public function findByExampleField($value) + public function getQueryBuilder(): QueryBuilder { - return $this->createQueryBuilder('l') - ->andWhere('l.exampleField = :val') - ->setParameter('val', $value) - ->orderBy('l.id', 'ASC') - ->setMaxResults(10) - ->getQuery() - ->getResult() - ; + return $this->createQueryBuilder('log'); } - */ - /* - public function findOneBySomeField($value): ?Log + public function findLatest(int $page = 1, int $amount = 6): Pagerfanta { - return $this->createQueryBuilder('l') - ->andWhere('l.exampleField = :val') - ->setParameter('val', $value) - ->getQuery() - ->getOneOrNullResult() - ; + $qb = $this->getQueryBuilder() + ->orderBy('log.createdAt', 'DESC') + ->setMaxResults($amount); + + return $this->createPaginator($qb->getQuery(), $page, $amount); + } + + private function createPaginator(Query $query, int $page, int $amountPerPage): Pagerfanta + { + $paginator = new Pagerfanta(new DoctrineORMAdapter($query, true, true)); + $paginator->setMaxPerPage($amountPerPage); + $paginator->setCurrentPage($page); + return $paginator; } - */ } diff --git a/templates/pages/logviewer.html.twig b/templates/pages/logviewer.html.twig new file mode 100644 index 00000000..da068d6c --- /dev/null +++ b/templates/pages/logviewer.html.twig @@ -0,0 +1,74 @@ +{% extends '@bolt/_base/layout.html.twig' %} +{% import '@bolt/_macro/_macro.html.twig' as macro %} + +{% block title %} + {{ macro.icon('tachometer-alt') }} + {{ 'caption.logviewer'|trans }} +{% endblock title %} + +{# This 'topsection' gets output _before_ the main form, allowing `dump()`, without breaking Vue #} +{% block topsection %} + +{% endblock %} + +{# The 'main' section is the main contents of the page. Usually this is Vue-ified. #} +{% block main %} + + + {% for item in items %} + + + + + + + + + {% endfor %} +
+ № {{ item.id }} + + {{ item.levelName }} + + + + + + {{ item.message }} + + {{ item.createdAt|date('Y-m-d H:i:s') }}
+ {% if item.user %} + {{ item.user.username }}(№ {{ item.user.id }}) + {% else %} + - + {% endif %} +
+ + {{ pager(items, template = '@bolt/helpers/_pager_bootstrap.html.twig', class="justify-content-center") }} + + +{% endblock %} + + From 9baff64d166c92bf72c6e16fe4869ef28b10ab26 Mon Sep 17 00:00:00 2001 From: Bob den Otter Date: Sat, 4 Jan 2020 13:42:22 +0100 Subject: [PATCH 4/7] working --- config/services.yaml | 2 +- public/theme/leaf | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 120000 public/theme/leaf diff --git a/config/services.yaml b/config/services.yaml index ecb9699f..cadcc197 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -69,7 +69,7 @@ services: Bolt\Menu\BackendMenuBuilderInterface: '@Bolt\Menu\BackendMenuBuilder' Bolt\Menu\FrontendMenuBuilder: ~ - + Bolt\Menu\FrontendMenuBuilderInterface: '@Bolt\Menu\FrontendMenuBuilder' # Needed for SetContent from bolt/core diff --git a/public/theme/leaf b/public/theme/leaf deleted file mode 120000 index 94662826..00000000 --- a/public/theme/leaf +++ /dev/null @@ -1 +0,0 @@ -../../../themedev/bolt-leaf-theme \ No newline at end of file From 48aec6758eae7f7a109a05660eb5a7a60d1f5e52 Mon Sep 17 00:00:00 2001 From: Bob den Otter Date: Sat, 4 Jan 2020 14:24:29 +0100 Subject: [PATCH 5/7] Working on Log Viewer --- config/packages/dev/monolog.yaml | 2 +- config/packages/prod/monolog.yaml | 7 ++- src/Menu/BackendMenuBuilder.php | 61 ++++++++++++++++--------- src/Security/LoginFormAuthenticator.php | 21 ++++++++- src/Security/LogoutListener.php | 14 +++++- templates/pages/logviewer.html.twig | 45 +++++++++++++----- translations/messages.en.xlf | 54 ++++++++++++++++++++++ 7 files changed, 166 insertions(+), 38 deletions(-) diff --git a/config/packages/dev/monolog.yaml b/config/packages/dev/monolog.yaml index 9097062d..0ba7a2c1 100644 --- a/config/packages/dev/monolog.yaml +++ b/config/packages/dev/monolog.yaml @@ -15,7 +15,7 @@ monolog: # type: chromephp # level: info console: - type: console + type: console process_psr_3_messages: false channels: ['!event', '!doctrine', '!console'] db: diff --git a/config/packages/prod/monolog.yaml b/config/packages/prod/monolog.yaml index 04a2ae11..e1cf6f67 100644 --- a/config/packages/prod/monolog.yaml +++ b/config/packages/prod/monolog.yaml @@ -1,4 +1,5 @@ monolog: + channels: ['db'] handlers: main: type: fingers_crossed @@ -10,6 +11,10 @@ monolog: path: '%kernel.logs_dir%/%kernel.environment%.log' level: debug console: - type: console + type: console process_psr_3_messages: false channels: ['!event', '!doctrine'] + db: + channels: ['db'] + type: service + id: Bolt\Log\LogHandler diff --git a/src/Menu/BackendMenuBuilder.php b/src/Menu/BackendMenuBuilder.php index d6ce975d..9d640256 100644 --- a/src/Menu/BackendMenuBuilder.php +++ b/src/Menu/BackendMenuBuilder.php @@ -8,6 +8,7 @@ use Bolt\Configuration\Config; use Bolt\Configuration\Content\ContentType; use Bolt\Repository\ContentRepository; use Bolt\Twig\ContentExtension; +use Bolt\Version; use Knp\Menu\FactoryInterface; use Knp\Menu\ItemInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; @@ -207,6 +208,14 @@ final class BackendMenuBuilder implements BackendMenuBuilderInterface ], ]); + $menu->getChild('Maintenance')->addChild('Log viewer', [ + 'uri' => $this->urlGenerator->generate('bolt_logviewer'), + 'extras' => [ + 'name' => $t->trans('caption.logviewer'), + 'icon' => 'fa-clipboard', + ], + ]); + $menu->getChild('Maintenance')->addChild('Bolt API', [ 'uri' => $this->urlGenerator->generate('api_entrypoint'), 'extras' => [ @@ -215,13 +224,16 @@ final class BackendMenuBuilder implements BackendMenuBuilderInterface ], ]); - $menu->getChild('Maintenance')->addChild('Fixtures', [ - 'uri' => '', - 'extras' => [ - 'name' => $t->trans('caption.fixtures_dummy_content'), - 'icon' => 'fa-hat-wizard', - ], - ]); + /* + * @todo Make fixtures work from the backend + */ + // $menu->getChild('Maintenance')->addChild('Fixtures', [ + // 'uri' => '', + // 'extras' => [ + // 'name' => $t->trans('caption.fixtures_dummy_content'), + // 'icon' => 'fa-hat-wizard', + // ], + // ]); $menu->getChild('Maintenance')->addChild('Clear the cache', [ 'uri' => $this->urlGenerator->generate('bolt_clear_cache'), @@ -231,13 +243,16 @@ final class BackendMenuBuilder implements BackendMenuBuilderInterface ], ]); - $menu->getChild('Maintenance')->addChild('Installation checks', [ - 'uri' => '', - 'extras' => [ - 'name' => $t->trans('caption.installation_checks'), - 'icon' => 'fa-clipboard-check', - ], - ]); + /* + * @todo Make Installation checks work from the backend + */ + // $menu->getChild('Maintenance')->addChild('Installation checks', [ + // 'uri' => '', + // 'extras' => [ + // 'name' => $t->trans('caption.installation_checks'), + // 'icon' => 'fa-clipboard-check', + // ], + // ]); $menu->getChild('Maintenance')->addChild('Translations', [ 'uri' => $this->urlGenerator->generate('translation_index'), @@ -247,14 +262,16 @@ final class BackendMenuBuilder implements BackendMenuBuilderInterface ], ]); - // @todo When we're close to stable release, make this less prominent - $menu->getChild('Maintenance')->addChild('The Kitchensink', [ - 'uri' => $this->urlGenerator->generate('bolt_kitchensink'), - 'extras' => [ - 'name' => $t->trans('caption.kitchensink'), - 'icon' => 'fa-bath', - ], - ]); + // Hide this menu item, unless we're on a "Git clone" install. + if (Version::installType() === 'Git clone') { + $menu->getChild('Maintenance')->addChild('The Kitchensink', [ + 'uri' => $this->urlGenerator->generate('bolt_kitchensink'), + 'extras' => [ + 'name' => $t->trans('caption.kitchensink'), + 'icon' => 'fa-bath', + ], + ]); + } $menu->getChild('Maintenance')->addChild('About Bolt', [ 'uri' => $this->urlGenerator->generate('bolt_about'), diff --git a/src/Security/LoginFormAuthenticator.php b/src/Security/LoginFormAuthenticator.php index d7e03382..567c7d32 100644 --- a/src/Security/LoginFormAuthenticator.php +++ b/src/Security/LoginFormAuthenticator.php @@ -8,6 +8,7 @@ use Bolt\Entity\User; use Bolt\Entity\UserAuthToken; use Bolt\Repository\UserRepository; use Doctrine\Common\Persistence\ObjectManager; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\RouterInterface; @@ -39,13 +40,23 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator /** @var ObjectManager */ private $em; - public function __construct(UserRepository $userRepository, RouterInterface $router, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder, ObjectManager $em) - { + /** @var LoggerInterface */ + private $logger; + + public function __construct( + UserRepository $userRepository, + RouterInterface $router, + CsrfTokenManagerInterface $csrfTokenManager, + UserPasswordEncoderInterface $passwordEncoder, + ObjectManager $em, + LoggerInterface $dbLogger +) { $this->userRepository = $userRepository; $this->router = $router; $this->csrfTokenManager = $csrfTokenManager; $this->passwordEncoder = $passwordEncoder; $this->em = $em; + $this->logger = $dbLogger; } protected function getLoginUrl() @@ -114,6 +125,12 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator $this->em->persist($user); $this->em->flush(); + $userArr = [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + ]; + $this->logger->notice('User \'{username}\' logged in (manually)', $userArr); + return new RedirectResponse($request->getSession()->get( '_security.'.$providerKey.'.target_path', $this->router->generate('bolt_dashboard') diff --git a/src/Security/LogoutListener.php b/src/Security/LogoutListener.php index 5ce6a213..c2b46a64 100644 --- a/src/Security/LogoutListener.php +++ b/src/Security/LogoutListener.php @@ -6,6 +6,7 @@ namespace Bolt\Security; use Bolt\Entity\User; use Doctrine\Common\Persistence\ObjectManager; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -15,9 +16,13 @@ class LogoutListener implements LogoutHandlerInterface { private $em; - public function __construct(ObjectManager $em) + /** @var LoggerInterface */ + private $logger; + + public function __construct(ObjectManager $em, LoggerInterface $dbLogger) { $this->em = $em; + $this->logger = $dbLogger; } public function logout(Request $request, Response $response, TokenInterface $token): void @@ -26,6 +31,13 @@ class LogoutListener implements LogoutHandlerInterface if (! $user instanceof User) { return; } + + $userArr = [ + 'id' => $user->getId(), + 'username' => $user->getUsername(), + ]; + $this->logger->notice('User \'{username}\' logged out (manually)', $userArr); + $this->em->remove($user->getUserAuthToken()); $this->em->flush(); } diff --git a/templates/pages/logviewer.html.twig b/templates/pages/logviewer.html.twig index da068d6c..720376cc 100644 --- a/templates/pages/logviewer.html.twig +++ b/templates/pages/logviewer.html.twig @@ -2,7 +2,7 @@ {% import '@bolt/_macro/_macro.html.twig' as macro %} {% block title %} - {{ macro.icon('tachometer-alt') }} + {{ macro.icon('clipboard-list') }} {{ 'caption.logviewer'|trans }} {% endblock title %} @@ -15,9 +15,19 @@ {% block main %} + + + + + + + + + + {% for item in items %} - +
{{ __('label.id') }}{{ __('label.level') }}{{ __('label.message') }}{{ __('label.timestamp') }}
+ № {{ item.id }} @@ -25,8 +35,8 @@ - {{ item.message }} + {{ item.message|replace(item.context|default([])) }} + {% if item.context is iterable %} + + {% endif %} + {{ item.createdAt|date('Y-m-d H:i:s') }}
{% if item.user %} - {{ item.user.username }}(№ {{ item.user.id }}) + {{ __('label.user') }}: {{ item.user.username }}(№ {{ item.user.id }}) {% else %} - {% endif %} @@ -68,7 +94,4 @@ {{ pager(items, template = '@bolt/helpers/_pager_bootstrap.html.twig', class="justify-content-center") }} - {% endblock %} - - diff --git a/translations/messages.en.xlf b/translations/messages.en.xlf index c1436a42..f5f67d60 100644 --- a/translations/messages.en.xlf +++ b/translations/messages.en.xlf @@ -2103,5 +2103,59 @@ Download + + + caption.logviewer + Log Viewer + + + + + label.request + Request + + + + + label.trace + Trace + + + + + label.context + Context + + + + + label.id + ID + + + + + label.level + Level + + + + + label.message + Message + + + + + label.timestamp + Timestamp + + + + + label.user + User + + From a15c132c8eb4af8c921bad4d3f835cb002704328 Mon Sep 17 00:00:00 2001 From: Bob den Otter Date: Sat, 4 Jan 2020 14:42:23 +0100 Subject: [PATCH 6/7] Create monolog.yaml --- config/packages/test/monolog.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 config/packages/test/monolog.yaml diff --git a/config/packages/test/monolog.yaml b/config/packages/test/monolog.yaml new file mode 100644 index 00000000..e5ebc282 --- /dev/null +++ b/config/packages/test/monolog.yaml @@ -0,0 +1,7 @@ +monolog: + channels: ['db'] + handlers: + db: + channels: ['db'] + type: service + id: Bolt\Log\LogHandler \ No newline at end of file From d52b58b2fafced21f5b6f9d2b3fc628cf6586e83 Mon Sep 17 00:00:00 2001 From: Bob den Otter Date: Sat, 4 Jan 2020 14:54:16 +0100 Subject: [PATCH 7/7] Update LoginFormAuthenticatorTest.php --- tests/php/Security/LoginFormAuthenticatorTest.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/php/Security/LoginFormAuthenticatorTest.php b/tests/php/Security/LoginFormAuthenticatorTest.php index 70ea9d6d..b42b94f3 100644 --- a/tests/php/Security/LoginFormAuthenticatorTest.php +++ b/tests/php/Security/LoginFormAuthenticatorTest.php @@ -5,10 +5,12 @@ declare(strict_types=1); namespace Bolt\Tests\Security; use Bolt\Entity\User; +use Bolt\Log\LogHandler; use Bolt\Repository\UserRepository; use Bolt\Security\LoginFormAuthenticator; use Doctrine\Common\Persistence\ObjectManager; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; @@ -31,7 +33,7 @@ class LoginFormAuthenticatorTest extends TestCase ->with('bolt_login') ->willReturn('test_route'); - $res = $this->getTestObj(null, $router, null, null)->start($this->createMock(Request::class)); + $res = $this->getTestObj(null, $router, null, null, null)->start($this->createMock(Request::class)); $this->assertSame('test_route', $res->getTargetUrl()); } @@ -44,7 +46,7 @@ class LoginFormAuthenticatorTest extends TestCase 'isTokenValid' => true, ]); - $res = $this->getTestObj($userRepository, null, $csrfTokenManager, null)->getUser(self::TEST_TOKEN, $this->createMock(UserProviderInterface::class)); + $res = $this->getTestObj($userRepository, null, $csrfTokenManager, null, null)->getUser(self::TEST_TOKEN, $this->createMock(UserProviderInterface::class)); $this->assertInstanceOf(User::class, $res); } @@ -55,17 +57,18 @@ class LoginFormAuthenticatorTest extends TestCase ]); $this->expectException(InvalidCsrfTokenException::class); - $this->getTestObj(null, null, $csrfTokenManager, null)->getUser(self::TEST_TOKEN, $this->createMock(UserProviderInterface::class)); + $this->getTestObj(null, null, $csrfTokenManager, null, null)->getUser(self::TEST_TOKEN, $this->createMock(UserProviderInterface::class)); } - private function getTestObj(?UserRepository $userRepository, ?RouterInterface $router, ?CsrfTokenManagerInterface $csrfTokenManager, ?UserPasswordEncoderInterface $userPasswordEncoder): LoginFormAuthenticator + private function getTestObj(?UserRepository $userRepository, ?RouterInterface $router, ?CsrfTokenManagerInterface $csrfTokenManager, ?UserPasswordEncoderInterface $userPasswordEncoder, ?LoggerInterface $logger): LoginFormAuthenticator { return new LoginFormAuthenticator( $userRepository ?? $this->createMock(UserRepository::class), $router ?? $this->createMock(RouterInterface::class), $csrfTokenManager ?? $this->createMock(CsrfTokenManagerInterface::class), $userPasswordEncoder ?? $this->createMock(UserPasswordEncoderInterface::class), - $em ?? $this->createMock(ObjectManager::class) + $em ?? $this->createMock(ObjectManager::class), + $logger ?? $this->createMock(LoggerInterface::class) ); } }