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

Align return type of captureMessage to captureThrowable #39

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
53 changes: 48 additions & 5 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,19 +60,59 @@ public function testCommand(): void
'Value'
]);

$eventId = $this->sentryClient->captureMessage(
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
{
$captureResult = $this->sentryClient->captureMessage(
'Flownative Sentry Plugin Test',
Severity::debug(),
[
'Flownative Sentry Client Version' => 'dev'
]
);

$this->outputLine('<success>An informational message was sent to Sentry</success> Event ID: #%s', [$eventId]);
$this->outputLine();
$this->outputLine('This command will now throw an exception for testing purposes.');
if ($captureResult->suceess) {
$this->outputLine('<success>An informational message was sent to Sentry</success> Event ID: #%s', [$captureResult->eventId]);
} else {
$this->outputLine('<error>Sending an informational message to Sentry failed</error>: %s', [$captureResult->message]);
}
$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();
}
}
64 changes: 42 additions & 22 deletions Classes/Log/SentryFileBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@

use Flownative\Sentry\SentryClientTrait;
use Neos\Flow\Log\Backend\FileBackend;
use Sentry\Severity;
use Sentry\Breadcrumb;
use Sentry\SentrySdk;

class SentryFileBackend extends FileBackend
{
use SentryClientTrait;

private bool $capturingMessage = false;

/**
* Appends the given message along with the additional information into the log.
*
Expand All @@ -36,29 +35,50 @@ class SentryFileBackend extends FileBackend
*/
public function append(string $message, int $severity = LOG_INFO, $additionalData = null, ?string $packageKey = null, ?string $className = null, ?string $methodName = null): void
{
if ($this->capturingMessage) {
return;
try {
SentrySdk::getCurrentHub()->addBreadcrumb(
new Breadcrumb(
$this->getBreadcrumbLevel($severity),
$this->getBreadcrumbType($severity),
basename($this->logFileUrl),
$message,
($additionalData ?? []) + array_filter([
'packageKey' => $packageKey, 'className' => $className, 'methodName' => $methodName
]),
time()
)
);
} catch (\Throwable $throwable) {
parent::append(
sprintf('%s (%s)', $throwable->getMessage(), $throwable->getCode()),
LOG_WARNING,
null,
'Flownative.Sentry',
__CLASS__,
__METHOD__
);
}

try {
$this->capturingMessage = true;
parent::append($message, $severity, $additionalData, $packageKey, $className, $methodName);
}

$sentryClient = self::getSentryClient();
if ($severity <= LOG_NOTICE && $sentryClient) {
$sentrySeverity = match ($severity) {
LOG_WARNING => Severity::warning(),
LOG_ERR => Severity::error(),
LOG_CRIT, LOG_ALERT, LOG_EMERG => Severity::fatal(),
default => Severity::info(),
};
private function getBreadcrumbLevel(int $severity): string
{
return match ($severity) {
LOG_EMERG, LOG_ALERT, LOG_CRIT => Breadcrumb::LEVEL_FATAL,
LOG_ERR => Breadcrumb::LEVEL_ERROR,
LOG_WARNING => Breadcrumb::LEVEL_WARNING,
LOG_NOTICE, LOG_INFO => Breadcrumb::LEVEL_INFO,
default => Breadcrumb::LEVEL_DEBUG,
};
}

$sentryClient->captureMessage($message, $sentrySeverity, ['Additional Data' => $additionalData]);
}
parent::append($message, $severity, $additionalData, $packageKey, $className, $methodName);
} catch (\Throwable $throwable) {
echo sprintf('SentryFileBackend: %s (%s)', $throwable->getMessage(), $throwable->getCode());
} finally {
$this->capturingMessage = false;
private function getBreadcrumbType(int $severity): string
{
if ($severity >= LOG_ERR) {
return Breadcrumb::TYPE_ERROR;
}

return Breadcrumb::TYPE_DEFAULT;
}
}
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();
}
});
}
}
78 changes: 23 additions & 55 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,43 +203,34 @@ public function captureThrowable(Throwable $throwable, array $extraData = [], ar
$message = 'ignored';
}
return new CaptureResult(
true,
true,
$message,
(string)$sentryEventId
);
}

public function captureMessage(string $message, Severity $severity, array $extraData = [], array $tags = []): ?EventId
public function captureMessage(string $message, Severity $severity, array $extraData = [], array $tags = []): CaptureResult
{
if (empty($this->dsn)) {
if ($this->logger) {
$this->logger->warning('Sentry: Failed capturing message, because no Sentry DSN was set. Please check your settings.');
}
return null;
}

if (preg_match('/Sentry: [0-9a-f]{32}/', $message) === 1) {
return null;
return new CaptureResult(
false,
'Failed capturing message, because no Sentry DSN was set. Please check your settings.',
''
);
}

$this->setTags();
$this->configureScope($extraData, $tags);
$eventHint = EventHint::fromArray([
'stacktrace' => $this->prepareStacktrace()
]);
$sentryEventId = \Sentry\captureMessage($message, $severity, $eventHint);

if ($this->logger) {
$this->logger->log(
(string)$severity,
sprintf(
'%s (Sentry: %s)',
$message,
$sentryEventId
)
);
}
$sentryEventId = SentrySdk::getCurrentHub()->captureMessage($message, $severity, $eventHint);

return $sentryEventId;
return new CaptureResult(
true,
$message,
(string)$sentryEventId
);
}

private function configureScope(array $extraData, array $tags): void
Expand Down Expand Up @@ -326,7 +294,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
7 changes: 5 additions & 2 deletions Configuration/Settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ Flownative:
Neos:
Flow:
log:
systemLogger:
backend: Flownative\Sentry\Log\SentryFileBackend
psr3:
Neos\Flow\Log\PsrLoggerFactory:
systemLogger:
default:
class: 'Flownative\Sentry\Log\SentryFileBackend'
throwables:
storageClass: 'Flownative\Sentry\Log\SentryStorage'
optionsByImplementation:
Expand Down
Loading