Skip to content

Commit 06960f1

Browse files
authored
Enable error handlers back (#322)
* Reinforce E2E tests with log of sent events * Add failing test for notices * Fix wrong setup in test * Improve E2E tests; add case for fatals * Try to revert the integration disabling to have the full error reporting back * Fix test after last modification * Require --dev symfony/process for client isolation * Do not capture deprecations in E2E tests * Fix CS * Fix PHPStan * Fix last PHPStan error * Remove unneeded alias * Remove unused class * Try to avoid double reporting of fatal errors * Add changelog entry * Fix after-merge issues
1 parent 02e6902 commit 06960f1

12 files changed

+152
-65
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77
## Unreleased
88
- ...
99

10+
## 4.0.0 (TBA)
11+
- Enable back all error listeners from base SDK integration (#322)
12+
1013
## 3.5.1 (2020-05-07)
1114
- Capture events using the `Hub` in the `MessengerListener` to avoid loosing `Scope` data (#339, thanks to @sh41)
1215
- Capture multiple events if multiple exceptions are generated in a Messenger Worker context (#340, thanks to @emarref)

composer.json

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"require": {
2222
"php": "^7.1",
2323
"jean85/pretty-package-versions": "^1.0",
24+
"ocramius/package-versions": "^1.3.0",
2425
"sentry/sdk": "^2.1",
2526
"symfony/config": "^3.4||^4.0||^5.0",
2627
"symfony/console": "^3.4||^4.0||^5.0",
@@ -44,6 +45,7 @@
4445
"symfony/messenger": "^4.3||^5.0",
4546
"symfony/monolog-bundle": "^3.4",
4647
"symfony/phpunit-bridge": "^5.0",
48+
"symfony/process": "^3.4||^4.0||^5.0",
4749
"symfony/yaml": "^3.4||^4.0||^5.0"
4850
},
4951
"conflict": {

phpstan-baseline.neon

-5
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,6 @@ parameters:
235235
count: 1
236236
path: test/End2End/App/Kernel.php
237237

238-
-
239-
message: "#^Class PHPUnit_Framework_TestCase not found\\.$#"
240-
count: 1
241-
path: test/End2End/End2EndTest.php
242-
243238
-
244239
message: "#^Class Symfony\\\\Bundle\\\\FrameworkBundle\\\\Client not found\\.$#"
245240
count: 1

src/DependencyInjection/IntegrationFilterFactory.php

-35
This file was deleted.

src/DependencyInjection/SentryExtension.php

+8-10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\DependencyInjection\Exception\LogicException;
1818
use Symfony\Component\DependencyInjection\Loader;
1919
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\ErrorHandler\Error\FatalError;
2021
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
2122
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
2223
use Symfony\Component\HttpKernel\KernelEvents;
@@ -128,18 +129,15 @@ private function passConfigurationToOptions(ContainerBuilder $container, array $
128129
}
129130
}
130131

131-
if (\array_key_exists('excluded_exceptions', $processedOptions) && $processedOptions['excluded_exceptions']) {
132-
$ignoreOptions = [
133-
'ignore_exceptions' => $processedOptions['excluded_exceptions'],
134-
];
135-
136-
$integrations[] = new Definition(IgnoreErrorsIntegration::class, [$ignoreOptions]);
137-
}
132+
// we ignore fatal errors wrapped by Symfony because they produce double event reporting
133+
$processedOptions['excluded_exceptions'][] = FatalError::class;
134+
$ignoreOptions = [
135+
'ignore_exceptions' => $processedOptions['excluded_exceptions'],
136+
];
138137

139-
$integrationsCallable = new Definition('callable', [$integrations]);
140-
$integrationsCallable->setFactory([IntegrationFilterFactory::class, 'create']);
138+
$integrations[] = new Definition(IgnoreErrorsIntegration::class, [$ignoreOptions]);
141139

142-
$options->addMethodCall('setIntegrations', [$integrationsCallable]);
140+
$options->addMethodCall('setIntegrations', [$integrations]);
143141
}
144142

145143
/**

test/DependencyInjection/SentryExtensionTest.php

-10
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
use Sentry\Breadcrumb;
99
use Sentry\ClientInterface;
1010
use Sentry\Event;
11-
use Sentry\Integration\ErrorListenerIntegration;
12-
use Sentry\Integration\ExceptionListenerIntegration;
1311
use Sentry\Integration\IntegrationInterface;
1412
use Sentry\Monolog\Handler;
1513
use Sentry\Options;
@@ -339,14 +337,6 @@ public function testIntegrations(): void
339337

340338
$found = false;
341339
foreach ($integrations as $integration) {
342-
if ($integration instanceof ErrorListenerIntegration) {
343-
$this->fail('Should not have ErrorListenerIntegration registered');
344-
}
345-
346-
if ($integration instanceof ExceptionListenerIntegration) {
347-
$this->fail('Should not have ExceptionListenerIntegration registered');
348-
}
349-
350340
if ($integration instanceof IntegrationMock) {
351341
$found = true;
352342
}

test/End2End/App/Controller/MainController.php

+14
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,27 @@ public function exception(): Response
3131
throw new \RuntimeException('This is an intentional error');
3232
}
3333

34+
public function fatal(): Response
35+
{
36+
$foo = eval("return new class() implements \Serializable {};");
37+
38+
return new Response('This response should not happen: ' . json_encode($foo));
39+
}
40+
3441
public function index(): Response
3542
{
3643
$this->sentry->captureMessage('Hello there');
3744

3845
return new Response('Hello there');
3946
}
4047

48+
public function notice(): Response
49+
{
50+
@trigger_error('This is an intentional notice', E_USER_NOTICE);
51+
52+
return new Response('Hello there');
53+
}
54+
4155
public function subrequest(): Response
4256
{
4357
$request = $this->requestStack->getCurrentRequest();

test/End2End/App/config.yml

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
sentry:
2+
options:
3+
capture_silenced_errors: true
4+
error_types: E_ALL & ~E_USER_DEPRECATED
5+
16
framework:
27
router: { resource: "%routing_config_dir%/routing.yml" }
38
secret: secret
@@ -8,6 +13,17 @@ services:
813
alias: Sentry\State\HubInterface
914
public: true
1015

16+
Sentry\ClientBuilderInterface:
17+
class: Sentry\ClientBuilder
18+
arguments:
19+
- '@Sentry\Options'
20+
calls:
21+
- method: setTransportFactory
22+
arguments:
23+
- '@Sentry\SentryBundle\Test\End2End\StubTransportFactory'
24+
25+
Sentry\SentryBundle\Test\End2End\StubTransportFactory: ~
26+
1127
Sentry\SentryBundle\Test\End2End\App\Controller\MainController:
1228
autowire: true
1329
tags:

test/End2End/App/routing.yml

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ exception:
22
path: /exception
33
defaults: { _controller: 'Sentry\SentryBundle\Test\End2End\App\Controller\MainController::exception' }
44

5+
fatal:
6+
path: /fatal
7+
defaults: { _controller: 'Sentry\SentryBundle\Test\End2End\App\Controller\MainController::fatal' }
8+
59
200:
610
path: /200
711
defaults: { _controller: 'Sentry\SentryBundle\Test\End2End\App\Controller\MainController::index' }
@@ -13,3 +17,7 @@ secured200:
1317
subrequest:
1418
path: /subrequest
1519
defaults: { _controller: 'Sentry\SentryBundle\Test\End2End\App\Controller\MainController::subrequest' }
20+
21+
notice:
22+
path: /notice
23+
defaults: { _controller: 'Sentry\SentryBundle\Test\End2End\App\Controller\MainController::notice' }

test/End2End/End2EndTest.php

+59-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Sentry\SentryBundle\Test\End2End;
44

5-
use PHPUnit\Framework\TestCase;
65
use Sentry\SentryBundle\Test\End2End\App\Kernel;
76
use Sentry\State\HubInterface;
87
use Symfony\Bundle\FrameworkBundle\Client;
@@ -11,7 +10,6 @@
1110
use Symfony\Component\HttpFoundation\Response;
1211
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1312

14-
class_alias(TestCase::class, \PHPUnit_Framework_TestCase::class);
1513
if (! class_exists(KernelBrowser::class)) {
1614
class_alias(Client::class, KernelBrowser::class);
1715
}
@@ -21,11 +19,20 @@ class_alias(Client::class, KernelBrowser::class);
2119
*/
2220
class End2EndTest extends WebTestCase
2321
{
22+
public const SENT_EVENTS_LOG = '/tmp/sentry_e2e_test_sent_events.log';
23+
2424
protected static function getKernelClass(): string
2525
{
2626
return Kernel::class;
2727
}
2828

29+
protected function setUp(): void
30+
{
31+
parent::setUp();
32+
33+
file_put_contents(self::SENT_EVENTS_LOG, '');
34+
}
35+
2936
public function testGet200(): void
3037
{
3138
$client = static::createClient(['debug' => false]);
@@ -38,6 +45,7 @@ public function testGet200(): void
3845
$this->assertSame(200, $response->getStatusCode());
3946

4047
$this->assertLastEventIdIsNotNull($client);
48+
$this->assertEventCount(1);
4149
}
4250

4351
public function testGet200BehindFirewall(): void
@@ -52,6 +60,7 @@ public function testGet200BehindFirewall(): void
5260
$this->assertSame(200, $response->getStatusCode());
5361

5462
$this->assertLastEventIdIsNotNull($client);
63+
$this->assertEventCount(1);
5564
}
5665

5766
public function testGet200WithSubrequest(): void
@@ -66,6 +75,7 @@ public function testGet200WithSubrequest(): void
6675
$this->assertSame(200, $response->getStatusCode());
6776

6877
$this->assertLastEventIdIsNotNull($client);
78+
$this->assertEventCount(1);
6979
}
7080

7181
public function testGet404(): void
@@ -88,6 +98,7 @@ public function testGet404(): void
8898
}
8999

90100
$this->assertLastEventIdIsNotNull($client);
101+
$this->assertEventCount(1);
91102
}
92103

