From 9e0b5ef728dd4df8004f83c3be37e25db4dfb7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Romey?= Date: Wed, 21 May 2025 19:04:19 +0200 Subject: [PATCH 1/4] [WebProfilerBundle] display profiler url in logs --- .../EventListener/ProfilerLinkLogListener.php | 60 ++++++++ .../Resources/config/profiler.php | 10 ++ .../ProfilerLinkLogListenerTest.php | 142 ++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php create mode 100644 src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php new file mode 100644 index 0000000000000..2d5a6066ef187 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Psr\Log\LoggerInterface; + +/** + * @author Jérémy Romey jeremyFreeAgent + */ +class ProfilerLinkLogListener implements EventSubscriberInterface +{ + public function __construct( + protected ?LoggerInterface $logger = null, + private ?UrlGeneratorInterface $urlGenerator = null, + ) { + } + + public function onKernelResponse(ResponseEvent $event): void + { + if (null === $this->logger) { + return; + } + if (null === $this->urlGenerator) { + return; + } + + $response = $event->getResponse(); + $request = $event->getRequest(); + + if (!$event->isMainRequest()) { + return; + } + + if (false === $response->headers->has('X-Debug-Token')) { + return; + } + + $this->logger->debug(\sprintf('See profiler at %s', $this->urlGenerator->generate('_profiler', ['token' => $response->headers->get('X-Debug-Token')], UrlGeneratorInterface::ABSOLUTE_URL))); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::RESPONSE => ['onKernelResponse', -2048], + ]; + } +} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.php b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.php index edb464158045f..6079803e4232e 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/config/profiler.php @@ -16,6 +16,7 @@ use Symfony\Bundle\WebProfilerBundle\Controller\RouterController; use Symfony\Bundle\WebProfilerBundle\Csp\ContentSecurityPolicyHandler; use Symfony\Bundle\WebProfilerBundle\Csp\NonceGenerator; +use Symfony\Bundle\WebProfilerBundle\EventListener\ProfilerLinkLogListener; use Symfony\Bundle\WebProfilerBundle\Profiler\CodeExtension; use Symfony\Bundle\WebProfilerBundle\Twig\WebProfilerExtension; use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; @@ -84,5 +85,14 @@ ->set('twig.extension.code', CodeExtension::class) ->args([service('debug.file_link_formatter'), param('kernel.project_dir'), param('kernel.charset')]) ->tag('twig.extension') + + ->set('web_profiler.profiler_link_log_listener', ProfilerLinkLogListener::class) + ->args([ + service('logger')->nullOnInvalid(), + service('router')->ignoreOnInvalid(), + ]) + + ->tag('monolog.logger', ['channel' => 'profiler']) + ->tag('kernel.event_subscriber') ; }; diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php new file mode 100644 index 0000000000000..3cb47fce5c1f6 --- /dev/null +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\WebProfilerBundle\Tests\EventListener; + +use PHPUnit\Framework\TestCase; +use Symfony\Bundle\WebProfilerBundle\EventListener\ProfilerLinkLogListener; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Psr\Log\LoggerInterface; + +/** + * @author Jérémy Romey jeremyFreeAgent + */ +class ProfilerLinkLogListenerTest extends TestCase +{ + public function testProfilerLinkLog() + { + $response = new Response('I love Symfony', 200); + $response->headers->set('Location', 'https://example.com/'); + $response->headers->set('X-Debug-Token', '04bb3f'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $logger = $this->createMock(LoggerInterface::class); + $logger + ->expects($this->once()) + ->method('debug') + ->with('See profiler at http://mydomain.com/_profiler/04bb3f') + ; + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->once()) + ->method('generate') + ->with('_profiler', ['token' => '04bb3f'], UrlGeneratorInterface::ABSOLUTE_URL) + ->willReturn('http://mydomain.com/_profiler/04bb3f') + ; + + $listener = new ProfilerLinkLogListener($logger, $urlGenerator); + $listener->onKernelResponse($event); + } + + public function testProfilerLinkLogShouldNotLogWhenNoLogger() + { + $response = new Response('I love Symfony', 200); + $response->headers->set('Location', 'https://example.com/'); + $response->headers->set('X-Debug-Token', '04bb3f'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $logger = null; + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->never()) + ->method('generate') + ; + + $listener = new ProfilerLinkLogListener($logger, $urlGenerator); + $listener->onKernelResponse($event); + } + + public function testProfilerLinkLogShouldNotLogWhenNoUrlGenerator() + { + $response = new Response('I love Symfony', 200); + $response->headers->set('Location', 'https://example.com/'); + $response->headers->set('X-Debug-Token', '04bb3f'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $logger = $this->createMock(LoggerInterface::class); + $logger + ->expects($this->never()) + ->method('debug') + ; + + $urlGenerator = null; + + $listener = new ProfilerLinkLogListener($logger, $urlGenerator); + $listener->onKernelResponse($event); + } + + public function testProfilerLinkLogShouldNotLogWhenNotMainRequest() + { + $response = new Response('I love Symfony', 200); + $response->headers->set('Location', 'https://example.com/'); + $response->headers->set('X-Debug-Token', '04bb3f'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::SUB_REQUEST, $response); + + $logger = $this->createMock(LoggerInterface::class); + $logger + ->expects($this->never()) + ->method('debug') + ; + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->never()) + ->method('generate') + ; + + $listener = new ProfilerLinkLogListener($logger, $urlGenerator); + $listener->onKernelResponse($event); + } + + public function testProfilerLinkLogShouldNotLogWhenNoToken() + { + $response = new Response('I love Symfony', 200); + $response->headers->set('Location', 'https://example.com/'); + + $event = new ResponseEvent($this->createMock(Kernel::class), new Request(), HttpKernelInterface::MAIN_REQUEST, $response); + + $logger = $this->createMock(LoggerInterface::class); + $logger + ->expects($this->never()) + ->method('debug') + ; + + $urlGenerator = $this->createMock(UrlGeneratorInterface::class); + $urlGenerator + ->expects($this->never()) + ->method('generate') + ; + + $listener = new ProfilerLinkLogListener($logger, $urlGenerator); + $listener->onKernelResponse($event); + } +} From 22559d0e1371cb7288bc64c605a84034b3e30c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Romey?= Date: Wed, 21 May 2025 19:11:12 +0200 Subject: [PATCH 2/4] add changelog entry --- src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md index 5e5e8db36e233..adfe0a440737f 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/WebProfilerBundle/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 7.3 --- + * Display profiler URL in logs + * Add `profiler.php` and `wdt.php` routing configuration files (use them instead of their XML equivalent) Before: From bd46580aed2b2e55d5f6eedeb4d7a1598ca44ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Romey?= Date: Wed, 21 May 2025 20:05:03 +0200 Subject: [PATCH 3/4] use final and private attributes --- .../EventListener/ProfilerLinkLogListener.php | 4 ++-- .../Tests/EventListener/ProfilerLinkLogListenerTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php index 2d5a6066ef187..05007fe11f927 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php @@ -20,10 +20,10 @@ /** * @author Jérémy Romey jeremyFreeAgent */ -class ProfilerLinkLogListener implements EventSubscriberInterface +final class ProfilerLinkLogListener implements EventSubscriberInterface { public function __construct( - protected ?LoggerInterface $logger = null, + private ?LoggerInterface $logger = null, private ?UrlGeneratorInterface $urlGenerator = null, ) { } diff --git a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php index 3cb47fce5c1f6..e8342b7109b54 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Tests/EventListener/ProfilerLinkLogListenerTest.php @@ -24,7 +24,7 @@ /** * @author Jérémy Romey jeremyFreeAgent */ -class ProfilerLinkLogListenerTest extends TestCase +final class ProfilerLinkLogListenerTest extends TestCase { public function testProfilerLinkLog() { From 6143514547fa19c9dab99f57fd2c31b53b8719f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Romey?= Date: Wed, 21 May 2025 20:05:33 +0200 Subject: [PATCH 4/4] Update src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php Co-authored-by: Christophe Coevoet --- .../WebProfilerBundle/EventListener/ProfilerLinkLogListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php index 05007fe11f927..b18a61c781a3d 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php +++ b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php @@ -44,7 +44,7 @@ public function onKernelResponse(ResponseEvent $event): void return; } - if (false === $response->headers->has('X-Debug-Token')) { + if (!$response->headers->has('X-Debug-Token')) { return; }