Skip to content

Commit

Permalink
Improve debugging experience (#1705)
Browse files Browse the repository at this point in the history
Co-authored-by: Michi Hoffmann <cleptric@users.noreply.github.com>
  • Loading branch information
stayallive and cleptric committed Mar 14, 2024
1 parent 1b9e3b2 commit 76a2184
Show file tree
Hide file tree
Showing 16 changed files with 554 additions and 429 deletions.
24 changes: 16 additions & 8 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public function captureException(\Throwable $exception, ?Scope $scope = null, ?E
$className = \get_class($exception);
if ($this->isIgnoredException($className)) {
$this->logger->info(
'The event will be discarded because it matches an entry in "ignore_exceptions".',
'The exception will be discarded because it matches an entry in "ignore_exceptions".',
['className' => $className]
);

Expand Down Expand Up @@ -294,17 +294,24 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco
$event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT);
}

$eventDescription = sprintf(
'%s%s [%s]',
$event->getLevel() !== null ? $event->getLevel() . ' ' : '',
(string) $event->getType(),
(string) $event->getId()
);

$isEvent = EventType::event() === $event->getType();
$sampleRate = $this->options->getSampleRate();

// only sample with the `sample_rate` on errors/messages
if ($isEvent && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) {
$this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]);
$this->logger->info(sprintf('The %s will be discarded because it has been sampled.', $eventDescription), ['event' => $event]);

return null;
}

$event = $this->applyIgnoreOptions($event);
$event = $this->applyIgnoreOptions($event, $eventDescription);