93104
public function testGet500(): void
@@ -111,6 +122,44 @@ public function testGet500(): void
111122
}
112123

113124
$this->assertLastEventIdIsNotNull($client);
125+
$this->assertEventCount(1);
126+
}
127+
128+
public function testGetFatal(): void
129+
{
130+
$client = static::createClient();
131+
132+
try {
133+
$client->insulate(true);
134+
$client->request('GET', '/fatal');
135+
136+
$response = $client->getResponse();
137+
138+
$this->assertInstanceOf(Response::class, $response);
139+
$this->assertSame(500, $response->getStatusCode());
140+
$this->assertStringNotContainsString('not happen', $response->getContent() ?: '');
141+
} catch (\RuntimeException $exception) {
142+
$this->assertStringContainsStringIgnoringCase('error', $exception->getMessage());
143+
$this->assertStringContainsStringIgnoringCase('contains 2 abstract methods', $exception->getMessage());
144+
$this->assertStringContainsStringIgnoringCase('MainController.php', $exception->getMessage());
145+
$this->assertStringContainsStringIgnoringCase('eval()\'d code on line', $exception->getMessage());
146+
}
147+
148+
$this->assertEventCount(1);
149+
}
150+
151+
public function testNotice(): void
152+
{
153+
$client = static::createClient();
154+
$client->request('GET', '/notice');
155+
156+
$response = $client->getResponse();
157+
158+
$this->assertInstanceOf(Response::class, $response);
159+
$this->assertSame(200, $response->getStatusCode());
160+
161+
$this->assertLastEventIdIsNotNull($client);
162+
$this->assertEventCount(1);
114163
}
115164

