Skip to content

Commit d5cb8b2

Browse files
authored
Merge pull request #35 from flownative/task/early-logging
Early logging to Sentry
2 parents 32ff214 + f07e071 commit d5cb8b2

File tree

6 files changed

+120
-67
lines changed

6 files changed

+120
-67
lines changed

Classes/Command/SentryCommandController.php

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323

2424
final class SentryCommandController extends CommandController
2525
{
26+
const TEST_MODE_MESSAGE = 'message';
27+
const TEST_MODE_THROW = 'throw';
28+
const TEST_MODE_ERROR = 'error';
29+
2630
/**
2731
* @Flow\Inject
2832
* @var SentryClient
@@ -39,13 +43,12 @@ final class SentryCommandController extends CommandController
3943
*
4044
* @throws SentryClientTestException
4145
*/
42-
public function testCommand(): void
46+
public function testCommand(string $mode = self::TEST_MODE_THROW): void
4347
{
4448
$this->output->outputLine('<b>Testing Sentry setup …</b>');
4549
$this->output->outputLine('Using the following configuration:');
4650

4751
$options = $this->sentryClient->getOptions();
48-
4952
$this->output->outputTable([
5053
['DSN', $options->getDsn()],
5154
['Environment', $options->getEnvironment()],
@@ -57,6 +60,23 @@ public function testCommand(): void
5760
'Value'
5861
]);
5962

63+
switch ($mode) {
64+
case self::TEST_MODE_MESSAGE:
65+
$this->captureMessage();
66+
break;
67+
case self::TEST_MODE_THROW:
68+
$this->throwException();
69+
break;
70+
case self::TEST_MODE_ERROR:
71+
$this->triggerError();
72+
break;
73+
default:
74+
$this->output->outputLine('<error>Unknown mode given</error>');
75+
}
76+
}
77+
78+
private function captureMessage(): void
79+
{
6080
$eventId = $this->sentryClient->captureMessage(
6181
'Flownative Sentry Plugin Test',
6282
Severity::debug(),
@@ -65,11 +85,30 @@ public function testCommand(): void
6585
]
6686
);
6787

88+
$this->outputLine();
6889
$this->outputLine('<success>An informational message was sent to Sentry</success> Event ID: #%s', [$eventId]);
6990
$this->outputLine();
91+
}
92+
93+
private function throwException(): void
94+
{
95+
$this->outputLine();
7096
$this->outputLine('This command will now throw an exception for testing purposes.');
7197
$this->outputLine();
72-
7398
(new ThrowingClass())->throwException(new StringableTestArgument((string)M_PI));
7499
}
100+
101+
private function triggerError(): void
102+
{
103+
$this->outputLine();
104+
$this->outputLine('This command will now cause a return type error for testing purposes.');
105+
$this->outputLine();
106+
107+
$function = static function (): int {
108+
/** @noinspection PhpStrictTypeCheckingInspection */
109+
return 'wrong type';
110+
};
111+
/** @noinspection PhpExpressionResultUnusedInspection */
112+
$function();
113+
}
75114
}

Classes/Package.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Flownative\Sentry;
5+
6+
use Neos\Flow\Core\Booting\Sequence;
7+
use Neos\Flow\Core\Bootstrap;
8+
use Neos\Flow\Package\Package as BasePackage;
9+
10+
class Package extends BasePackage
11+
{
12+
public function boot(Bootstrap $bootstrap)
13+
{
14+
$dispatcher = $bootstrap->getSignalSlotDispatcher();
15+
16+
$dispatcher->connect(Sequence::class, 'afterInvokeStep', function ($step) {
17+
if ($step->getIdentifier() === 'neos.flow:objectmanagement:runtime') {
18+
// instantiate client to set up Sentry and register error handler early
19+
/** @noinspection PhpExpressionResultUnusedInspection */
20+
new SentryClient();
21+
}
22+
});
23+
}
24+
}

Classes/SentryClient.php

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
use Flownative\Sentry\Context\UserContextServiceInterface;
1818
use Flownative\Sentry\Context\WithExtraDataInterface;
1919
use Flownative\Sentry\Log\CaptureResult;
20-
use GuzzleHttp\Psr7\ServerRequest;
21-
use Jenssegers\Agent\Agent;
2220
use Neos\Flow\Annotations as Flow;
2321
use Neos\Flow\Core\Bootstrap;
2422
use Neos\Flow\Error\WithReferenceCodeInterface;
@@ -31,7 +29,6 @@
3129
use Neos\Flow\Utility\Environment;
3230
use Neos\Utility\Arrays;
3331
use Psr\Log\LoggerInterface;
34-
use Psr\Log\LogLevel;
3532
use Sentry\Event;
3633
use Sentry\EventHint;
3734
use Sentry\EventId;
@@ -93,6 +90,10 @@ public function injectSettings(array $settings): void
9390

