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: diff --git a/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php b/src/Symfony/Bundle/WebProfilerBundle/EventListener/ProfilerLinkLogListener.php new file mode 100644 index 0000000000000..b18a61c781a3d --- /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 + */ +final class ProfilerLinkLogListener implements EventSubscriberInterface +{ + public function __construct( + private ?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 (!$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..e8342b7109b54 --- /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 + */ +final 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); + } +}