Skip to content

Commit 962a8cf

Browse files
authored
Add handled/unhandled exception support (#674)
1 parent 95c2a54 commit 962a8cf

File tree

8 files changed

+124
-17
lines changed

8 files changed

+124
-17
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Report exceptions to Sentry as unhandled by default (#670)
6+
- Exceptions from messages which will be retried are sent to Sentry as handled (#670)
7+
58
## 4.5.0 (2022-11-28)
69

710
- Symfony version 3.4 is no longer supported

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"jean85/pretty-package-versions": "^1.5 || ^2.0",
2828
"php-http/discovery": "^1.11",
2929
"sentry/sdk": "^3.3",
30+
"sentry/sentry": "^3.12",
3031
"symfony/cache-contracts": "^1.1||^2.4||^3.0",
3132
"symfony/config": "^4.4.20||^5.0.11||^6.0",
3233
"symfony/console": "^4.4.20||^5.0.11||^6.0",

src/EventListener/ConsoleListener.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Sentry\SentryBundle\EventListener;
66

7+
use Sentry\Event;
8+
use Sentry\EventHint;
9+
use Sentry\ExceptionMechanism;
710
use Sentry\State\HubInterface;
811
use Sentry\State\Scope;
912
use Symfony\Component\Console\Event\ConsoleCommandEvent;
@@ -82,7 +85,12 @@ public function handleConsoleErrorEvent(ConsoleErrorEvent $event): void
8285
$scope->setTag('console.command.exit_code', (string) $event->getExitCode());
8386

8487
if ($this->captureErrors) {
85-
$this->hub->captureException($event->getError());
88+
$hint = EventHint::fromArray([
89+
'exception' => $event->getError(),
90+
'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
91+
]);
92+
93+
$this->hub->captureEvent(Event::createEvent(), $hint);
8694
}
8795
});
8896
}

src/EventListener/ErrorListener.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Sentry\SentryBundle\EventListener;
66

7+
use Sentry\Event;
8+
use Sentry\EventHint;
9+
use Sentry\ExceptionMechanism;
710
use Sentry\State\HubInterface;
811
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
912

@@ -34,6 +37,11 @@ public function __construct(HubInterface $hub)
3437
*/
3538
public function handleExceptionEvent(ExceptionEvent $event): void
3639
{
37-
$this->hub->captureException($event->getThrowable());
40+
$hint = EventHint::fromArray([
41+
'exception' => $event->getThrowable(),
42+
'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
43+
]);
44+
45+
$this->hub->captureEvent(Event::createEvent(), $hint);
3846
}
3947
}

src/EventListener/MessengerListener.php

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
namespace Sentry\SentryBundle\EventListener;
66

77
use Sentry\Event;
8+
use Sentry\EventHint;
9+
use Sentry\ExceptionMechanism;
810
use Sentry\State\HubInterface;
911
use Sentry\State\Scope;
1012
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
@@ -62,13 +64,7 @@ public function handleWorkerMessageFailedEvent(WorkerMessageFailedEvent $event):
6264
$scope->setTag('messenger.message_bus', $messageBusStamp->getBusName());
6365
}
6466

65-
if ($exception instanceof HandlerFailedException) {
66-
foreach ($exception->getNestedExceptions() as $nestedException) {
67-
$this->hub->captureException($nestedException);
68-
}
69-
} else {
70-
$this->hub->captureException($exception);
71-
}
67+
$this->captureException($exception, $event->willRetry());
7268
});
7369

7470
$this->flushClient();
@@ -86,6 +82,33 @@ public function handleWorkerMessageHandledEvent(WorkerMessageHandledEvent $event
8682
$this->flushClient();
8783
}
8884

85+
/**
86+
* Creates Sentry events from the given exception.
87+
*
88+
* Unpacks multiple exceptions wrapped in a HandlerFailedException and notifies
89+
* Sentry of each individual exception.
90+
*
91+
* If the message will be retried the exceptions will be marked as handled
92+
* in Sentry.
93+
*/
94+
private function captureException(\Throwable $exception, bool $willRetry): void
95+
{
96+
if ($exception instanceof HandlerFailedException) {
97+
foreach ($exception->getNestedExceptions() as $nestedException) {
98+
$this->captureException($nestedException, $willRetry);
99+
}
100+
101+
return;
102+
}
103+
104+
$hint = EventHint::fromArray([
105+
'exception' => $exception,
106+
'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, $willRetry),
107+
]);
108+
109+
$this->hub->captureEvent(Event::createEvent(), $hint);
110+
}
111+
89112
private function flushClient(): void
90113
{
91114
$client = $this->hub->getClient();

tests/EventListener/AbstractConsoleListenerTest.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use PHPUnit\Framework\MockObject\MockObject;
88
use PHPUnit\Framework\TestCase;
99
use Sentry\Event;
10+
use Sentry\EventHint;
1011
use Sentry\SentryBundle\EventListener\ConsoleListener;
1112
use Sentry\State\HubInterface;
1213
use Sentry\State\Scope;
@@ -113,8 +114,20 @@ public function testHandleConsoleErrorEvent(bool $captureErrors): void
113114
});
114115