9491
public function initializeObject(): void
9592
{
93+
if (empty($this->dsn)) {
94+
return;
95+
}
96+
9697
$representationSerializer = new RepresentationSerializer(
9798
new Options([])
9899
);
@@ -102,10 +103,6 @@ public function initializeObject(): void
102103
$representationSerializer
103104
);
104105

105-
if (empty($this->dsn)) {
106-
return;
107-
}
108-
109106
\Sentry\init([
110107
'dsn' => $this->dsn,
111108
'environment' => $this->environment,
@@ -118,8 +115,7 @@ public function initializeObject(): void
118115
FLOW_PATH_ROOT . '/Packages/Framework/Neos.Flow/Classes/Log/',
119116
FLOW_PATH_ROOT . '/Packages/Libraries/neos/flow-log/'
120117
],
121-
'default_integrations' => false,
122-
'attach_stacktrace' => true
118+
'attach_stacktrace' => true,
123119
]);
124120

125121
$client = SentrySdk::getCurrentHub()->getClient();
@@ -136,38 +132,18 @@ private function setTags(): void
136132
try {
137133
$flowPackage = $this->packageManager->getPackage('Neos.Flow');
138134
$flowVersion = $flowPackage->getInstalledVersion();
139-
} catch (UnknownPackageException $e) {
135+
} catch (UnknownPackageException) {
140136
}
141137
}
142138
if (empty($flowVersion)) {
143139
$flowVersion = FLOW_VERSION_BRANCH;
144140
}
145141

146-
$currentSession = null;
147-
if ($this->sessionManager) {
148-
$currentSession = $this->sessionManager->getCurrentSession();
149-
}
142+
$currentSession = $this->sessionManager?->getCurrentSession();
150143