116165
private function assertLastEventIdIsNotNull(KernelBrowser $client): void
@@ -123,4 +172,12 @@ private function assertLastEventIdIsNotNull(KernelBrowser $client): void
123172

124173
$this->assertNotNull($hub->getLastEventId(), 'Last error not captured');
125174
}
175+
176+
private function assertEventCount(int $expectedCount): void
177+
{
178+
$events = file_get_contents(self::SENT_EVENTS_LOG);
179+
$this->assertNotFalse($events, 'Cannot read sent events log');
180+
$listOfEvents = array_filter(explode(StubTransportFactory::SEPARATOR, trim($events)));
181+
$this->assertCount($expectedCount, $listOfEvents, 'Wrong number of events sent: ' . PHP_EOL . $events);
182+
}
126183
}

test/End2End/StubTransportFactory.php

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Sentry\SentryBundle\Test\End2End;
4+
5+
use Sentry\Event;
6+
use Sentry\Options;
7+
use Sentry\Transport\TransportFactoryInterface;
8+
use Sentry\Transport\TransportInterface;
9+
10+
class StubTransportFactory implements TransportFactoryInterface
11+
{
12+
public const SEPARATOR = '###';
13+
14+
public function create(Options $options): TransportInterface
15+
{
16+
return new class() implements TransportInterface {
17+
public function send(Event $event): ?string
18+
{
19+
touch(End2EndTest::SENT_EVENTS_LOG);
20+
21+
if ($event->getMessage()) {
22+
$message = $event->getMessage();
23+
} elseif ($event->getExceptions()) {
24+
$message = $event->getExceptions()[0]['value'];
25+
} else {
26+
$message = 'NO MESSAGE NOR EXCEPTIONS';
27+
}
28+
29+
file_put_contents(
30+
End2EndTest::SENT_EVENTS_LOG,
31+
$event->getId() . ': ' . $message . PHP_EOL . StubTransportFactory::SEPARATOR . PHP_EOL,
32+
FILE_APPEND
33+
);
34+
35+
return $event->getId();
36+
}
37+
};
38+
}
39+
}

test/SentryBundleTest.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -127,16 +127,16 @@ public function testContainerHasTestCommandRegisteredCorrectly(): void
127127
$this->assertArrayHasKey('console.command', $consoleListener->getTags());
128128
}
129129

130-
public function testIntegrationsListenersAreDisabledByDefault(): void
130+
public function testIntegrationsListenersAreEnabled(): void
131131
{
132132
$container = $this->getContainer();
133133

134134
$hub = $container->get(HubInterface::class);
135135

136136
$this->assertInstanceOf(HubInterface::class, $hub);
137137
$this->assertInstanceOf(IntegrationInterface::class, $hub->getIntegration(RequestIntegration::class));
138-
$this->assertNull($hub->getIntegration(ErrorListenerIntegration::class));
139-
$this->assertNull($hub->getIntegration(ExceptionListenerIntegration::class));
138+
$this->assertNotNull($hub->getIntegration(ErrorListenerIntegration::class));
139+
$this->assertNotNull($hub->getIntegration(ExceptionListenerIntegration::class));
140140
}
141141

142142
private function getContainer(): ContainerBuilder

0 commit comments

Comments
 (0)