vendor/sentry/sentry/src/Client.php line 177

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sentry;
  4. use GuzzleHttp\Promise\PromiseInterface;
  5. use Psr\Log\LoggerInterface;
  6. use Psr\Log\NullLogger;
  7. use Sentry\Integration\IntegrationInterface;
  8. use Sentry\Integration\IntegrationRegistry;
  9. use Sentry\Serializer\RepresentationSerializer;
  10. use Sentry\Serializer\RepresentationSerializerInterface;
  11. use Sentry\Serializer\SerializerInterface;
  12. use Sentry\State\Scope;
  13. use Sentry\Transport\TransportInterface;
  14. /**
  15.  * Default implementation of the {@see ClientInterface} interface.
  16.  *
  17.  * @author Stefano Arlandini <sarlandini@alice.it>
  18.  */
  19. final class Client implements ClientInterface
  20. {
  21.     /**
  22.      * The version of the protocol to communicate with the Sentry server.
  23.      */
  24.     public const PROTOCOL_VERSION '7';
  25.     /**
  26.      * The identifier of the SDK.
  27.      */
  28.     public const SDK_IDENTIFIER 'sentry.php';
  29.     /**
  30.      * The version of the SDK.
  31.      */
  32.     public const SDK_VERSION '3.22.1';
  33.     /**
  34.      * @var Options The client options
  35.      */
  36.     private $options;
  37.     /**
  38.      * @var TransportInterface The transport
  39.      */
  40.     private $transport;
  41.     /**
  42.      * @var LoggerInterface The PSR-3 logger
  43.      */
  44.     private $logger;
  45.     /**
  46.      * @var array<string, IntegrationInterface> The stack of integrations
  47.      *
  48.      * @psalm-var array<class-string<IntegrationInterface>, IntegrationInterface>
  49.      */
  50.     private $integrations;
  51.     /**
  52.      * @var StacktraceBuilder
  53.      */
  54.     private $stacktraceBuilder;
  55.     /**
  56.      * @var string The Sentry SDK identifier
  57.      */
  58.     private $sdkIdentifier;
  59.     /**
  60.      * @var string The SDK version of the Client
  61.      */
  62.     private $sdkVersion;
  63.     /**
  64.      * Constructor.
  65.      *
  66.      * @param Options                                $options                  The client configuration
  67.      * @param TransportInterface                     $transport                The transport
  68.      * @param string|null                            $sdkIdentifier            The Sentry SDK identifier
  69.      * @param string|null                            $sdkVersion               The Sentry SDK version
  70.      * @param SerializerInterface|null               $serializer               The serializer argument is deprecated since version 3.3 and will be removed in 4.0. It's currently unused.
  71.      * @param RepresentationSerializerInterface|null $representationSerializer The serializer for function arguments
  72.      * @param LoggerInterface|null                   $logger                   The PSR-3 logger
  73.      */
  74.     public function __construct(
  75.         Options $options,
  76.         TransportInterface $transport,
  77.         ?string $sdkIdentifier null,
  78.         ?string $sdkVersion null,
  79.         ?SerializerInterface $serializer null,
  80.         ?RepresentationSerializerInterface $representationSerializer null,
  81.         ?LoggerInterface $logger null
  82.     ) {
  83.         $this->options $options;
  84.         $this->transport $transport;
  85.         $this->logger $logger ?? new NullLogger();
  86.         $this->integrations IntegrationRegistry::getInstance()->setupIntegrations($options$this->logger);
  87.         $this->stacktraceBuilder = new StacktraceBuilder($options$representationSerializer ?? new RepresentationSerializer($this->options));
  88.         $this->sdkIdentifier $sdkIdentifier ?? self::SDK_IDENTIFIER;
  89.         $this->sdkVersion $sdkVersion ?? self::SDK_VERSION;
  90.     }
  91.     /**
  92.      * {@inheritdoc}
  93.      */
  94.     public function getOptions(): Options
  95.     {
  96.         return $this->options;
  97.     }
  98.     /**
  99.      * {@inheritdoc}
  100.      */
  101.     public function getCspReportUrl(): ?string
  102.     {
  103.         $dsn $this->options->getDsn();
  104.         if (null === $dsn) {
  105.             return null;
  106.         }
  107.         $endpoint $dsn->getCspReportEndpointUrl();
  108.         $query array_filter([
  109.             'sentry_release' => $this->options->getRelease(),
  110.             'sentry_environment' => $this->options->getEnvironment(),
  111.         ]);
  112.         if (!empty($query)) {
  113.             $endpoint .= '&' http_build_query($query'''&');
  114.         }
  115.         return $endpoint;
  116.     }
  117.     /**
  118.      * {@inheritdoc}
  119.      */
  120.     public function captureMessage(string $message, ?Severity $level null, ?Scope $scope null, ?EventHint $hint null): ?EventId
  121.     {
  122.         $event Event::createEvent();
  123.         $event->setMessage($message);
  124.         $event->setLevel($level);
  125.         return $this->captureEvent($event$hint$scope);
  126.     }
  127.     /**
  128.      * {@inheritdoc}
  129.      */
  130.     public function captureException(\Throwable $exception, ?Scope $scope null, ?EventHint $hint null): ?EventId
  131.     {
  132.         $hint $hint ?? new EventHint();
  133.         if (null === $hint->exception) {
  134.             $hint->exception $exception;
  135.         }
  136.         return $this->captureEvent(Event::createEvent(), $hint$scope);
  137.     }
  138.     /**
  139.      * {@inheritdoc}
  140.      */
  141.     public function captureEvent(Event $event, ?EventHint $hint null, ?Scope $scope null): ?EventId
  142.     {
  143.         $event $this->prepareEvent($event$hint$scope);
  144.         if (null === $event) {
  145.             return null;
  146.         }
  147.         try {
  148.             /** @var Response $response */
  149.             $response $this->transport->send($event)->wait();
  150.             $event $response->getEvent();
  151.             if (null !== $event) {
  152.                 return $event->getId();
  153.             }
  154.         } catch (\Throwable $exception) {
  155.         }
  156.         return null;
  157.     }
  158.     /**
  159.      * {@inheritdoc}
  160.      */
  161.     public function captureLastError(?Scope $scope null, ?EventHint $hint null): ?EventId
  162.     {
  163.         $error error_get_last();
  164.         if (null === $error || !isset($error['message'][0])) {
  165.             return null;
  166.         }
  167.         $exception = new \ErrorException(@$error['message'], 0, @$error['type'], @$error['file'], @$error['line']);
  168.         return $this->captureException($exception$scope$hint);
  169.     }
  170.     /**
  171.      * {@inheritdoc}
  172.      *
  173.      * @psalm-template T of IntegrationInterface
  174.      */
  175.     public function getIntegration(string $className): ?IntegrationInterface
  176.     {
  177.         /** @psalm-var T|null */
  178.         return $this->integrations[$className] ?? null;
  179.     }
  180.     /**
  181.      * {@inheritdoc}
  182.      */
  183.     public function flush(?int $timeout null): PromiseInterface
  184.     {
  185.         return $this->transport->close($timeout);
  186.     }
  187.     /**
  188.      * {@inheritdoc}
  189.      */
  190.     public function getStacktraceBuilder(): StacktraceBuilder
  191.     {
  192.         return $this->stacktraceBuilder;
  193.     }
  194.     /**
  195.      * Assembles an event and prepares it to be sent of to Sentry.
  196.      *
  197.      * @param Event          $event The payload that will be converted to an Event
  198.      * @param EventHint|null $hint  May contain additional information about the event
  199.      * @param Scope|null     $scope Optional scope which enriches the Event
  200.      *
  201.      * @return Event|null The prepared event object or null if it must be discarded
  202.      */
  203.     private function prepareEvent(Event $event, ?EventHint $hint null, ?Scope $scope null): ?Event
  204.     {
  205.         if (null !== $hint) {
  206.             if (null !== $hint->exception && empty($event->getExceptions())) {
  207.                 $this->addThrowableToEvent($event$hint->exception$hint);
  208.             }
  209.             if (null !== $hint->stacktrace && null === $event->getStacktrace()) {
  210.                 $event->setStacktrace($hint->stacktrace);
  211.             }
  212.         }
  213.         $this->addMissingStacktraceToEvent($event);
  214.         $event->setSdkIdentifier($this->sdkIdentifier);
  215.         $event->setSdkVersion($this->sdkVersion);
  216.         $event->setTags(array_merge($this->options->getTags(), $event->getTags()));
  217.         if (null === $event->getServerName()) {
  218.             $event->setServerName($this->options->getServerName());
  219.         }
  220.         if (null === $event->getRelease()) {
  221.             $event->setRelease($this->options->getRelease());
  222.         }
  223.         if (null === $event->getEnvironment()) {
  224.             $event->setEnvironment($this->options->getEnvironment() ?? Event::DEFAULT_ENVIRONMENT);
  225.         }
  226.         if (null === $event->getLogger()) {
  227.             $event->setLogger($this->options->getLogger(false));
  228.         }
  229.         $isTransaction EventType::transaction() === $event->getType();
  230.         $sampleRate $this->options->getSampleRate();
  231.         if (!$isTransaction && $sampleRate && mt_rand(1100) / 100.0 $sampleRate) {
  232.             $this->logger->info('The event will be discarded because it has been sampled.', ['event' => $event]);
  233.             return null;
  234.         }
  235.         $event $this->applyIgnoreOptions($event);
  236.         if (null === $event) {
  237.             return null;
  238.         }
  239.         if (null !== $scope) {
  240.             $beforeEventProcessors $event;
  241.             $event $scope->applyToEvent($event$hint$this->options);
  242.             if (null === $event) {
  243.                 $this->logger->info(
  244.                     'The event will be discarded because one of the event processors returned "null".',
  245.                     ['event' => $beforeEventProcessors]
  246.                 );
  247.                 return null;
  248.             }
  249.         }
  250.         $beforeSendCallback $event;
  251.         $event $this->applyBeforeSendCallback($event$hint);
  252.         if (null === $event) {
  253.             $this->logger->info(
  254.                 sprintf(
  255.                     'The event will be discarded because the "%s" callback returned "null".',
  256.                     $this->getBeforeSendCallbackName($beforeSendCallback)
  257.                 ),
  258.                 ['event' => $beforeSendCallback]
  259.             );
  260.         }
  261.         return $event;
  262.     }
  263.     private function applyIgnoreOptions(Event $event): ?Event
  264.     {
  265.         if ($event->getType() === EventType::event()) {
  266.             $exceptions $event->getExceptions();
  267.             if (empty($exceptions)) {
  268.                 return $event;
  269.             }
  270.             foreach ($exceptions as $exception) {
  271.                 if (\in_array($exception->getType(), $this->options->getIgnoreExceptions(), true)) {
  272.                     $this->logger->info(
  273.                         'The event will be discarded because it matches an entry in "ignore_exceptions".',
  274.                         ['event' => $event]
  275.                     );
  276.                     return null;
  277.                 }
  278.             }
  279.         }
  280.         if ($event->getType() === EventType::transaction()) {
  281.             $transactionName $event->getTransaction();
  282.             if (null === $transactionName) {
  283.                 return $event;
  284.             }
  285.             if (\in_array($transactionName$this->options->getIgnoreTransactions(), true)) {
  286.                 $this->logger->info(
  287.                     'The event will be discarded because it matches a entry in "ignore_transactions".',
  288.                     ['event' => $event]
  289.                 );
  290.                 return null;
  291.             }
  292.         }
  293.         return $event;
  294.     }
  295.     private function applyBeforeSendCallback(Event $event, ?EventHint $hint): ?Event
  296.     {
  297.         if ($event->getType() === EventType::event()) {
  298.             return ($this->options->getBeforeSendCallback())($event$hint);
  299.         }
  300.         if ($event->getType() === EventType::transaction()) {
  301.             return ($this->options->getBeforeSendTransactionCallback())($event$hint);
  302.         }
  303.         return $event;
  304.     }
  305.     private function getBeforeSendCallbackName(Event $event): string
  306.     {
  307.         $beforeSendCallbackName 'before_send';
  308.         if ($event->getType() === EventType::transaction()) {
  309.             $beforeSendCallbackName 'before_send_transaction';
  310.         }
  311.         return $beforeSendCallbackName;
  312.     }
  313.     /**
  314.      * Optionally adds a missing stacktrace to the Event if the client is configured to do so.
  315.      *
  316.      * @param Event $event The Event to add the missing stacktrace to
  317.      */
  318.     private function addMissingStacktraceToEvent(Event $event): void
  319.     {
  320.         if (!$this->options->shouldAttachStacktrace()) {
  321.             return;
  322.         }
  323.         // We should not add a stacktrace when the event already has one or contains exceptions
  324.         if (null !== $event->getStacktrace() || !empty($event->getExceptions())) {
  325.             return;
  326.         }
  327.         $event->setStacktrace($this->stacktraceBuilder->buildFromBacktrace(
  328.             debug_backtrace(0),
  329.             __FILE__,
  330.             __LINE__ 3
  331.         ));
  332.     }
  333.     /**
  334.      * Stores the given exception in the passed event.
  335.      *
  336.      * @param Event      $event     The event that will be enriched with the exception
  337.      * @param \Throwable $exception The exception that will be processed and added to the event
  338.      * @param EventHint  $hint      Contains additional information about the event
  339.      */
  340.     private function addThrowableToEvent(Event $event\Throwable $exceptionEventHint $hint): void
  341.     {
  342.         if ($exception instanceof \ErrorException && null === $event->getLevel()) {
  343.             $event->setLevel(Severity::fromError($exception->getSeverity()));
  344.         }
  345.         $exceptions = [];
  346.         do {
  347.             $exceptions[] = new ExceptionDataBag(
  348.                 $exception,
  349.                 $this->stacktraceBuilder->buildFromException($exception),
  350.                 $hint->mechanism ?? new ExceptionMechanism(ExceptionMechanism::TYPE_GENERICtrue, ['code' => $exception->getCode()])
  351.             );
  352.         } while ($exception $exception->getPrevious());
  353.         $event->setExceptions($exceptions);
  354.     }
  355. }