phplaravelsentry

Make adding custom instrumentation in Sentry less verbose


I'm playing around with instrumentation using Sentry and I'm so discouraged by how many lines of code I have to add everywhere.

According to the Sentry docs, I have to add all these lines every time I want to measure something:

        $sentryTransactionContext = (new TransactionContext('Something that needs measuring'));
        $sentryTransactionContext->setOp('http.server');
        $sentryTransaction = startTransaction($sentryTransactionContext);
        SentrySdk::getCurrentHub()->setSpan($sentryTransaction);
        $spanContext = (new SpanContext());
        $spanContext->setOp('something.that.needs.measuring');
        $span1 = $sentryTransaction->startChild($spanContext);
        \Sentry\SentrySdk::getCurrentHub()->setSpan($span1);

        // Do something that needs to be measured...

        $span1->finish();
        SentrySdk::getCurrentHub()->setSpan($sentryTransaction);
        $sentryTransaction->finish();

Is all that stuff really supposed to go in all my different Controller methods, or places where I need to measure how long a piece of code takes? It would be so much duplicate code.

Ideally, I would like to just do this:

public function create(HttpRequest $request)
{
        sentry_measure_start('slow.task');

        // Something slow that needs to be measured

        sentry_measure_stop('slow.task');
}

Is that possible?


Solution

  • You could write a Service class that handles and simplifies the syntax for starting and stopping Sentry transactions and spans.

    1. Create a app/Services/SentryMeasureService.php

      namespace App\Services;
      
      use Sentry\Tracing\TransactionContext;
      use Sentry\Tracing\SpanContext;
      use Sentry\SentrySdk;
      
      class SentryMeasure {
          private static $transactions = [];
          private static $spans = [];
      
          /**
           * Start measuring a transaction or span
           * 
           * @param string $name Unique identifier for the transaction or span
           * @param string $type Type of measurement (transaction or span)
           * @param string|null $parentName Parent transaction/span name (optional)
           */
          public static function start(string $name, string $type = 'transaction', ?string $parentName = null)
          {
              try {
                  if ($type === 'transaction') {
                      // Create and start a new transaction
                      $transactionContext = new \Sentry\Tracing\TransactionContext($name);
                      $transactionContext->setOp('http.server');
                      $transaction = \Sentry\startTransaction($transactionContext);
      
                      \Sentry\SentrySdk::getCurrentHub()->setSpan($transaction);
      
                      self::$transactions[$name] = $transaction;
                  } elseif ($type === 'span') {
                      if (!isset(self::$transactions[$parentName])) {
                          throw new \Exception("Parent transaction '{$parentName}' not found");
                      }
      
                      $parentTransaction = self::$transactions[$parentName];
      
                      $spanContext = new \Sentry\Tracing\SpanContext();
                      $spanContext->setOp($name);
                      $span = $parentTransaction->startChild($spanContext);
      
                      \Sentry\SentrySdk::getCurrentHub()->setSpan($span);
      
                      self::$spans[$name] = $span;
                  } else {
                      throw new \InvalidArgumentException("Invalid measurement type. Use 'transaction' or 'span'.");
                  }
              } catch (\Exception $e) {
                  error_log("Sentry measurement start error: " . $e->getMessage());
              }
          }
      
          /**
           * Stop measuring a transaction or span
           * 
           * @param string $name Unique identifier for the transaction or span to stop
           * @param string $type Type of measurement (transaction or span)
           */
          public static function stop(string $name, string $type = 'transaction')
          {
              try {
                  if ($type === 'transaction') {
                      if (isset(self::$transactions[$name])) {
                          $transaction = self::$transactions[$name];
                          $transaction->finish();
      
                          unset(self::$transactions[$name]);
                      }
                  } elseif ($type === 'span') {
                      if (isset(self::$spans[$name])) {
                          $span = self::$spans[$name];
                          $span->finish();
      
                          unset(self::$spans[$name]);
      
                          if (!empty(self::$transactions)) {
                              $lastTransactionName = array_key_last(self::$transactions);
                              $lastTransaction = self::$transactions[$lastTransactionName];
                              \Sentry\SentrySdk::getCurrentHub()->setSpan($lastTransaction);
                          }
                      }
                  } else {
                      throw new \InvalidArgumentException("Invalid measurement type. Use 'transaction' or 'span'.");
                  }
              } catch (\Exception $e) {
                  error_log("Sentry measurement stop error: " . $e->getMessage());
              }
          }
      }
      
    2. Create a app/helpers.php file to implement your helper functions

      use App\Services\SentryMeasureService;
      
      if (!function_exists('sentry_measure_start')) {
          function sentry_measure_start(string $name, ?string $parentName = null)
          {
              SentryMeasureService::start($name, $parentName === null ? 'transaction' : 'span', $parentName);
          }
      }
      
      if (!function_exists('sentry_measure_stop')) {
          function sentry_measure_stop(string $name)
          {
              SentryMeasureService::stop($name, 'span');
              SentryMeasureService::stop($name, 'transaction');
          }
      }
      
    3. To make the helper functions visible you need to modify your composer.json more specifically the autoload key. Add a files array

      inside autoload.

      "autoload": {
          "files": [
              "app/helpers.php"
          ],
          "classmap": [
              "database/seeds",
              "database/factories"
          ],
          "psr-4": {
              "App\\": "app/"
          }
      },
      
    4. Once you added the file you need to dump the autloader

      composer dump-autoload
      

    Now you should be able to use the functions the way you suggested:

    public function create(Request $request)
    {
        sentry_measure_start('slow.task');
    
        sleep(2); // example of a slow operation
    
        sentry_measure_stop('slow.task');
    }