115116
$this->hub->expects($captureErrors ? $this->once() : $this->never())
116-
->method('captureException')
117-
->with($consoleEvent->getError());
117+
->method('captureEvent')
118+
->with(
119+
$this->anything(),
120+
$this->logicalAnd(
121+
$this->isInstanceOf(EventHint::class),
122+
$this->callback(function (EventHint $subject) use ($consoleEvent) {
123+
self::assertSame($consoleEvent->getError(), $subject->exception);
124+
self::assertNotNull($subject->mechanism);
125+
self::assertFalse($subject->mechanism->isHandled());
126+
127+
return true;
128+
})
129+
)
130+
);
118131

119132
$listener->handleConsoleErrorEvent($consoleEvent);
120133

tests/EventListener/ErrorListenerTest.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use PHPUnit\Framework\MockObject\MockObject;
88
use PHPUnit\Framework\TestCase;
9+
use Sentry\EventHint;
910
use Sentry\SentryBundle\EventListener\ErrorListener;
1011
use Sentry\State\HubInterface;
1112
use Symfony\Component\HttpFoundation\Request;
@@ -35,9 +36,23 @@ protected function setUp(): void
3536
*/
3637
public function testHandleExceptionEvent(ExceptionEvent $event): void
3738
{
39+
$expectedException = $event->getThrowable();
40+
3841
$this->hub->expects($this->once())
39-
->method('captureException')
40-
->with($event->getThrowable());
42+
->method('captureEvent')
43+
->with(
44+
$this->anything(),
45+
$this->logicalAnd(
46+
$this->isInstanceOf(EventHint::class),
47+
$this->callback(function (EventHint $subject) use ($expectedException) {
48+
self::assertSame($expectedException, $subject->exception);
49+
self::assertNotNull($subject->mechanism);
50+
self::assertFalse($subject->mechanism->isHandled());
51+
52+
return true;
53+
})
54+
)
55+
);
4156

4257
$this->listener->handleExceptionEvent($event);
4358
}

tests/EventListener/MessengerListenerTest.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPUnit\Framework\TestCase;
99
use Sentry\ClientInterface;
1010
use Sentry\Event;
11+
use Sentry\EventHint;
1112
use Sentry\SentryBundle\EventListener\MessengerListener;
1213
use Sentry\State\HubInterface;
1314
use Sentry\State\Scope;
@@ -42,7 +43,7 @@ protected function setUp(): void
4243
* @param \Throwable[] $exceptions
4344
* @param array<string, string> $expectedTags
4445
*/
45-
public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMessageFailedEvent $event, array $expectedTags): void
46+
public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMessageFailedEvent $event, array $expectedTags, bool $expectedIsHandled): void
4647
{
4748
if (!$this->supportsMessenger()) {
4849
$this->markTestSkipped('Messenger not supported in this environment.');
@@ -57,9 +58,21 @@ public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMess
5758
});
5859

5960
$this->hub->expects($this->exactly(\count($exceptions)))
60-
->method('captureException')
61-
->withConsecutive(...array_map(static function (\Throwable $exception): array {
62-
return [$exception];
61+
->method('captureEvent')
62+
->withConsecutive(...array_map(function (\Throwable $expectedException) use ($expectedIsHandled): array {
63+
return [
64+
$this->anything(),
65+
$this->logicalAnd(
66+
$this->isInstanceOf(EventHint::class),
67+
$this->callback(function (EventHint $subject) use ($expectedException, $expectedIsHandled) {
68+
self::assertSame($expectedException, $subject->exception);
69+
self::assertNotNull($subject->mechanism);
70+
self::assertSame($expectedIsHandled, $subject->mechanism->isHandled());
71+
72+
return true;
73+
})
74+
),
75+
];
6376
}, $exceptions));
6477

6578
$this->hub->expects($this->once())
@@ -97,6 +110,17 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
97110
'messenger.receiver_name' => 'receiver',
98111
'messenger.message_class' => \get_class($envelope->getMessage()),
99112
],
113+
false,
114+
];
115+
116+
yield 'envelope.throwable INSTANCEOF HandlerFailedException - RETRYING' => [
117+
$exceptions,
118+
$this->getMessageFailedEvent($envelope, 'receiver', new HandlerFailedException($envelope, $exceptions), true),
119+
[
120+
'messenger.receiver_name' => 'receiver',
121+
'messenger.message_class' => \get_class($envelope->getMessage()),
122+
],
123+
true,
100124
];
101125

102126
yield 'envelope.throwable INSTANCEOF Exception' => [
@@ -106,6 +130,17 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
106130
'messenger.receiver_name' => 'receiver',
107131
'messenger.message_class' => \get_class($envelope->getMessage()),
108132
],
133+
false,
134+
];
135+
136+
yield 'envelope.throwable INSTANCEOF Exception - RETRYING' => [
137+
[$exceptions[0]],
138+
$this->getMessageFailedEvent($envelope, 'receiver', $exceptions[0], true),
139+
[
140+
'messenger.receiver_name' => 'receiver',
141+
'messenger.message_class' => \get_class($envelope->getMessage()),
142+
],
143+
true,
109144
];
110145

111146
$envelope = new Envelope((object) [], [new BusNameStamp('bus.foo')]);
@@ -118,6 +153,7 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
118153
'messenger.message_class' => \get_class($envelope->getMessage()),
119154
'messenger.message_bus' => 'bus.foo',
120155
],
156+
false,
121157
];
122158
}
123159

0 commit comments

Comments
 (0)