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?
You could write a Service class that handles and simplifies the syntax for starting and stopping Sentry transactions and spans.
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());
}
}
}
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');
}
}
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/"
}
},
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');
}