Viewing file: Client.php (13.09 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php
declare(strict_types=1);
namespace Sentry;
use GuzzleHttp\Promise\PromiseInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Sentry\Integration\IntegrationInterface; use Sentry\Integration\IntegrationRegistry; use Sentry\Serializer\RepresentationSerializer; use Sentry\Serializer\RepresentationSerializerInterface; use Sentry\Serializer\SerializerInterface; use Sentry\State\Scope; use Sentry\Transport\TransportInterface;
/** * Default implementation of the {@see ClientInterface} interface. * * @author Stefano Arlandini <sarlandini@alice.it> */ final class Client implements ClientInterface { /** * The version of the protocol to communicate with the Sentry server. */ public const PROTOCOL_VERSION = '7';
/** * The identifier of the SDK. */ public const SDK_IDENTIFIER = 'sentry.php';
/** * The version of the SDK. */ public const SDK_VERSION = '3.22.1';
/** * @var Options The client options */ private $options;
/** * @var TransportInterface The transport */ private $transport;
/** * @var LoggerInterface The PSR-3 logger */ private $logger;
/** * @var array<string, IntegrationInterface> The stack of integrations * * @psalm-var array<class-string<IntegrationInterface>, IntegrationInterface> */ private $integrations;
/** * @var StacktraceBuilder */ private $stacktraceBuilder;
/** * @var string The Sentry SDK identifier */ private $sdkIdentifier;
/** * @var string The SDK version of the Client */ private $sdkVersion;
/** * Constructor. * * @param Options $options The client configuration * @param TransportInterface $transport The transport * @param string|null $sdkIdentifier The Sentry SDK identifier * @param string|null $sdkVersion The Sentry SDK version * @param SerializerInterface|null $serializer The serializer argument is deprecated since version 3.3 and will be removed in 4.0. It's currently unused. * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments * @param LoggerInterface|null $logger The PSR-3 logger */ public function __construct( Options $options, TransportInterface $transport, ?string $sdkIdentifier = null, ?string $sdkVersion = null, ?SerializerInterface $serializer = null, ?RepresentationSerializerInterface $representationSerializer = null, ?LoggerInterface $logger = null ) { $this->options = $options; $this->transport = $transport; $this->logger = $logger ?? new NullLogger(); $this->integrations = IntegrationRegistry::getInstance()->setupIntegrations($options, $this->logger); $this->stacktraceBuilder = new StacktraceBuilder($options, $representationSerializer ?? new RepresentationSerializer($this->options)); $this->sdkIdentifier = $sdkIdentifier ?? self::SDK_IDENTIFIER; $this->sdkVersion = $sdkVersion ?? self::SDK_VERSION; }
/** * {@inheritdoc} */ public function getOptions(): Options { return $this->options; }
/** * {@inheritdoc} */ public function getCspReportUrl(): ?string { $dsn = $this->options->getDsn();
if (null === $dsn) { return null; }
$endpoint = $dsn->getCspReportEndpointUrl(); $query = array_filter([ 'sentry_release' => $this->options->getRelease(), 'sentry_environment' => $this->options->getEnvironment(), ]);
if (!empty($query)) { $endpoint .= '&' . http_build_query($query, '', '&'); }
return $endpoint; }
/** * {@inheritdoc} */ public function captureMessage(string $message, ?Severity $level = null, ?Scope $scope = null, ?EventHint $hint = null): ?EventId { $event = Event::createEvent(); $event->setMessage($message); $event->setLevel($level);
return $this->captureEvent($event, $hint, $scope); }
/** * {@inheritdoc} */ public function captureException(\Throwable $exception, ?Scope $scope = null, ?EventHint $hint = null): ?EventId { $hint = $hint ?? new EventHint();
if (null === $hint->exception) { $hint->exception = $exception; }
return $this->captureEvent(Event::createEvent(), $hint, $scope); }
/** * {@inheritdoc} */ public function captureEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?EventId { $event = $this->prepareEvent($event, $hint, $scope);
if (null === $event) { return null; }
try { /** @var Response $response */ $response = $this->transport->send($event)->wait(); $event = $response->getEvent();
if (null !== $event) { return $event->getId(); } } catch (\Throwable $exception) { }
return null; }
/** * {@inheritdoc} */ public function captureLastError(?Scope $scope = null, ?EventHint $hint = null): ?EventId { $error = error_get_last();
if (null === $error || !isset($error['message'][0])) { return null; }
$exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']);
return $this->captureException($exception, $scope, $hint); }
/** * {@inheritdoc} * * @psalm-template T of IntegrationInterface */ public function getIntegration(string $className): ?IntegrationInterface { /** @psalm-var T|null */ return $this->integrations[$className] ?? null; }
/** * {@inheritdoc} */ public function flush(?int $timeout = null): PromiseInterface { return $this->transport->close($timeout); }
/** * {@inheritdoc} */ public function getStacktraceBuilder(): StacktraceBuilder { return $this->stacktraceBuilder; }
/** * Assembles an event and prepares it to be sent of to Sentry. * * @param Event $event The payload that will be converted to an Event * @param EventHint|null $hint May contain additional information about the event * @param Scope|null $scope Optional scope which enriches the Event * * @return Event|null The prepared event object or null if it must be discarded */ private function prepareEvent(Event $event, ?EventHint $hint = null, ?Scope $scope = null): ?Event { if (null !== $hint) { if (null !== $hint->exception && empty($event->getExceptions())) { $this->addThrowableToEvent($event, $hint->exception, $hint); }
if (null !== $hint->stacktrace && null === $event->getStacktrace()) { $event->setStacktrace($hint->stacktrace); } }
$this->addMissingStacktraceToEvent($event);
$event->setSdkIdentifier($this->sdkIdentifier); $event->setSdkVersion($this->sdkVersion); $event->setTags(array_merge($this->options->getTags(), $event->getTags()));
if (null === $event->getServerName()) { $event->setServerName($this->options->getServerName()); }
if (null === $event->getRelease()) { $event->setRelease($this->options->getRelease()); }
if (null === $event->getEnvironment()) { $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT); }
if (null === $event->getLogger()) { $event->setLogger($this->options->getLogger(false)); }
$isTransaction = EventType::transaction() === $event->getType(); $sampleRate = $this->options->getSampleRate();
if (!$isTransaction && $sampleRate < 1 && mt_rand(1, 100) / 100.0 > $sampleRate) { $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]);
return null; }
$event = $this->applyIgnoreOptions($event);
if (null === $event) { return null; }
if (null !== $scope) { $beforeEventProcessors = $event; $event = $scope->applyToEvent($event, $hint, $this->options);
if (null === $event) { $this->logger->info( 'The event will be discarded because one of the event processors returned "null".', ['event' => $beforeEventProcessors] );
return null; } }
$beforeSendCallback = $event; $event = $this->applyBeforeSendCallback($event, $hint);
if (null === $event) { $this->logger->info( sprintf( 'The event will be discarded because the "%s" callback returned "null".', $this->getBeforeSendCallbackName($beforeSendCallback) ), ['event' => $beforeSendCallback] ); }
return $event; }
private function applyIgnoreOptions(Event $event): ?Event { if ($event->getType() === EventType::event()) { $exceptions = $event->getExceptions();
if (empty($exceptions)) { return $event; }
foreach ($exceptions as $exception) { if (\in_array($exception->getType(), $this->options->getIgnoreExceptions(), true)) { $this->logger->info( 'The event will be discarded because it matches an entry in "ignore_exceptions".', ['event' => $event] );
return null; } } }
if ($event->getType() === EventType::transaction()) { $transactionName = $event->getTransaction();
if (null === $transactionName) { return $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".', ['event' => $event] );
return null; } }
return $event; }
private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event { if ($event->getType() === EventType::event()) { return ($this->options->getBeforeSendCallback())($event, $hint); }
if ($event->getType() === EventType::transaction()) { return ($this->options->getBeforeSendTransactionCallback())($event, $hint); }
return $event; }
private function getBeforeSendCallbackName(Event $event): string { $beforeSendCallbackName = 'before_send';
if ($event->getType() === EventType::transaction()) { $beforeSendCallbackName = 'before_send_transaction'; }
return $beforeSendCallbackName; }
/** * Optionally adds a missing stacktrace to the Event if the client is configured to do so. * * @param Event $event The Event to add the missing stacktrace to */ private function addMissingStacktraceToEvent(Event $event): void { if (!$this->options->shouldAttachStacktrace()) { return; }
// We should not add a stacktrace when the event already has one or contains exceptions if (null !== $event->getStacktrace() || !empty($event->getExceptions())) { return; }
$event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace( debug_backtrace(0), __FILE__, __LINE__ - 3 )); }
/** * Stores the given exception in the passed event. * * @param Event $event The event that will be enriched with the exception * @param \Throwable $exception The exception that will be processed and added to the event * @param EventHint $hint Contains additional information about the event */ private function addThrowableToEvent(Event $event, \Throwable $exception, EventHint $hint): void { if ($exception instanceof \ErrorException && null === $event->getLevel()) { $event->setLevel(Severity::fromError($exception->getSeverity())); }
$exceptions = [];
do { $exceptions[] = new ExceptionDataBag( $exception, $this->stacktraceBuilder->buildFromException($exception), $hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERIC, true, ['code' => $exception->getCode()]) ); } while ($exception = $exception->getPrevious());
$event->setExceptions($exceptions); } }
|