Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Early logging to Sentry #35

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions Classes/Command/SentryCommandController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@

final class SentryCommandController extends CommandController
{
const TEST_MODE_MESSAGE = 'message';
const TEST_MODE_THROW = 'throw';
const TEST_MODE_ERROR = 'error';

/**
* @Flow\Inject
* @var SentryClient
Expand All @@ -39,13 +43,12 @@ final class SentryCommandController extends CommandController
*
* @throws SentryClientTestException
*/
public function testCommand(): void
public function testCommand(string $mode = self::TEST_MODE_THROW): void
{
$this->output->outputLine('<b>Testing Sentry setup …</b>');
$this->output->outputLine('Using the following configuration:');

$options = $this->sentryClient->getOptions();

$this->output->outputTable([
['DSN', $options->getDsn()],
['Environment', $options->getEnvironment()],
Expand All @@ -57,6 +60,23 @@ public function testCommand(): void
'Value'
]);

switch ($mode) {
case self::TEST_MODE_MESSAGE:
$this->captureMessage();
break;
case self::TEST_MODE_THROW:
$this->throwException();
break;
case self::TEST_MODE_ERROR:
$this->triggerError();
break;
default:
$this->output->outputLine('<error>Unknown mode given</error>');
}
}

private function captureMessage(): void
{
$eventId = $this->sentryClient->captureMessage(
'Flownative Sentry Plugin Test',
Severity::debug(),
Expand All @@ -65,11 +85,30 @@ public function testCommand(): void
]
);

$this->outputLine();
$this->outputLine('<success>An informational message was sent to Sentry</success> Event ID: #%s', [$eventId]);
$this->outputLine();
}

private function throwException(): void
{
$this->outputLine();
$this->outputLine('This command will now throw an exception for testing purposes.');
$this->outputLine();

(new ThrowingClass())->throwException(new StringableTestArgument((string)M_PI));
}

private function triggerError(): void
{
$this->outputLine();
$this->outputLine('This command will now cause a return type error for testing purposes.');
$this->outputLine();

$function = static function (): int {
/** @noinspection PhpStrictTypeCheckingInspection */
return 'wrong type';
};
/** @noinspection PhpExpressionResultUnusedInspection */
$function();
}
}
24 changes: 24 additions & 0 deletions Classes/Package.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);

namespace Flownative\Sentry;

use Neos\Flow\Core\Booting\Sequence;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Package\Package as BasePackage;

class Package extends BasePackage
{
public function boot(Bootstrap $bootstrap)
{
$dispatcher = $bootstrap->getSignalSlotDispatcher();

$dispatcher->connect(Sequence::class, 'afterInvokeStep', function ($step) {
if ($step->getIdentifier() === 'neos.flow:objectmanagement:runtime') {
// instantiate client to set up Sentry and register error handler early
/** @noinspection PhpExpressionResultUnusedInspection */
new SentryClient();
}
});
}
}
44 changes: 11 additions & 33 deletions Classes/SentryClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
use Flownative\Sentry\Context\UserContextServiceInterface;
use Flownative\Sentry\Context\WithExtraDataInterface;
use Flownative\Sentry\Log\CaptureResult;
use GuzzleHttp\Psr7\ServerRequest;
use Jenssegers\Agent\Agent;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Error\WithReferenceCodeInterface;
Expand All @@ -31,7 +29,6 @@
use Neos\Flow\Utility\Environment;
use Neos\Utility\Arrays;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Sentry\Event;
use Sentry\EventHint;
use Sentry\EventId;
Expand Down Expand Up @@ -93,6 +90,10 @@ public function injectSettings(array $settings): void

public function initializeObject(): void
{
if (empty($this->dsn)) {
return;
}

$representationSerializer = new RepresentationSerializer(
new Options([])
);
Expand All @@ -102,10 +103,6 @@ public function initializeObject(): void
$representationSerializer
);

if (empty($this->dsn)) {
return;
}

\Sentry\init([
'dsn' => $this->dsn,
'environment' => $this->environment,
Expand All @@ -118,8 +115,7 @@ public function initializeObject(): void
FLOW_PATH_ROOT . '/Packages/Framework/Neos.Flow/Classes/Log/',
FLOW_PATH_ROOT . '/Packages/Libraries/neos/flow-log/'
],
'default_integrations' => false,
'attach_stacktrace' => true
'attach_stacktrace' => true,
]);

$client = SentrySdk::getCurrentHub()->getClient();
Expand All @@ -136,38 +132,18 @@ private function setTags(): void
try {
$flowPackage = $this->packageManager->getPackage('Neos.Flow');
$flowVersion = $flowPackage->getInstalledVersion();
} catch (UnknownPackageException $e) {
} catch (UnknownPackageException) {
}
}
if (empty($flowVersion)) {
$flowVersion = FLOW_VERSION_BRANCH;
}

$currentSession = null;
if ($this->sessionManager) {
$currentSession = $this->sessionManager->getCurrentSession();
}
$currentSession = $this->sessionManager?->getCurrentSession();

SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) use ($flowVersion, $currentSession): void {
$scope->setTag('flow_version', $flowVersion);
$scope->setTag('flow_context', (string)Bootstrap::$staticObjectManager->get(Environment::class)->getContext());
$scope->setTag('php_version', PHP_VERSION);

if (PHP_SAPI !== 'cli') {
$scope->setTag('uri',
(string)ServerRequest::fromGlobals()->getUri());

$agent = new Agent();
$scope->setContext('client_os', [
'name' => $agent->platform(),
'version' => $agent->version($agent->platform())
]);

$scope->setContext('client_browser', [
'name' => $agent->browser(),
'version' => $agent->version($agent->browser())
]);
}

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

$captureException = (!in_array(get_class($throwable), $this->excludeExceptionTypes, true));
if ($captureException) {
$this->setTags();
$this->configureScope($extraData, $tags);
if ($throwable instanceof Exception && $throwable->getStatusCode() === 404) {
SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope): void {
Expand All @@ -226,7 +203,7 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar
$message = 'ignored';
}
return new CaptureResult(
true,
true,
$message,
(string)$sentryEventId
);
Expand All @@ -249,6 +226,7 @@ public function captureMessage(string $message, Severity $severity, array $extra
$eventHint = EventHint::fromArray([
'stacktrace' => $this->prepareStacktrace()
]);
$this->setTags();
$sentryEventId = \Sentry\captureMessage($message, $severity, $eventHint);

if ($this->logger) {
Expand Down Expand Up @@ -326,7 +304,7 @@ private function prepareStacktrace(\Throwable $throwable = null): Stacktrace
$frame->getRawFunctionName(),
$frame->getAbsoluteFilePath(),
$frame->getVars(),
strpos($classPathAndFilename, 'Packages/Framework/') === false
!str_contains($classPathAndFilename, 'Packages/Framework/')
);
}
return new Stacktrace($frames);
Expand Down
2 changes: 1 addition & 1 deletion Classes/Test/JsonSerializableTestArgument.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public function __construct(int $value)
$this->value = $value;
}

public function jsonSerialize()
public function jsonSerialize(): int
{
return $this->value;
}
Expand Down
69 changes: 41 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ reporting of errors to [Sentry](https://www.sentry.io)

## Key Features

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

This packages takes some extra care to clean up paths and filenames of
stacktraces so you get a good overview while looking at an issue in the
Sentry UI.

## Installation

Expand All @@ -29,7 +32,13 @@ $ composer require flownative/sentry
You need to at least specify a DSN to be used as a logging target. Apart
from that, you can configure the Sentry environment and release. These
options can either be set in the Flow settings or, more conveniently, by
setting the respective environment variables.
setting the respective environment variables:

- `SENTRY_DSN`
- `SENTRY_ENVIRONMENT`
- `SENTRY_RELEASE`

The package uses these environment variables by default in the settings:

```yaml
Flownative:
Expand Down Expand Up @@ -71,12 +80,12 @@ similar to the following message:

## Additional Data

Exceptions declared in an application can optionally implement
`WithAdditionalDataInterface` provided by this package. If they do, the
array returned by `getAdditionalData()` will be visible in the "additional
Exceptions declared in an application can optionally implement
`WithAdditionalDataInterface` provided by this package. If they do, the
array returned by `getAdditionalData()` will be visible in the "additional
data" section in Sentry.

Note that the array must only contain values of simple types, such as
Note that the array must only contain values of simple types, such as
strings, booleans or integers.

## Testing the Client
Expand All @@ -90,35 +99,39 @@ Run the following command in your terminal to test your configuration:
./flow sentry:test
Testing Sentry setup …
Using the following configuration:
+-------------+------------------------------------------------------------+
| Option | Value |
+-------------+------------------------------------------------------------+
+-------------+----------------------------------------------------------+
| Option | Value |
+-------------+----------------------------------------------------------+
| DSN | https://[email protected]/1234567 |
| Environment | development |
| Release | dev |
| Server Name | test_container |
| Sample Rate | 1 |
+-------------+------------------------------------------------------------+
An informational message was sent to Sentry Event ID: #587abc123457abcd8f873b4212345678
| Environment | development |
| Release | dev |
| Server Name | test_container |
| Sample Rate | 1 |
+-------------+----------------------------------------------------------+

This command will now throw an exception for testing purposes.

Test exception in SentryCommandController
Test exception in ThrowingClass

Type: Flownative\Sentry\Exception\SentryClientTestException
Code: 1614759519
Type: Flownative\Sentry\Test\SentryClientTestException
Code: 1662712736
File: Data/Temporary/Development/SubContextBeach/SubContextInstance/Cache/Code/Fl
ow_Object_Classes/Flownative_Sentry_Command_SentryCommandController.php
Line: 79
ow_Object_Classes/Flownative_Sentry_Test_ThrowingClass.php
Line: 41

Nested exception:
Test "previous" exception thrown by the SentryCommandController
Test "previous" exception in ThrowingClass

Type: RuntimeException
Code: 1614759554
Code: 1662712735
File: Data/Temporary/Development/SubContextBeach/SubContextInstance/Cache/Code/Fl
ow_Object_Classes/Flownative_Sentry_Command_SentryCommandController.php
Line: 78
ow_Object_Classes/Flownative_Sentry_Test_ThrowingClass.php
Line: 40

Open Data/Logs/Exceptions/2021030308325919ecbf.txt for a full stack trace.
Open Data/Logs/Exceptions/202411181211403b652e.txt for a full stack trace.
```

There are two more test modes for message capturing and error handling:

- `./flow sentry:test --mode message`
- `./flow sentry:test --mode error`
3 changes: 1 addition & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
"ext-json": "*",
"php": "^8.1",
"neos/flow": "^8.0 || ^9.0 || @dev",
"sentry/sentry": "^4.0",
"jenssegers/agent": "^2.6"
"sentry/sentry": "^4.0"
},
"autoload": {
"psr-4": {
Expand Down