You are here

RequestSubscriber.php in Raven: Sentry Integration 3.x

Same filename and directory in other branches
  1. 8.2 src/EventSubscriber/RequestSubscriber.php

File

src/EventSubscriber/RequestSubscriber.php
View source
<?php

namespace Drupal\raven\EventSubscriber;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\raven\Logger\Raven;
use Sentry\SentrySdk;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\TransactionContext;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\KernelEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Initializes Raven logger so Sentry functions can be called.
 */
class RequestSubscriber implements EventSubscriberInterface, ContainerAwareInterface, TrustedCallbackInterface {
  use ContainerAwareTrait;

  /**
   * Config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface|null
   */
  protected $configFactory;

  /**
   * {@inheritdoc}
   *
   * @var \Symfony\Component\DependencyInjection\ContainerInterface|null
   */
  protected $container;

  /**
   * Raven logger service.
   *
   * @var \Drupal\raven\Logger\Raven|null
   */
  protected $logger;

  /**
   * Time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface|null
   */
  protected $time;

  /**
   * The transaction for HTTP requests.
   *
   * @var \Sentry\Tracing\Transaction|null
   */
  public $transaction;

  /**
   * Constructs the request subscriber.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory object.
   * @param \Drupal\raven\Logger\Raven $logger
   *   The logger service.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   */
  public function __construct(ConfigFactoryInterface $config_factory = NULL, Raven $logger = NULL, TimeInterface $time = NULL) {
    $this->configFactory = $config_factory;
    $this->logger = $logger;
    $this->time = $time;
  }

  /**
   * Starts a transaction if performance tracing is enabled.
   *
   * @todo In Drupal 9+ the event should actually be RequestEvent.
   */
  public function onRequest(KernelEvent $event) {
    $method = method_exists($event, 'isMainRequest') ? 'isMainRequest' : 'isMasterRequest';
    if (!$event
      ->{$method}() || !$this->configFactory) {
      return;
    }
    $config = $this->configFactory
      ->get('raven.settings');
    if (!$config
      ->get('request_tracing') || !$this->logger || !$this->logger
      ->getClient()) {
      return;
    }
    $request = $event
      ->getRequest();
    $sentryTraceHeader = $request->headers
      ->get('sentry-trace');
    $transactionContext = $sentryTraceHeader && method_exists(TransactionContext::class, 'fromSentryTrace') ? TransactionContext::fromSentryTrace($sentryTraceHeader) : new TransactionContext();
    $transactionContext
      ->setName($request
      ->getMethod() . ' ' . $request
      ->getUri());
    $transactionContext
      ->setOp('http.server');
    $transactionContext
      ->setTags([
      'http.method' => $request
        ->getMethod(),
      'http.url' => $request
        ->getUri(),
    ]);
    $transactionContext
      ->setStartTimestamp($this->time
      ->getRequestMicroTime());
    $this->transaction = \Sentry\startTransaction($transactionContext);
    SentrySdk::getCurrentHub()
      ->setSpan($this->transaction);
    if ($config
      ->get('database_tracing')) {
      foreach (Database::getAllConnectionInfo() as $key => $info) {
        Database::startLog('raven', $key);
      }
    }
  }

  /**
   * Performance tracing.
   *
   * @todo In Drupal 9+ the event should actually be TerminateEvent.
   */
  public function onTerminate(KernelEvent $event) {
    if (!$this->transaction) {
      return;
    }
    if (method_exists($event, 'getResponse')) {
      $this->transaction
        ->setHttpStatus($event
        ->getResponse()
        ->getStatusCode());
    }
    if ($this->configFactory
      ->get('raven.settings')
      ->get('database_tracing')) {
      $this
        ->collectDatabaseLog();
    }
    $this->transaction
      ->finish();
  }

  /**
   * If database was initialized, create a span for each logged query.
   */
  public function collectDatabaseLog() {
    if (!$this->transaction || !$this->container || !$this->container
      ->initialized('database')) {
      return;
    }
    $connections = [];
    foreach (Database::getAllConnectionInfo() as $key => $info) {
      try {
        $database = Database::getConnection('default', $key);
        if ($logger = $database
          ->getLogger()) {
          $connections[$key] = $logger
            ->get('raven');
        }
      } catch (\Exception $e) {

        // Could not connect.
      }
    }
    foreach ($connections as $key => $queries) {
      foreach ($queries as $query) {
        if (empty($query['start'])) {

          // Older versions of Drupal do not record query start time.
          return;
        }
        $context = new SpanContext();
        $context
          ->setOp('sql.query');
        $context
          ->setDescription($query['query']);
        $context
          ->setTags([
          'database' => $key,
        ]);
        $context
          ->setStartTimestamp($query['start']);
        $context
          ->setEndTimestamp($query['start'] + $query['time']);
        $this->transaction
          ->startChild($context);
      }
    }
  }

  /**
   * Registers the methods in this class that should be listeners.
   *
   * @return array
   *   An array of event listener definitions.
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::REQUEST][] = [
      'onRequest',
      222,
    ];
    $events[KernelEvents::TERMINATE][] = [
      'onTerminate',
      222,
    ];
    return $events;
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return [
      'getTraceparent',
    ];
  }

  /**
   * Callback for returning the Sentry trace string as renderable array.
   */
  public function getTraceParent() {
    $markup = '';
    if (class_exists(SentrySdk::class)) {
      if ($span = SentrySdk::getCurrentHub()
        ->getSpan()) {
        $markup = $span
          ->toTraceparent();
      }
    }
    return [
      '#markup' => $markup,
    ];
  }

}

Classes

Namesort descending Description
RequestSubscriber Initializes Raven logger so Sentry functions can be called.