151144
SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) use ($flowVersion, $currentSession): void {
152145
$scope->setTag('flow_version', $flowVersion);
153146
$scope->setTag('flow_context', (string)Bootstrap::$staticObjectManager->get(Environment::class)->getContext());
154-
$scope->setTag('php_version', PHP_VERSION);
155-
156-
if (PHP_SAPI !== 'cli') {
157-
$scope->setTag('uri',
158-
(string)ServerRequest::fromGlobals()->getUri());
159-
160-
$agent = new Agent();
161-
$scope->setContext('client_os', [
162-
'name' => $agent->platform(),
163-
'version' => $agent->version($agent->platform())
164-
]);
165-
166-
$scope->setContext('client_browser', [
167-
'name' => $agent->browser(),
168-
'version' => $agent->version($agent->browser())
169-
]);
170-
}
171147

172148
if ($currentSession instanceof Session && $currentSession->isStarted()) {
173149
$scope->setTag('flow_session_sha1', sha1($currentSession->getId()));
@@ -213,6 +189,7 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar
213189

214190
$captureException = (!in_array(get_class($throwable), $this->excludeExceptionTypes, true));
215191
if ($captureException) {
192+
$this->setTags();
216193
$this->configureScope($extraData, $tags);
217194
if ($throwable instanceof Exception && $throwable->getStatusCode() === 404) {
218195
SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope): void {
@@ -226,7 +203,7 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar
226203
$message = 'ignored';
227204
}
228205
return new CaptureResult(
229-
true,
206+
true,
230207
$message,
231208
(string)$sentryEventId
232209
);
@@ -249,6 +226,7 @@ public function captureMessage(string $message, Severity $severity, array $extra
249226
$eventHint = EventHint::fromArray([
250227
'stacktrace' => $this->prepareStacktrace()
251228
]);
229+
$this->setTags();
252230
$sentryEventId = \Sentry\captureMessage($message, $severity, $eventHint);
253231

254232
if ($this->logger) {
@@ -326,7 +304,7 @@ private function prepareStacktrace(\Throwable $throwable = null): Stacktrace
326304
$frame->getRawFunctionName(),
327305
$frame->getAbsoluteFilePath(),
328306
$frame->getVars(),
329-
strpos($classPathAndFilename, 'Packages/Framework/') === false
307+
!str_contains($classPathAndFilename, 'Packages/Framework/')
330308
);
331309
}
332310
return new Stacktrace($frames);

Classes/Test/JsonSerializableTestArgument.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function __construct(int $value)
2222
$this->value = $value;
2323
}
2424

25-
public function jsonSerialize()
25+
public function jsonSerialize(): int
2626
{
2727
return $this->value;
2828
}

README.md

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ reporting of errors to [Sentry](https://www.sentry.io)
99

1010
## Key Features
1111

12-
This package makes sure that exceptions and errors logged by the Flow
13-
framework also end up in Sentry. This client takes some extra care to
14-
clean up paths and filenames of stacktraces so you get good overview
15-
while looking at an issue in the Sentry UI.
12+
This package makes sure that throwables and exceptions logged in a Flow
13+
application also end up in Sentry. This is done by implementing Flow's
14+
`ThrowableStorageInterface` and configuring the default implementation.
15+
16+
This packages takes some extra care to clean up paths and filenames of
17+
stacktraces so you get a good overview while looking at an issue in the
18+
Sentry UI.
1619

1720
## Installation
1821

@@ -29,7 +32,13 @@ $ composer require flownative/sentry
2932
You need to at least specify a DSN to be used as a logging target. Apart
3033
from that, you can configure the Sentry environment and release. These
3134
options can either be set in the Flow settings or, more conveniently, by
32-
setting the respective environment variables.
35+
setting the respective environment variables:
36+
37+
- `SENTRY_DSN`
38+
- `SENTRY_ENVIRONMENT`
39+
- `SENTRY_RELEASE`
40+
41+
The package uses these environment variables by default in the settings:
3342

3443
```yaml
3544
Flownative:
@@ -71,12 +80,12 @@ similar to the following message:
7180
7281
## Additional Data
7382
74-
Exceptions declared in an application can optionally implement
75-
`WithAdditionalDataInterface` provided by this package. If they do, the
76-
array returned by `getAdditionalData()` will be visible in the "additional
83+
Exceptions declared in an application can optionally implement
84+
`WithAdditionalDataInterface` provided by this package. If they do, the
85+
array returned by `getAdditionalData()` will be visible in the "additional
7786
data" section in Sentry.
7887

79-
Note that the array must only contain values of simple types, such as
88+
Note that the array must only contain values of simple types, such as
8089
strings, booleans or integers.
8190

8291
## Testing the Client
@@ -90,35 +99,39 @@ Run the following command in your terminal to test your configuration:
9099
./flow sentry:test
91100
Testing Sentry setup …
92101
Using the following configuration:
93-
+-------------+------------------------------------------------------------+
94-
| Option | Value |
95-
+-------------+------------------------------------------------------------+
102+
+-------------+----------------------------------------------------------+
103+
| Option | Value |
104+
+-------------+----------------------------------------------------------+
96105
| DSN | https://abc123456789abcdef1234567890ab@sentry.io/1234567 |
97-
| Environment | development |
98-
| Release | dev |
99-
| Server Name | test_container |
100-
| Sample Rate | 1 |
101-
+-------------+------------------------------------------------------------+
102-
An informational message was sent to Sentry Event ID: #587abc123457abcd8f873b4212345678
106+
| Environment | development |
107+
| Release | dev |
108+
| Server Name | test_container |
109+
| Sample Rate | 1 |
110+
+-------------+----------------------------------------------------------+
103111
104112
This command will now throw an exception for testing purposes.
105113
106-
Test exception in SentryCommandController
114+
Test exception in ThrowingClass
107115
108-
Type: Flownative\Sentry\Exception\SentryClientTestException
109-
Code: 1614759519
116+
Type: Flownative\Sentry\Test\SentryClientTestException
117+
Code: 1662712736
110118
File: Data/Temporary/Development/SubContextBeach/SubContextInstance/Cache/Code/Fl
111-
ow_Object_Classes/Flownative_Sentry_Command_SentryCommandController.php
112-
Line: 79
119+
ow_Object_Classes/Flownative_Sentry_Test_ThrowingClass.php
120+
Line: 41
113121
114122
Nested exception:
115-
Test "previous" exception thrown by the SentryCommandController
123+
Test "previous" exception in ThrowingClass
116124
117125
Type: RuntimeException
118-
Code: 1614759554
126+
Code: 1662712735
119127
File: Data/Temporary/Development/SubContextBeach/SubContextInstance/Cache/Code/Fl
120-
ow_Object_Classes/Flownative_Sentry_Command_SentryCommandController.php
121-
Line: 78
128+
ow_Object_Classes/Flownative_Sentry_Test_ThrowingClass.php
129+
Line: 40
122130
123-
Open Data/Logs/Exceptions/2021030308325919ecbf.txt for a full stack trace.
131+
Open Data/Logs/Exceptions/202411181211403b652e.txt for a full stack trace.
124132
```
133+
134+
There are two more test modes for message capturing and error handling:
135+
136+
- `./flow sentry:test --mode message`
137+
- `./flow sentry:test --mode error`

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
"ext-json": "*",
1818
"php": "^8.1",
1919
"neos/flow": "^8.0 || ^9.0 || @dev",
20-
"sentry/sentry": "^4.0",
21-
"jenssegers/agent": "^2.6"
20+
"sentry/sentry": "^4.0"
2221
},
2322
"autoload": {
2423
"psr-4": {

0 commit comments

Comments
 (0)