if ($event === null) {
return null;
Expand All @@ -316,7 +323,7 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco

if ($event === null) {
$this->logger->info(
'The event will be discarded because one of the event processors returned "null".',
sprintf('The %s will be discarded because one of the event processors returned "null".', $eventDescription),
['event' => $beforeEventProcessors]
);

Expand All @@ -330,7 +337,8 @@ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $sco
if ($event === null) {
$this->logger->info(
sprintf(
'The event will be discarded because the "%s" callback returned "null".',
'The %s will be discarded because the "%s" callback returned "null".',
$eventDescription,
$this->getBeforeSendCallbackName($beforeSendCallback)
),
['event' => $beforeSendCallback]
Expand All @@ -351,7 +359,7 @@ private function isIgnoredException(string $className): bool
return false;
}

private function applyIgnoreOptions(Event $event): ?Event
private function applyIgnoreOptions(Event $event, string $eventDescription): ?Event
{
if ($event->getType() === EventType::event()) {
$exceptions = $event->getExceptions();
Expand All @@ -363,7 +371,7 @@ private function applyIgnoreOptions(Event $event): ?Event
foreach ($exceptions as $exception) {
if ($this->isIgnoredException($exception->getType())) {
$this->logger->info(
'The event will be discarded because it matches an entry in "ignore_exceptions".',
sprintf('The %s will be discarded because it matches an entry in "ignore_exceptions".', $eventDescription),
['event' => $event]
);

Expand All @@ -381,7 +389,7 @@ private function applyIgnoreOptions(Event $event): ?Event

if (\in_array($transactionName, $this->options->getIgnoreTransactions(), true)) {
$this->logger->info(
'The event will be discarded because it matches a entry in "ignore_transactions".',
sprintf('The %s will be discarded because it matches a entry in "ignore_transactions".', $eventDescription),
['event' => $event]
);

Expand Down
15 changes: 15 additions & 0 deletions src/EventType.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ public static function metrics(): self
return self::getInstance('metrics');
}

/**
* List of all cases on the enum.
*
* @return self[]
*/
public static function cases(): array
{
return [
self::event(),
self::transaction(),
self::checkIn(),
self::metrics(),
];
}

public function __toString(): string
{
return $this->value;
Expand Down
19 changes: 14 additions & 5 deletions src/Integration/IntegrationRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,29 +51,38 @@ public static function getInstance(): self
public function setupIntegrations(Options $options, LoggerInterface $logger): array
{
$integrations = [];
$installed = [];

foreach ($this->getIntegrationsToSetup($options) as $integration) {
$integrations[\get_class($integration)] = $integration;
$integrationName = \get_class($integration);

$this->setupIntegration($integration, $logger);
$integrations[$integrationName] = $integration;

if ($this->setupIntegration($integration)) {
$installed[] = $integrationName;
}
}

if (\count($installed) > 0) {
$logger->debug(sprintf('The "%s" integration(s) have been installed.', implode(', ', $installed)));
}

return $integrations;
}

private function setupIntegration(IntegrationInterface $integration, LoggerInterface $logger): void
private function setupIntegration(IntegrationInterface $integration): bool
{
$integrationName = \get_class($integration);

if (isset($this->integrations[$integrationName])) {
return;
return false;
}

$integration->setupOnce();

$this->integrations[$integrationName] = true;

$logger->debug(sprintf('The "%s" integration has been installed.', $integrationName));
return true;
}

/**
Expand Down
9 changes: 9 additions & 0 deletions src/Options.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Sentry;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Sentry\HttpClient\HttpClientInterface;
use Sentry\Integration\ErrorListenerIntegration;
use Sentry\Integration\IntegrationInterface;
Expand Down Expand Up @@ -337,6 +338,14 @@ public function getLogger(): ?LoggerInterface
return $this->options['logger'];
}

/**
* Helper to always get a logger instance even if it was not set.
*/
public function getLoggerOrNullLogger(): LoggerInterface
{
return $this->getLogger() ?? new NullLogger();
}

/**
* Sets a PSR-3 compatible logger to log internal debug messages.
*/
Expand Down
20 changes: 20 additions & 0 deletions src/Profiling/Profile.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Sentry\Profiling;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Sentry\Context\OsContext;
use Sentry\Context\RuntimeContext;
use Sentry\Event;
Expand Down Expand Up @@ -118,9 +120,15 @@ final class Profile
*/
private $options;

/**
* @var LoggerInterface
*/
private $logger;

public function __construct(?Options $options = null)
{
$this->options = $options;
$this->logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger();
}

public function setStartTimeStamp(float $startTimeStamp): void
Expand All @@ -147,20 +155,28 @@ public function setEventId(EventId $eventId): void
public function getFormattedData(Event $event): ?array
{
if (!$this->validateExcimerLog()) {
$this->logger->warning('The profile does not contain enough samples, the profile will be discarded.');

return null;
}

$osContext = $event->getOsContext();
if (!$this->validateOsContext($osContext)) {
$this->logger->warning('The OS context is not missing or invalid, the profile will be discarded.');

return null;
}

$runtimeContext = $event->getRuntimeContext();
if (!$this->validateRuntimeContext($runtimeContext)) {
$this->logger->warning('The runtime context is not missing or invalid, the profile will be discarded.');

return null;
}

if (!$this->validateEvent($event)) {
$this->logger->warning('The event is missing a transaction and/or trace ID, the profile will be discarded.');

return null;
}

Expand Down Expand Up @@ -238,11 +254,15 @@ public function getFormattedData(Event $event): ?array
}

if (!$this->validateMaxDuration((float) $duration)) {
$this->logger->warning(sprintf('The profile is %ss which is longer than the allowed %ss, the profile will be discarded.', (float) $duration, self::MAX_PROFILE_DURATION));

return null;
}

$startTime = \DateTime::createFromFormat('U.u', number_format($this->startTimeStamp, 4, '.', ''), new \DateTimeZone('UTC'));
if ($startTime === false) {
$this->logger->warning(sprintf('The start time (%s) of the profile is not valid, the profile will be discarded.', $this->startTimeStamp));

return null;
}

Expand Down
24 changes: 18 additions & 6 deletions src/Profiling/Profiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Sentry\Profiling;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Sentry\Options;

/**
Expand All @@ -21,6 +23,11 @@ final class Profiler
*/
private $profile;

/**
* @var LoggerInterface
*/
private $logger;

/**
* @var float The sample rate (10.01ms/101 Hz)
*/
Expand All @@ -33,6 +40,7 @@ final class Profiler

public function __construct(?Options $options = null)
{
$this->logger = $options !== null ? $options->getLoggerOrNullLogger() : new NullLogger();
$this->profile = new Profile($options);

$this->initProfiler();
Expand Down Expand Up @@ -65,13 +73,17 @@ public function getProfile(): ?Profile

private function initProfiler(): void
{
if (\extension_loaded('excimer') && \PHP_VERSION_ID >= 70300) {
$this->profiler = new \ExcimerProfiler();
$this->profile->setStartTimeStamp(microtime(true));
if (!\extension_loaded('excimer')) {
$this->logger->warning('The profiler was started but is not available because the "excimer" extension is not loaded.');

$this->profiler->setEventType(EXCIMER_REAL);
$this->profiler->setPeriod(self::SAMPLE_RATE);
$this->profiler->setMaxDepth(self::MAX_STACK_DEPTH);
return;
}

$this->profiler = new \ExcimerProfiler();
$this->profile->setStartTimeStamp(microtime(true));

$this->profiler->setEventType(EXCIMER_REAL);
$this->profiler->setPeriod(self::SAMPLE_RATE);
$this->profiler->setMaxDepth(self::MAX_STACK_DEPTH);
}
}
5 changes: 5 additions & 0 deletions src/StacktraceBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Sentry;

use Sentry\Serializer\RepresentationSerializerInterface;
use Sentry\Util\PHPConfiguration;

/**
* This class builds {@see Stacktrace} objects from an instance of an exception
Expand All @@ -28,6 +29,10 @@ final class StacktraceBuilder
public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer)
{
$this->frameBuilder = new FrameBuilder($options, $representationSerializer);

if (PHPConfiguration::isBooleanIniOptionEnabled('zend.exception_ignore_args')) {
$options->getLoggerOrNullLogger()->warning('The "zend.exception_ignore_args" PHP setting is enabled which results in missing stack trace arguments, see: https://docs.sentry.io/platforms/php/troubleshooting/#missing-variables-in-stack-traces.');
}
}

/**
Expand Down

0 comments on commit 76a2184

Please sign in to comment.