diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 9de88acd..2e6b8d49 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -136,7 +136,9 @@ public function getConfigTreeBuilder() ->children() ->scalarNode('request')->defaultValue(0)->end() ->scalarNode('kernel_exception')->defaultValue(0)->end() + ->scalarNode('finish_request')->defaultValue(0)->end() ->scalarNode('console_exception')->defaultValue(0)->end() + ->scalarNode('console_terminate')->defaultValue(0)->end() ->end() ->end() ->end() diff --git a/src/EventListener/ExceptionListener.php b/src/EventListener/ExceptionListener.php index f8f1fd33..857f664b 100644 --- a/src/EventListener/ExceptionListener.php +++ b/src/EventListener/ExceptionListener.php @@ -8,9 +8,11 @@ use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\HttpKernelInterface; @@ -82,6 +84,10 @@ public function setClient(\Raven_Client $client) */ public function onKernelRequest(GetResponseEvent $event): void { + if (($route = $event->getRequest()->attributes->get('_route'))) { + $this->client->transaction->push($route); + } + if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { return; } @@ -115,6 +121,13 @@ public function onKernelException(GetResponseForExceptionEvent $event): void $this->client->captureException($exception); } + public function onFinishRequest(FinishRequestEvent $event): void + { + if (($route = $event->getRequest()->attributes->get('_route'))) { + $this->client->transaction->pop($route); + } + } + /** * This method only ensures that the client and error handlers are registered at the start of the command * execution cycle, and not only on exceptions @@ -125,7 +138,9 @@ public function onKernelException(GetResponseForExceptionEvent $event): void */ public function onConsoleCommand(ConsoleCommandEvent $event): void { - // only triggers loading of client, does not need to do anything. + if (($command = $event->getCommand())) { + $this->client->transaction->push($command->getName()); + } } public function onConsoleError(ConsoleErrorEvent $event): void @@ -138,6 +153,13 @@ public function onConsoleException(ConsoleExceptionEvent $event): void $this->handleConsoleError($event); } + public function onFinishCommand(ConsoleTerminateEvent $event): void + { + if (($command = $event->getCommand())) { + $this->client->transaction->pop($command->getName()); + } + } + /** * @param ConsoleExceptionEvent|ConsoleErrorEvent $event */ diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index c322f116..7269182f 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -23,8 +23,10 @@ + + diff --git a/test/DependencyInjection/SentryExtensionTest.php b/test/DependencyInjection/SentryExtensionTest.php index fccd1495..d7216758 100644 --- a/test/DependencyInjection/SentryExtensionTest.php +++ b/test/DependencyInjection/SentryExtensionTest.php @@ -287,12 +287,22 @@ public function test_that_it_has_proper_event_listener_tags_for_exception_listen 'method' => 'onKernelException', 'priority' => '%sentry.listener_priorities.kernel_exception%', ], + [ + 'event' => 'kernel.finish_request', + 'method' => 'onFinishRequest', + 'priority' => '%sentry.listener_priorities.finish_request%', + ], ['event' => 'console.command', 'method' => 'onConsoleCommand'], [ 'event' => 'console.error', 'method' => 'onConsoleError', 'priority' => '%sentry.listener_priorities.console_exception%', ], + [ + 'event' => 'console.terminate', + 'method' => 'onFinishCommand', + 'priority' => '%sentry.listener_priorities.console_terminate%', + ], ], $tags ); diff --git a/test/EventListener/ExceptionListenerTest.php b/test/EventListener/ExceptionListenerTest.php index 8aa3423d..ecccd6be 100644 --- a/test/EventListener/ExceptionListenerTest.php +++ b/test/EventListener/ExceptionListenerTest.php @@ -9,8 +9,10 @@ use Sentry\SentryBundle\EventListener\SentryExceptionListenerInterface; use Sentry\SentryBundle\SentrySymfonyEvents; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleErrorEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\DependencyInjection\Alias; @@ -18,6 +20,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; use Symfony\Component\HttpKernel\Event\GetResponseEvent; use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; use Symfony\Component\HttpKernel\Exception\HttpException; @@ -81,6 +84,10 @@ public function test_that_user_data_is_not_set_on_subrequest() { $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -107,6 +114,10 @@ public function test_that_user_data_is_not_set_if_token_storage_not_present() $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -136,6 +147,10 @@ public function test_that_user_data_is_not_set_if_authorization_checker_not_pres $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -167,6 +182,10 @@ public function test_that_user_data_is_not_set_if_token_not_present() $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -209,6 +228,10 @@ public function test_that_user_data_is_not_set_if_not_authorized() $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent->expects($this->once()) ->method('getRequestType') ->willReturn(HttpKernelInterface::MASTER_REQUEST); @@ -254,6 +277,10 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -303,6 +330,10 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -358,6 +389,10 @@ public function test_that_username_is_set_from_user_interface_if_token_present_a $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -404,6 +439,10 @@ public function test_that_ip_is_set_from_request_stack() $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockRequest = $this->createMock(Request::class); $mockRequest @@ -438,6 +477,59 @@ public function test_that_ip_is_set_from_request_stack() $listener->onKernelRequest($mockEvent); } + public function test_that_it_sets_transaction_from_route() + { + $mockEvent = $this->createMock(GetResponseEvent::class); + + $request = new Request(); + $request->attributes->set('_route', 'my_route'); + $mockEvent + ->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + $mockEvent + ->method('getRequestType') + ->willReturn(HttpKernelInterface::SUB_REQUEST); + + $mockTransactionStack = $this->createMock(\Raven_TransactionStack::class); + $this->mockSentryClient->transaction = $mockTransactionStack; + + $mockTransactionStack + ->expects($this->once()) + ->method('push') + ->with('my_route'); + + $this->containerBuilder->compile(); + $listener = $this->getListener(); + $listener->onKernelRequest($mockEvent); + } + + public function test_that_it_pops_transaction_from_route() + { + $mockEvent = $this->createMock(FinishRequestEvent::class); + + $request = new Request(); + $request->attributes->set('_route', 'my_route'); + $mockEvent + ->expects($this->once()) + ->method('getRequest') + ->willReturn($request); + + $mockTransactionStack = $this->createMock(\Raven_TransactionStack::class); + $this->mockSentryClient->transaction = $mockTransactionStack; + + $mockTransactionStack + ->expects($this->once()) + ->method('pop') + ->with('my_route'); + + $this->containerBuilder->compile(); + /** @var ExceptionListener $listener */ + $listener = $this->getListener(); + $listener->onFinishRequest($mockEvent); + } + public function test_regression_with_unauthenticated_user_token_PR_78() { $mockToken = $this->createMock(TokenInterface::class); @@ -447,6 +539,10 @@ public function test_regression_with_unauthenticated_user_token_PR_78() $mockEvent = $this->createMock(GetResponseEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getRequestType') @@ -465,6 +561,10 @@ public function test_that_it_does_not_report_http_exception_if_included_in_captu { $mockEvent = $this->createMock(GetResponseForExceptionEvent::class); + $mockEvent + ->method('getRequest') + ->willReturn(new Request()); + $mockEvent ->expects($this->once()) ->method('getException') @@ -510,6 +610,30 @@ public function test_that_it_captures_exception() $listener->onKernelException($mockEvent); } + public function test_that_it_sets_transaction_from_command() + { + $mockEvent = $this->createMock(ConsoleCommandEvent::class); + + $command = new Command('my_command'); + $mockEvent + ->expects($this->once()) + ->method('getCommand') + ->willReturn($command); + + $mockTransactionStack = $this->createMock(\Raven_TransactionStack::class); + $this->mockSentryClient->transaction = $mockTransactionStack; + + $mockTransactionStack + ->expects($this->once()) + ->method('push') + ->with('my_command'); + + $this->containerBuilder->compile(); + /** @var ExceptionListener $listener */ + $listener = $this->getListener(); + $listener->onConsoleCommand($mockEvent); + } + /** * @dataProvider mockCommandProvider */ @@ -593,6 +717,30 @@ public function test_that_it_captures_console_error(?Command $mockCommand, strin $listener->onConsoleError($event); } + public function test_that_it_pops_transaction_from_command() + { + $mockEvent = $this->createMock(ConsoleTerminateEvent::class); + + $command = new Command('my_command'); + $mockEvent + ->expects($this->once()) + ->method('getCommand') + ->willReturn($command); + + $mockTransactionStack = $this->createMock(\Raven_TransactionStack::class); + $this->mockSentryClient->transaction = $mockTransactionStack; + + $mockTransactionStack + ->expects($this->once()) + ->method('pop') + ->with('my_command'); + + $this->containerBuilder->compile(); + /** @var ExceptionListener $listener */ + $listener = $this->getListener(); + $listener->onFinishCommand($mockEvent); + } + public function mockCommandProvider() { $mockCommand = $this->createMock(Command::class);