Skip to content

Commit 45a7c8c

Browse files
committed
feat: Report to Sentry if exceptions have been handled or not
1 parent d45365a commit 45a7c8c

File tree

7 files changed

+126
-18
lines changed

7 files changed

+126
-18
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.
6+
- Exceptions from messages which will be retried are sent to Sentry as handled.
7+
58
## 4.4.0 (2022-10-20)
69

710
- feat: Add support for Dynamic Sampling (#665)

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: 12 additions & 2 deletions
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

@@ -35,9 +38,16 @@ public function __construct(HubInterface $hub)
3538
public function handleExceptionEvent(ErrorListenerExceptionEvent $event): void
3639
{
3740
if ($event instanceof ExceptionEvent) {
38-
$this->hub->captureException($event->getThrowable());
41+
$exception = $event->getThrowable();
3942
} else {
40-
$this->hub->captureException($event->getException());
43+
$exception = $event->getException();
4144
}
45+
46+
$hint = EventHint::fromArray([
47+
'exception' => $exception,
48+
'mechanism' => new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, false),
49+
]);
50+
51+
$this->hub->captureEvent(Event::createEvent(), $hint);
4252
}
4353
}

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;
@@ -40,9 +41,23 @@ protected function setUp(): void
4041
*/
4142
public function testHandleExceptionEvent($event): void
4243
{
44+
$expectedException = $event instanceof ExceptionEvent ? $event->getThrowable() : $event->getException();
45+
4346
$this->hub->expects($this->once())
44-
->method('captureException')
45-
->with($event instanceof ExceptionEvent ? $event->getThrowable() : $event->getException());
47+
->method('captureEvent')
48+
->with(
49+
$this->anything(),
50+
$this->logicalAnd(
51+
$this->isInstanceOf(EventHint::class),
52+
$this->callback(function (EventHint $subject) use ($expectedException) {
53+
self::assertSame($expectedException, $subject->exception);
54+
self::assertNotNull($subject->mechanism);
55+
self::assertFalse($subject->mechanism->isHandled());
56+
57+
return true;
58+
})
59+
)
60+
);
4661

4762
$this->listener->handleExceptionEvent($event);
4863
}

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\SentryBundle\Tests\End2End\App\Kernel;
1314
use Sentry\State\HubInterface;
@@ -43,7 +44,7 @@ protected function setUp(): void
4344
* @param \Throwable[] $exceptions
4445
* @param array<string, string> $expectedTags
4546
*/
46-
public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMessageFailedEvent $event, array $expectedTags): void
47+
public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMessageFailedEvent $event, array $expectedTags, bool $expectedIsHandled): void
4748
{
4849
if (!$this->supportsMessenger()) {
4950
$this->markTestSkipped('Messenger not supported in this environment.');
@@ -58,9 +59,21 @@ public function testHandleWorkerMessageFailedEvent(array $exceptions, WorkerMess
5859
});
5960

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

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

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

112147
$envelope = new Envelope((object) [], [new BusNameStamp('bus.foo')]);
@@ -119,6 +154,7 @@ public function handleWorkerMessageFailedEventDataProvider(): \Generator
119154
'messenger.message_class' => \get_class($envelope->getMessage()),
120155
'messenger.message_bus' => 'bus.foo',
121156
],
157+
false,
122158
];
123159
}
124160

0 commit comments

Comments
 (0)