diff --git a/composer.json b/composer.json index 1dd326c0b..38708cb61 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "psr/http-factory-implementation": "*", "psy/psysh": "^0.10.0 || ^0.11.0", "ramsey/uuid": "^4.7", - "sentry/sentry": "^4.15.0", + "sentry/sentry": "^4.16.0", "symfony/console": "^5.3 || ^6.0 || ^7.0", "symfony/http-foundation": "^5.3 || ^6.0 || ^7.0", "symfony/polyfill-php84": "^1.33", diff --git a/src/sentry/composer.json b/src/sentry/composer.json index 0a4fe819f..d797f834c 100644 --- a/src/sentry/composer.json +++ b/src/sentry/composer.json @@ -33,7 +33,7 @@ "hyperf/http-server": "~3.1.0", "hyperf/support": "~3.1.0", "hyperf/tappable": "~3.1.0", - "sentry/sentry": "^4.15.0", + "sentry/sentry": "^4.16.0", "symfony/polyfill-php85": "^1.33" }, "suggest": { diff --git a/src/sentry/src/Aspect/CoroutineAspect.php b/src/sentry/src/Aspect/CoroutineAspect.php index 2eb5f76e4..da7c7cc18 100644 --- a/src/sentry/src/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Aspect/CoroutineAspect.php @@ -29,6 +29,11 @@ class CoroutineAspect extends AbstractAspect \Psr\Http\Message\ServerRequestInterface::class, ]; + public function __construct() + { + $this->priority = PHP_INT_MAX - 1; + } + public function process(ProceedingJoinPoint $proceedingJoinPoint) { $callable = $proceedingJoinPoint->arguments['keys']['callable']; @@ -40,7 +45,7 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $current = Co::getContextFor(); foreach ($keys as $key) { - if (isset($from[$key])) { + if (isset($from[$key]) && ! isset($current[$key])) { $current[$key] = $from[$key]; } } diff --git a/src/sentry/src/Tracing/Aspect/AmqpProducerAspect.php b/src/sentry/src/Tracing/Aspect/AmqpProducerAspect.php index 54b8ab647..213162740 100644 --- a/src/sentry/src/Tracing/Aspect/AmqpProducerAspect.php +++ b/src/sentry/src/Tracing/Aspect/AmqpProducerAspect.php @@ -17,12 +17,13 @@ use FriendsOfHyperf\Sentry\Util\Carrier; use Hyperf\Amqp\Annotation\Producer; use Hyperf\Amqp\Message\ProducerMessage; +use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Annotation\AnnotationCollector; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use PhpAmqpLib\Wire\AMQPTable; - -use function Hyperf\Tappable\tap; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; /** * @property array{application_headers?:AMQPTable} $properties @@ -60,16 +61,6 @@ protected function handleProduceMessage(ProceedingJoinPoint $proceedingJoinPoint return $proceedingJoinPoint->process(); } - $span = $this->startSpan( - op: 'queue.publish', - description: sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName), - origin: 'auto.amqp' - ); - - if (! $span) { - return $proceedingJoinPoint->process(); - } - $routingKey = $producerMessage->getRoutingKey(); $exchange = $producerMessage->getExchange(); $poolName = $producerMessage->getPoolName(); @@ -87,30 +78,42 @@ protected function handleProduceMessage(ProceedingJoinPoint $proceedingJoinPoint $messageId = uniqid('amqp_', true); $destinationName = implode(', ', (array) $routingKey); $bodySize = strlen($producerMessage->payload()); - $span->setData([ - 'messaging.system' => 'amqp', - 'messaging.operation' => 'publish', - 'messaging.message.id' => $messageId, - 'messaging.message.body.size' => $bodySize, - 'messaging.destination.name' => $destinationName, - // for amqp - 'messaging.amqp.message.type' => $producerMessage->getTypeString(), - 'messaging.amqp.message.routing_key' => $routingKey, - 'messaging.amqp.message.exchange' => $exchange, - 'messaging.amqp.message.pool_name' => $poolName, - ]); - $carrier = Carrier::fromSpan($span)->with([ - 'publish_time' => microtime(true), - 'message_id' => $messageId, - 'destination_name' => $destinationName, - 'body_size' => $bodySize, - ]); - (function () use ($carrier) { - $this->properties['application_headers'] ??= new AMQPTable(); - $this->properties['application_headers']->set(Constants::TRACE_CARRIER, $carrier->toJson()); - })->call($producerMessage); - - return tap($proceedingJoinPoint->process(), fn () => $span->finish()); + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint, $producerMessage, $messageId, $destinationName, $bodySize) { + $span = $scope->getSpan(); + if ($span) { + $carrier = Carrier::fromSpan($span)->with([ + 'publish_time' => microtime(true), + 'message_id' => $messageId, + 'destination_name' => $destinationName, + 'body_size' => $bodySize, + ]); + (function () use ($carrier) { + $this->properties['application_headers'] ??= new AMQPTable(); + $this->properties['application_headers']->set(Constants::TRACE_CARRIER, $carrier->toJson()); + })->call($producerMessage); + } + + return $proceedingJoinPoint->process(); + }, + SpanContext::make() + ->setOp('queue.publish') + ->setDescription(sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName)) + ->setOrigin('auto.amqp') + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'messaging.system' => 'amqp', + 'messaging.operation' => 'publish', + 'messaging.message.id' => $messageId, + 'messaging.message.body.size' => $bodySize, + 'messaging.destination.name' => $destinationName, + // for amqp + 'messaging.amqp.message.type' => $producerMessage->getTypeString(), + 'messaging.amqp.message.routing_key' => $routingKey, + 'messaging.amqp.message.exchange' => $exchange, + 'messaging.amqp.message.pool_name' => $poolName, + ]) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php b/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php index 5f3e6a5a4..7b38fcf1c 100644 --- a/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php +++ b/src/sentry/src/Tracing/Aspect/AsyncQueueJobMessageAspect.php @@ -17,8 +17,11 @@ use FriendsOfHyperf\Sentry\Util\Carrier; use Hyperf\AsyncQueue\Driver\RedisDriver; use Hyperf\Context\Context; +use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; use function Hyperf\Support\with; @@ -73,46 +76,45 @@ public function handlePush(ProceedingJoinPoint $proceedingJoinPoint) { /** @var \Hyperf\AsyncQueue\JobInterface $job */ $job = $proceedingJoinPoint->arguments['keys']['job'] ?? null; - $span = $this->startSpan( - op: 'queue.publish', - description: $job::class, - origin: 'auto.queue' - ); - if (! $span) { - return $proceedingJoinPoint->process(); + /** @var \Hyperf\AsyncQueue\Driver\Driver $driver */ + $driver = $proceedingJoinPoint->getInstance(); + $messageId = method_exists($job, 'getId') ? $job->getId() : uniqid('async_queue_', true); + $destinationName = Context::get('sentry.messaging.destination.name', 'default'); + $bodySize = (fn ($job) => strlen($this->packer->pack($job)))->call($driver, $job); + $data = [ + 'coroutine.id' => Coroutine::id(), + 'messaging.system' => 'async_queue', + 'messaging.operation' => 'publish', + 'messaging.message.id' => $messageId, + 'messaging.message.body.size' => $bodySize, + 'messaging.destination.name' => $destinationName, + ]; + + if ($driver instanceof RedisDriver) { + $data = array_merge($data, $this->buildSpanDataOfRedisDriver($driver)); } - try { - /** @var \Hyperf\AsyncQueue\Driver\Driver $driver */ - $driver = $proceedingJoinPoint->getInstance(); - $messageId = method_exists($job, 'getId') ? $job->getId() : uniqid('async_queue_', true); - $destinationName = Context::get('sentry.messaging.destination.name', 'default'); - $bodySize = (fn ($job) => strlen($this->packer->pack($job)))->call($driver, $job); - $span->setData([ - 'messaging.system' => 'async_queue', - 'messaging.operation' => 'publish', - 'messaging.message.id' => $messageId, - 'messaging.message.body.size' => $bodySize, - 'messaging.destination.name' => $destinationName, - ]); - if ($driver instanceof RedisDriver) { - $span->setData($this->buildSpanDataOfRedisDriver($driver)); - } - - $carrier = Carrier::fromSpan($span)->with([ - 'publish_time' => microtime(true), - 'message_id' => $messageId, - 'destination_name' => $destinationName, - 'body_size' => $bodySize, - ]); - - Context::set(Constants::TRACE_CARRIER, $carrier); - - return $proceedingJoinPoint->process(); - } finally { - $span->finish(); - } + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint, $messageId, $destinationName, $bodySize) { + $span = $scope->getSpan(); + $carrier = Carrier::fromSpan($span)->with([ + 'publish_time' => microtime(true), + 'message_id' => $messageId, + 'destination_name' => $destinationName, + 'body_size' => $bodySize, + ]); + + Context::set(Constants::TRACE_CARRIER, $carrier); + + return $proceedingJoinPoint->process(); + }, + SpanContext::make() + ->setOp('queue.publish') + ->setDescription($job::class) + ->setOrigin('auto.queue') + ->setData($data) + ); } protected function buildSpanDataOfRedisDriver(RedisDriver $driver): array diff --git a/src/sentry/src/Tracing/Aspect/CacheAspect.php b/src/sentry/src/Tracing/Aspect/CacheAspect.php index f44eaa449..217b266af 100644 --- a/src/sentry/src/Tracing/Aspect/CacheAspect.php +++ b/src/sentry/src/Tracing/Aspect/CacheAspect.php @@ -13,9 +13,11 @@ use FriendsOfHyperf\Sentry\Switcher; use FriendsOfHyperf\Sentry\Tracing\SpanStarter; +use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; -use Sentry\SentrySdk; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; use function Hyperf\Tappable\tap; @@ -47,63 +49,60 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - $parent = SentrySdk::getCurrentHub()->getSpan(); + $method = $proceedingJoinPoint->methodName; + $op = match ($method) { + 'set', 'setMultiple' => 'cache.put', + 'get', 'fetch', 'getMultiple' => 'cache.get', + 'delete', 'deleteMultiple' => 'cache.remove', + 'clear' => 'cache.flush', + default => 'cache', + }; - try { - $method = $proceedingJoinPoint->methodName; - $op = match ($method) { - 'set', 'setMultiple' => 'cache.put', - 'get', 'fetch', 'getMultiple' => 'cache.get', - 'delete', 'deleteMultiple' => 'cache.remove', - 'clear' => 'cache.flush', - default => 'cache', - }; + $arguments = $proceedingJoinPoint->arguments['keys'] ?? []; - $arguments = $proceedingJoinPoint->arguments['keys'] ?? []; + /** @var string[] $keys */ + $keys = match ($method) { + 'set', 'get', 'delete' => [$arguments['key'] ?? 'unknown'], + 'setMultiple' => array_keys($arguments['values'] ?? []), + 'getMultiple', 'deleteMultiple' => $arguments['keys'] ?? [], + default => [], + }; - /** @var string[] $keys */ - $keys = match ($method) { - 'set', 'get', 'delete' => [$arguments['key'] ?? 'unknown'], - 'setMultiple' => array_keys($arguments['values'] ?? []), - 'getMultiple', 'deleteMultiple' => $arguments['keys'] ?? [], - default => [], - }; - - $span = $this->startSpan( - op: $op, - description: implode(', ', $keys), - origin: 'auto.cache', - asParent: true - )?->setData([ - 'cache.key' => $keys, - 'cache.ttl' => $arguments['ttl'] ?? null, - 'item_size' => match (true) { - isset($arguments['value']) => strlen(json_encode($arguments['value'])), - isset($arguments['values']) && is_array($arguments['values']) => strlen(json_encode(array_values($arguments['values']))), - default => 0, - }, - ]); - - return tap($proceedingJoinPoint->process(), function ($result) use ($span, $method) { - $data = match ($method) { - 'get' => [ - 'cache.hit' => ! is_null($result), - 'cache.item_size' => strlen((string) json_encode($result)), - ], - 'fetch' => [ - 'cache.hit' => ($result[0] ?? false) !== false, - 'cache.item_size' => strlen((string) json_encode($result[1] ?? '')), - ], - 'getMultiple' => [ - 'cache.hit' => ! empty($result), - 'cache.item_size' => strlen((string) json_encode(array_values((array) $result))), - ], - default => [], - }; - $span?->setData($data)->finish(); - }); - } finally { - SentrySdk::getCurrentHub()->setSpan($parent); - } + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint, $method) { + return tap($proceedingJoinPoint->process(), function ($result) use ($method, $scope) { + $data = match ($method) { + 'get' => [ + 'cache.hit' => ! is_null($result), + 'cache.item_size' => strlen((string) json_encode($result)), + ], + 'fetch' => [ + 'cache.hit' => ($result[0] ?? false) !== false, + 'cache.item_size' => strlen((string) json_encode($result[1] ?? '')), + ], + 'getMultiple' => [ + 'cache.hit' => ! empty($result), + 'cache.item_size' => strlen((string) json_encode(array_values((array) $result))), + ], + default => [], + }; + $scope->getSpan()?->setData($data); + }); + }, + SpanContext::make() + ->setOp($op) + ->setDescription(implode(', ', $keys)) + ->setOrigin('auto.cache') + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'cache.key' => $keys, + 'cache.ttl' => $arguments['ttl'] ?? null, + 'item_size' => match (true) { + isset($arguments['value']) => strlen(json_encode($arguments['value'])), + isset($arguments['values']) && is_array($arguments['values']) => strlen(json_encode(array_values($arguments['values']))), + default => 0, + }, + ]) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php index 174be3b8c..18de02cab 100644 --- a/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoordinatorAspect.php @@ -17,10 +17,8 @@ use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; -use Sentry\Tracing\SpanStatus; -use Throwable; - -use function Hyperf\Support\class_basename; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; class CoordinatorAspect extends AbstractAspect { @@ -36,35 +34,22 @@ public function __construct(protected Switcher $switcher) public function process(ProceedingJoinPoint $proceedingJoinPoint) { - $data = [ - 'coroutine.id' => Coroutine::id(), - 'timeout' => $timeout = $proceedingJoinPoint->arguments['keys']['timeout'] ?? -1, - ]; - - $span = $this->startSpan( - op: sprintf('%s.%s', strtolower(class_basename($proceedingJoinPoint->className)), $proceedingJoinPoint->methodName), - description: sprintf('%s::%s(%s)', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName, $timeout), - origin: 'auto.coordinator', - )?->setData($data); - - try { + if (! $this->switcher->isTracingSpanEnabled('coordinator')) { return $proceedingJoinPoint->process(); - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData([ - 'exception.stack_trace' => (string) $exception, - ]); - } - throw $exception; - } finally { - $span?->finish(); } + + $timeout = $proceedingJoinPoint->arguments['keys']['timeout'] ?? -1; + + return $this->trace( + fn (Scope $scope) => $proceedingJoinPoint->process(), + SpanContext::make() + ->setOp('coordinator.yield') + ->setDescription(sprintf('%s::%s(%s)', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName, $timeout)) + ->setOrigin('auto.coordinator') + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'timeout' => $timeout, + ]) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php index c2b190d95..1b6219765 100644 --- a/src/sentry/src/Tracing/Aspect/CoroutineAspect.php +++ b/src/sentry/src/Tracing/Aspect/CoroutineAspect.php @@ -14,27 +14,33 @@ use FriendsOfHyperf\Sentry\Switcher; use FriendsOfHyperf\Sentry\Tracing\SpanStarter; use FriendsOfHyperf\Sentry\Util\CoroutineBacktraceHelper; -use Hyperf\Coroutine\Coroutine as Co; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; +use Hyperf\Engine\Coroutine as Co; use Sentry\SentrySdk; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; use Throwable; use function Hyperf\Coroutine\defer; +use function Sentry\continueTrace; class CoroutineAspect extends AbstractAspect { use SpanStarter; - public ?int $priority = PHP_INT_MAX; - public array $classes = [ 'Hyperf\Coroutine\Coroutine::create', ]; + protected array $keys = [ + SentrySdk::class, + \Psr\Http\Message\ServerRequestInterface::class, + ]; + public function __construct(protected Switcher $switcher) { + $this->priority = PHP_INT_MAX; } public function process(ProceedingJoinPoint $proceedingJoinPoint) @@ -48,61 +54,85 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $callingOnFunction = CoroutineBacktraceHelper::foundCallingOnFunction(); + // Only trace the top-level coroutine creation. if (! $callingOnFunction) { return $proceedingJoinPoint->process(); } - $callable = $proceedingJoinPoint->arguments['keys']['callable']; - $parent = $this->startSpan( - op: 'coroutine.create', - description: $callingOnFunction, - origin: 'auto.coroutine', - )?->setOrigin('auto.coroutine'); + // If there's no active transaction, skip tracing. + $transaction = SentrySdk::getCurrentHub()->getTransaction(); - if (! $parent) { + // If there's no active transaction, skip tracing. + if (! $transaction || ! $transaction->getSampled()) { return $proceedingJoinPoint->process(); } - $parent->setData(['coroutine.id' => Co::id()]); + // Start a span for the coroutine creation. + $parent = $transaction->startChild( + SpanContext::make() + ->setOp('coroutine.create') + ->setDescription($callingOnFunction) + ->setOrigin('auto.coroutine') + ->setData(['coroutine.id' => Co::id()]) + ); + SentrySdk::getCurrentHub()->setSpan($parent); + + $cid = Co::id(); + $keys = $this->keys; + $callable = $proceedingJoinPoint->arguments['keys']['callable']; - $proceedingJoinPoint->arguments['keys']['callable'] = function () use ($callable, $parent, $callingOnFunction) { - $transaction = $this->startCoroutineTransaction( - parent: $parent, - name: 'coroutine', - op: 'coroutine.execute', - description: $callingOnFunction, - origin: 'auto.coroutine', - )->setData(['coroutine.id' => Co::id()]); + // Transfer the Sentry context to the new coroutine. + $proceedingJoinPoint->arguments['keys']['callable'] = function () use ($callable, $parent, $callingOnFunction, $cid, $keys) { + $from = Co::getContextFor($cid); + $current = Co::getContextFor(); - defer(function () use ($transaction) { - SentrySdk::getCurrentHub()->setSpan($transaction); - $transaction->finish(); + foreach ($keys as $key) { + if (isset($from[$key]) && ! isset($current[$key])) { + $current[$key] = $from[$key]; + } + } + + $coTransaction = $this->startTransaction( + continueTrace($parent->toTraceparent(), $parent->toBaggage()) + ->setName('coroutine') + ->setOp('coroutine.execute') + ->setDescription($callingOnFunction) + ->setOrigin('auto.coroutine') + ->setData(['coroutine.id' => Co::id()]) + ); + + defer(function () use ($coTransaction) { + SentrySdk::getCurrentHub()->setSpan($coTransaction); + $coTransaction->finish(); }); try { $callable(); } catch (Throwable $exception) { - $transaction->setStatus(SpanStatus::internalError()) + $coTransaction->setStatus(SpanStatus::internalError()) ->setTags([ 'error' => 'true', 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), ]); if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData([ + $coTransaction->setData([ 'exception.stack_trace' => (string) $exception, ]); } throw $exception; - } finally { - // ... } }; - $parent->finish(); - - return $proceedingJoinPoint->process(); + try { + return $proceedingJoinPoint->process(); + } finally { + $parent->finish(); + SentrySdk::getCurrentHub()->setSpan($transaction); + } } } diff --git a/src/sentry/src/Tracing/Aspect/DbAspect.php b/src/sentry/src/Tracing/Aspect/DbAspect.php index e1bf16304..6730bf168 100644 --- a/src/sentry/src/Tracing/Aspect/DbAspect.php +++ b/src/sentry/src/Tracing/Aspect/DbAspect.php @@ -20,9 +20,8 @@ use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use Psr\Container\ContainerInterface; -use Sentry\SentrySdk; -use Sentry\Tracing\SpanStatus; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; /** * @property string $poolName @@ -47,10 +46,6 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - if (! SentrySdk::getCurrentHub()->getSpan()) { - return $proceedingJoinPoint->process(); - } - $arguments = $proceedingJoinPoint->arguments['keys']; $poolName = (fn () => $this->poolName)->call($proceedingJoinPoint->getInstance()); /** @var \Hyperf\Pool\Pool $pool */ @@ -69,13 +64,6 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $sqlParse = SqlParser::parse($sql); $table = $sqlParse['table']; $operation = $sqlParse['operation']; - - $span = $this->startSpan( - op: 'db.sql.query', - description: $sql, - origin: 'auto.db', - ); - $data = [ 'coroutine.id' => Coroutine::id(), 'db.system' => $driver, @@ -98,34 +86,21 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) } } - $span?->setData($data); - - try { - $result = $proceedingJoinPoint->process(); - if ($this->switcher->isTracingExtraTagEnabled('db.result')) { - $span?->setData([ - 'db.result' => json_encode($result, JSON_UNESCAPED_UNICODE), - ]); - } - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData([ - 'exception.stack_trace' => (string) $exception, - ]); - } - - throw $exception; - } finally { - $span?->finish(); - } - - return $result; + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint) { + $result = $proceedingJoinPoint->process(); + if ($this->switcher->isTracingExtraTagEnabled('db.result')) { + $scope->getSpan()?->setData([ + 'db.result' => json_encode($result, JSON_UNESCAPED_UNICODE), + ]); + } + return $result; + }, + SpanContext::make() + ->setOp('db.sql.query') + ->setDescription($sql) + ->setOrigin('auto.db') + ->setData($data) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php b/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php index d36cf26ad..473a529e8 100644 --- a/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php +++ b/src/sentry/src/Tracing/Aspect/ElasticsearchAspect.php @@ -16,8 +16,8 @@ use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; -use Sentry\Tracing\SpanStatus; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; class ElasticsearchAspect extends AbstractAspect { @@ -62,47 +62,31 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - $span = $this->startSpan( - op: 'db.elasticsearch', - description: sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName), - origin: 'auto.elasticsearch', - )?->setData([ - 'coroutine.id' => Coroutine::id(), - 'db.system' => 'elasticsearch', - 'db.operation.name' => $proceedingJoinPoint->methodName, - 'http.request.method' => '', // TODO - 'url.full' => '', // TODO - 'server.host' => '', // TODO - 'server.port' => '', // TODO - 'arguments' => json_encode($proceedingJoinPoint->arguments['keys'], JSON_UNESCAPED_UNICODE), - ]); - - try { - $result = $proceedingJoinPoint->process(); - if ($this->switcher->isTracingExtraTagEnabled('elasticsearch.result')) { - $span?->setData([ - 'elasticsearch.result' => json_encode($result, JSON_UNESCAPED_UNICODE), - ]); - } - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData([ - 'exception.stack_trace' => (string) $exception, - ]); - } - - throw $exception; - } finally { - $span?->finish(); - } - - return $result; + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint) { + $result = $proceedingJoinPoint->process(); + if ($this->switcher->isTracingExtraTagEnabled('elasticsearch.result')) { + $scope->getSpan()?->setData([ + 'elasticsearch.result' => (string) json_encode($result, JSON_UNESCAPED_UNICODE), + ]); + } + return $result; + }, + SpanContext::make() + ->setOp('db.elasticsearch') + ->setDescription(sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName)) + ->setOrigin('auto.elasticsearch') + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'db.system' => 'elasticsearch', + 'db.operation.name' => $proceedingJoinPoint->methodName, + 'arguments' => (string) json_encode($proceedingJoinPoint->arguments['keys'], JSON_UNESCAPED_UNICODE), + // TODO + // 'http.request.method' => '', + // 'url.full' => '', + // 'server.host' => '', + // 'server.port' => '', + ]) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/FilesystemAspect.php b/src/sentry/src/Tracing/Aspect/FilesystemAspect.php index af1f673bd..8975553c8 100644 --- a/src/sentry/src/Tracing/Aspect/FilesystemAspect.php +++ b/src/sentry/src/Tracing/Aspect/FilesystemAspect.php @@ -13,10 +13,11 @@ use FriendsOfHyperf\Sentry\Aspect\FilesystemAspect as BaseFilesystemAspect; use FriendsOfHyperf\Sentry\Tracing\SpanStarter; +use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\ProceedingJoinPoint; use Override; -use Sentry\Tracing\SpanStatus; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; class FilesystemAspect extends BaseFilesystemAspect { @@ -31,30 +32,13 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) [$op, $description, $data] = $this->getSentryMetadata($proceedingJoinPoint); - $span = $this->startSpan( - op: $op, - description: $description, - origin: 'auto.filesystem', - )?->setData($data); - - try { - return $proceedingJoinPoint->process(); - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData([ - 'exception.stack_trace' => (string) $exception, - ]); - } - throw $exception; - } finally { - $span?->finish(); - } + return $this->trace( + fn (Scope $scope) => $proceedingJoinPoint->process(), + SpanContext::make() + ->setOp($op) + ->setDescription($description) + ->setOrigin('auto.filesystem') + ->setData(['coroutine.id' => Coroutine::id()] + $data) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/GrpcAspect.php b/src/sentry/src/Tracing/Aspect/GrpcAspect.php index 29e5b17f0..ca00fd5a2 100644 --- a/src/sentry/src/Tracing/Aspect/GrpcAspect.php +++ b/src/sentry/src/Tracing/Aspect/GrpcAspect.php @@ -18,9 +18,8 @@ use Hyperf\Di\Aop\ProceedingJoinPoint; use Hyperf\GrpcClient\BaseClient; use Sentry\SentrySdk; -use Sentry\Tracing\SpanStatus; -use Swoole\Http2\Response as Http2Response; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; class GrpcAspect extends AbstractAspect { @@ -43,9 +42,9 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $method = $proceedingJoinPoint->arguments['keys']['method']; $options = $proceedingJoinPoint->arguments['keys']['options']; $data = [ + 'coroutine.id' => Coroutine::id(), 'grpc.method' => $method, 'grpc.options' => $options, - 'coroutine.id' => Coroutine::id(), ]; $parent = SentrySdk::getCurrentHub()->getSpan(); @@ -65,49 +64,13 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) // Inject tracing headers $proceedingJoinPoint->arguments['keys']['options'] = $options; - // Start gRPC client span - $span = $this->startSpan( - op: 'grpc.client', - description: $method, - origin: 'auto.grpc', - )?->setData($data); - - try { - $result = $proceedingJoinPoint->process(); - - [$message, $code, $response] = $result; - - if ($response instanceof Http2Response) { - $span?->setData([ - 'response.status' => $code, - 'response.reason' => $message, - 'response.headers' => $response->headers, - ]); - if ($this->switcher->isTracingExtraTagEnabled('response.body')) { - $span?->setData([ - 'response.body' => $response->data, - ]); - } - } - - return $result; - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData([ - 'exception.stack_trace' => (string) $exception, - ]); - } - - throw $exception; - } finally { - $span?->finish(); - } + return $this->trace( + fn (Scope $scope) => $proceedingJoinPoint->process(), + SpanContext::make() + ->setOp('grpc.client') + ->setDescription($method) + ->setOrigin('auto.grpc') + ->setData($data) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php index f75c35627..6787cd672 100644 --- a/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php +++ b/src/sentry/src/Tracing/Aspect/GuzzleHttpClientAspect.php @@ -22,6 +22,8 @@ use Hyperf\Di\Aop\ProceedingJoinPoint; use Psr\Container\ContainerInterface; use Psr\Http\Message\RequestInterface; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; use Throwable; @@ -45,10 +47,7 @@ public function __construct( public function process(ProceedingJoinPoint $proceedingJoinPoint) { - if ( - ! $this->switcher->isTracingSpanEnabled('guzzle') - || Context::get(RpcAspect::SPAN) // If the parent span is not exists or the parent span is belongs to rpc, then skip. - ) { + if (! $this->switcher->isTracingSpanEnabled('guzzle')) { return $proceedingJoinPoint->process(); } @@ -70,117 +69,123 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) return $proceedingJoinPoint->process(); } - // Inject trace context && Start span - $span = $this->startSpan( - op: 'http.client', - description: $request->getMethod() . ' ' . (string) $request->getUri(), - origin: 'auto.http.client', - ); + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint, $options, $guzzleConfig) { + $span = $scope->getSpan(); - if (! $span) { - return $proceedingJoinPoint->process(); - } + if (! $span) { + return $proceedingJoinPoint->process(); + } - // Inject trace context - $options['headers'] = array_replace($options['headers'] ?? [], [ - 'sentry-trace' => $span->toTraceparent(), - 'baggage' => $span->toBaggage(), - 'traceparent' => $span->toW3CTraceparent(), - ]); - - // Override the headers - $proceedingJoinPoint->arguments['keys']['options']['headers'] = $options['headers']; - $onStats = $options['on_stats'] ?? null; - - // Add or override the on_stats option to record the request duration. - $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($options, $guzzleConfig, $onStats, $span) { - $request = $stats->getRequest(); - $uri = $request->getUri(); - $span->setData([ - // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http - 'http.query' => $uri->getQuery(), - 'http.fragment' => $uri->getFragment(), - 'http.request.method' => $request->getMethod(), - 'http.request.body.size' => strlen($options['body'] ?? ''), - 'http.request.full_url' => (string) $request->getUri(), - 'http.request.path' => $request->getUri()->getPath(), - 'http.request.scheme' => $request->getUri()->getScheme(), - 'http.request.host' => $request->getUri()->getHost(), - 'http.request.port' => $request->getUri()->getPort(), - 'http.request.user_agent' => $request->getHeaderLine('User-Agent'), // updated key for consistency - 'http.request.headers' => $request->getHeaders(), - // Other - 'coroutine.id' => Coroutine::id(), - 'http.system' => 'guzzle', - 'http.guzzle.config' => $guzzleConfig, - 'http.guzzle.options' => $options ?? [], - 'duration' => $stats->getTransferTime() * 1000, // in milliseconds - ]); - - if ($response = $stats->getResponse()) { - $span->setData([ - 'http.response.status_code' => $response->getStatusCode(), - 'http.response.reason' => $response->getReasonPhrase(), - 'http.response.headers' => $response->getHeaders(), - 'http.response.body.size' => $response->getBody()->getSize() ?? 0, - 'http.response_content_length' => $response->getHeaderLine('Content-Length'), - 'http.decoded_response_content_length' => $response->getHeaderLine('X-Decoded-Content-Length'), - 'http.response_transfer_size' => $response->getHeaderLine('Content-Length'), + // Inject trace context + $options['headers'] = array_replace($options['headers'] ?? [], [ + 'sentry-trace' => $span->toTraceparent(), + 'baggage' => $span->toBaggage(), + 'traceparent' => $span->toW3CTraceparent(), ]); - if ($this->switcher->isTracingExtraTagEnabled('http.response.body.contents')) { - $isTextual = \preg_match( - '/^(text\/|application\/(json|xml|x-www-form-urlencoded))/i', - $response->getHeaderLine('Content-Type') - ) === 1; - $body = $response->getBody(); - - if ($isTextual && $body->isSeekable()) { - $pos = $body->tell(); - $span->setData([ - 'http.response.body.contents' => \GuzzleHttp\Psr7\Utils::copyToString($body, 8192), // 8KB 上限 - ]); - $body->seek($pos); - } else { - $span->setData(['http.response.body.contents' => '[binary omitted]']); - } - } - - $span->setStatus(SpanStatus::createFromHttpStatusCode($response->getStatusCode())); + // Override the headers + $proceedingJoinPoint->arguments['keys']['options']['headers'] = $options['headers']; + $onStats = $options['on_stats'] ?? null; - if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) { - $span->setTags([ - 'error' => 'true', - 'http.response.reason' => $response->getReasonPhrase(), - ]); - } - } - - if ($stats->getHandlerErrorData()) { - $exception = $stats->getHandlerErrorData() instanceof Throwable - ? $stats->getHandlerErrorData() - : new Error(); - $span->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + // Add or override the on_stats option to record the request duration. + $proceedingJoinPoint->arguments['keys']['options']['on_stats'] = function (TransferStats $stats) use ($options, $guzzleConfig, $onStats, $span) { + $request = $stats->getRequest(); + $uri = $request->getUri(); $span->setData([ - 'exception.message' => $exception->getMessage(), - 'exception.stack_trace' => (string) $exception, + // See: https://develop.sentry.dev/sdk/performance/span-data-conventions/#http + 'http.query' => $uri->getQuery(), + 'http.fragment' => $uri->getFragment(), + 'http.request.method' => $request->getMethod(), + 'http.request.body.size' => strlen($options['body'] ?? ''), + 'http.request.full_url' => (string) $request->getUri(), + 'http.request.path' => $request->getUri()->getPath(), + 'http.request.scheme' => $request->getUri()->getScheme(), + 'http.request.host' => $request->getUri()->getHost(), + 'http.request.port' => $request->getUri()->getPort(), + 'http.request.user_agent' => $request->getHeaderLine('User-Agent'), // updated key for consistency + 'http.request.headers' => $request->getHeaders(), + // Other + 'coroutine.id' => Coroutine::id(), + 'http.system' => 'guzzle', + 'http.guzzle.config' => $guzzleConfig, + 'http.guzzle.options' => $options ?? [], + 'duration' => $stats->getTransferTime() * 1000, // in milliseconds ]); - } - } - $span->finish(); + if ($response = $stats->getResponse()) { + $span->setData([ + 'http.response.status_code' => $response->getStatusCode(), + 'http.response.reason' => $response->getReasonPhrase(), + 'http.response.headers' => $response->getHeaders(), + 'http.response.body.size' => $response->getBody()->getSize() ?? 0, + 'http.response_content_length' => $response->getHeaderLine('Content-Length'), + 'http.decoded_response_content_length' => $response->getHeaderLine('X-Decoded-Content-Length'), + 'http.response_transfer_size' => $response->getHeaderLine('Content-Length'), + ]); - if (is_callable($onStats)) { - ($onStats)($stats); - } - }; + if ($this->switcher->isTracingExtraTagEnabled('http.response.body.contents')) { + $isTextual = \preg_match( + '/^(text\/|application\/(json|xml|x-www-form-urlencoded))/i', + $response->getHeaderLine('Content-Type') + ) === 1; + $body = $response->getBody(); + + if ($isTextual && $body->isSeekable()) { + $pos = $body->tell(); + $span->setData([ + 'http.response.body.contents' => \GuzzleHttp\Psr7\Utils::copyToString($body, 8192), // 8KB 上限 + ]); + $body->seek($pos); + } else { + $span->setData(['http.response.body.contents' => '[binary omitted]']); + } + } + + $span->setStatus( + SpanStatus::createFromHttpStatusCode($response->getStatusCode()) + ); + + if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 600) { + $span->setTags([ + 'error' => 'true', + 'http.response.reason' => $response->getReasonPhrase(), + ]); + } + } + + if ($stats->getHandlerErrorData()) { + $exception = $stats->getHandlerErrorData() instanceof Throwable + ? $stats->getHandlerErrorData() + : new Error(); + $span->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $span->setData([ + 'exception.stack_trace' => (string) $exception, + ]); + } + } - return $proceedingJoinPoint->process(); + if (is_callable($onStats)) { + ($onStats)($stats); + } + }; + + return $proceedingJoinPoint->process(); + }, + SpanContext::make() + ->setOp('http.client') + ->setDescription($request->getMethod() . ' ' . (string) $request->getUri()) + ->setOrigin('auto.http.client') + ->setData(['coroutine.id' => Coroutine::id()]) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php b/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php index 7538e8139..7f573b451 100644 --- a/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php +++ b/src/sentry/src/Tracing/Aspect/KafkaProducerAspect.php @@ -15,12 +15,13 @@ use FriendsOfHyperf\Sentry\Switcher; use FriendsOfHyperf\Sentry\Tracing\SpanStarter; use FriendsOfHyperf\Sentry\Util\Carrier; +use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; use longlang\phpkafka\Producer\ProduceMessage; use longlang\phpkafka\Protocol\RecordBatch\RecordHeader; - -use function Hyperf\Tappable\tap; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; /** * @property array $headers @@ -55,71 +56,76 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) protected function sendAsync(ProceedingJoinPoint $proceedingJoinPoint) { - $span = $this->startSpan( - op: 'queue.publish', - description: sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName), - origin: 'auto.kafka' - ); - - if (! $span) { - return $proceedingJoinPoint->process(); - } - $messageId = uniqid('kafka_', true); $destinationName = $proceedingJoinPoint->arguments['keys']['topic'] ?? 'unknown'; $bodySize = strlen($proceedingJoinPoint->arguments['keys']['value'] ?? ''); - $span->setData([ - 'messaging.system' => 'kafka', - 'messaging.operation' => 'publish', - 'messaging.message.id' => $messageId, - 'messaging.message.body.size' => $bodySize, - 'messaging.destination.name' => $destinationName, - ]); - - $carrier = Carrier::fromSpan($span) - ->with([ - 'publish_time' => microtime(true), - 'message_id' => $messageId, - 'destination_name' => $destinationName, - 'body_size' => $bodySize, - ]); - $headers = $proceedingJoinPoint->arguments['keys']['headers'] ?? []; - $headers[] = (new RecordHeader()) - ->setHeaderKey(Constants::TRACE_CARRIER) - ->setValue($carrier->toJson()); - $proceedingJoinPoint->arguments['keys']['headers'] = $headers; - - return tap($proceedingJoinPoint->process(), fn () => $span->finish()); + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint, $messageId, $destinationName, $bodySize) { + $span = $scope->getSpan(); + if ($span) { + $carrier = Carrier::fromSpan($span) + ->with([ + 'publish_time' => microtime(true), + 'message_id' => $messageId, + 'destination_name' => $destinationName, + 'body_size' => $bodySize, + ]); + $headers = $proceedingJoinPoint->arguments['keys']['headers'] ?? []; + $headers[] = (new RecordHeader()) + ->setHeaderKey(Constants::TRACE_CARRIER) + ->setValue($carrier->toJson()); + $proceedingJoinPoint->arguments['keys']['headers'] = $headers; + } + + return $proceedingJoinPoint->process(); + }, + SpanContext::make() + ->setOp('queue.publish') + ->setDescription(sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName)) + ->setOrigin('auto.kafka') + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'messaging.system' => 'kafka', + 'messaging.operation' => 'publish', + 'messaging.message.id' => $messageId, + 'messaging.message.body.size' => $bodySize, + 'messaging.destination.name' => $destinationName, + ]) + ); } protected function sendBatchAsync(ProceedingJoinPoint $proceedingJoinPoint) { /** @var ProduceMessage[] $messages */ $messages = $proceedingJoinPoint->arguments['keys']['messages'] ?? []; - $span = $this->startSpan( - 'queue.publish', - sprintf('%s::%s', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName), - origin: 'auto.kafka' - ); - - if (! $span) { - return $proceedingJoinPoint->process(); - } - - foreach ($messages as $message) { - (function () use ($span) { - $carrier = Carrier::fromSpan($span) - ->with([ - 'publish_time' => microtime(true), - 'message_id' => uniqid('kafka_', true), - 'destination_name' => $this->getTopic(), - 'body_size' => strlen((string) $this->getValue()), - ]); - $this->headers[] = (new RecordHeader())->setHeaderKey(Constants::TRACE_CARRIER)->setValue($carrier->toJson()); - })->call($message); - } - return tap($proceedingJoinPoint->process(), fn () => $span->finish()); + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint, $messages) { + $span = $scope->getSpan(); + + if ($span) { + foreach ($messages as $message) { + (function () use ($span) { + $carrier = Carrier::fromSpan($span) + ->with([ + 'publish_time' => microtime(true), + 'message_id' => uniqid('kafka_', true), + 'destination_name' => $this->getTopic(), + 'body_size' => strlen((string) $this->getValue()), + ]); + $this->headers[] = (new RecordHeader())->setHeaderKey(Constants::TRACE_CARRIER)->setValue($carrier->toJson()); + })->call($message); + } + } + + return $proceedingJoinPoint->process(); + }, + SpanContext::make() + ->setOp('queue.publish') + ->setDescription(sprintf('%s::%s()', $proceedingJoinPoint->className, $proceedingJoinPoint->methodName)) + ->setOrigin('auto.kafka') + ->setData(['coroutine.id' => Coroutine::id()]) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/RedisAspect.php b/src/sentry/src/Tracing/Aspect/RedisAspect.php index b92b9ec8b..80d5d8a28 100644 --- a/src/sentry/src/Tracing/Aspect/RedisAspect.php +++ b/src/sentry/src/Tracing/Aspect/RedisAspect.php @@ -21,8 +21,10 @@ use Hyperf\Redis\Pool\PoolFactory; use Hyperf\Redis\Redis; use Psr\Container\ContainerInterface; -use Sentry\Tracing\SpanStatus; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; + +use function Hyperf\Tappable\tap; /** * @deprecated since v3.1, will be removed in v3.2. @@ -72,44 +74,26 @@ class_exists(CommandExecuted::class) 'db.redis.pool.using' => $pool->getCurrentConnections(), ]; - // rule: operation db.table - // $op = sprintf('%s %s', $arguments['name'], $arguments['arguments']['key'] ?? ''); - // $description = sprintf('%s::%s()', $proceedingJoinPoint->className, $arguments['name']); $key = $arguments['arguments'][0] ?? ''; $description = sprintf( '%s %s', strtoupper($arguments['name'] ?? ''), is_array($key) ? implode(',', $key) : $key ); - $span = $this->startSpan( - op: 'db.redis', - description: $description, - origin: 'auto.cache.redis', - )?->setData($data); - - try { - $result = $proceedingJoinPoint->process(); - - if ($this->switcher->isTracingExtraTagEnabled('redis.result')) { - $span?->setData(['redis.result' => $result]); - } - return $result; - } catch (Throwable $e) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $e::class, - 'exception.message' => $e->getMessage(), - 'exception.code' => (string) $e->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData(['exception.stack_trace' => (string) $e]); - } - - throw $e; - } finally { - $span?->finish(); - } + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint) { + return tap($proceedingJoinPoint->process(), function ($result) use ($scope) { + if ($this->switcher->isTracingExtraTagEnabled('redis.result')) { + $scope->getSpan()?->setData(['redis.result' => $result]); + } + }); + }, + SpanContext::make() + ->setOp('db.redis') + ->setDescription($description) + ->setOrigin('auto.cache.redis') + ->setData($data) + ); } } diff --git a/src/sentry/src/Tracing/Aspect/RpcAspect.php b/src/sentry/src/Tracing/Aspect/RpcAspect.php index 70932f4c6..a2526cb2a 100644 --- a/src/sentry/src/Tracing/Aspect/RpcAspect.php +++ b/src/sentry/src/Tracing/Aspect/RpcAspect.php @@ -24,9 +24,10 @@ use Hyperf\RpcClient; use Hyperf\Stringable\Str; use Psr\Container\ContainerInterface; -use Sentry\Tracing\Span; -use Sentry\Tracing\SpanStatus; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; + +use function Hyperf\Tappable\tap; /** * @property string $prototype @@ -35,9 +36,7 @@ class RpcAspect extends AbstractAspect { use SpanStarter; - public const SPAN = 'sentry.tracing.rpc.span'; - - protected const DATA = 'sentry.tracing.rpc.data'; + public const SPAN_CONTEXT = 'sentry.tracing.rpc.span_context'; public array $classes = [ RpcClient\AbstractServiceClient::class . '::__generateRpcPath', @@ -79,79 +78,52 @@ private function handleGenerateRpcPath(ProceedingJoinPoint $proceedingJoinPoint) default => 'rpc', }; - // $package.$service/$path - $op = sprintf('%s.%s/%s', $package, $service, $path); - $span = $this->startSpan( - op: $op, - description: $path, - origin: 'auto.rpc', - ); - - if (! $span) { - return $path; - } - - $data = [ - 'coroutine.id' => Coroutine::id(), - 'rpc.system' => $system, - 'rpc.method' => $proceedingJoinPoint->arguments['keys']['methodName'] ?? '', - 'rpc.service' => $service, - ]; - - $span->setData($data); - - Context::set(static::DATA, $data); // @deprecated since v3.1, will be removed in v3.2 - Context::set(static::SPAN, $span); - - if ($this->container->has(Rpc\Context::class)) { - $this->container->get(Rpc\Context::class) - ->set(Constants::TRACE_CARRIER, Carrier::fromSpan($span)->toJson()); - } + Context::set(static::SPAN_CONTEXT, SpanContext::make() + ->setOp(sprintf('%s.%s/%s', $package, $service, $path)) + ->setDescription($path) + ->setOrigin('auto.rpc') + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'rpc.system' => $system, + 'rpc.service' => $service, + 'rpc.method' => $proceedingJoinPoint->arguments['keys']['methodName'] ?? '', + ])); return $path; } private function handleSend(ProceedingJoinPoint $proceedingJoinPoint) { - // TODO - // 'server.address' => '', - // 'server.port' => '', - - /** @var null|Span $span */ - $span = Context::get(static::SPAN); + /** @var null|SpanContext $spanContext */ + $spanContext = Context::get(static::SPAN_CONTEXT); - $span?->setData((array) Context::get(static::DATA, [])); - - if ($this->container->has(Rpc\Context::class)) { - $span?->setData(['rpc.context' => $this->container->get(Rpc\Context::class)->getData()]); + if (! $spanContext) { + return $proceedingJoinPoint->process(); } try { - $result = $proceedingJoinPoint->process(); - - if ($this->switcher->isTracingExtraTagEnabled('rpc.result')) { - $span?->setData(['rpc.result' => $result]); - } - - return $result; - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData(['exception.stack_trace' => (string) $exception]); - } - - throw $exception; + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint) { + $span = $scope->getSpan(); + if ($span && $this->container->has(Rpc\Context::class)) { + // Inject the RPC context data into span. + $span->setData([ + 'rpc.context' => $this->container->get(Rpc\Context::class)->getData(), + ]); + // Inject the tracing carrier into RPC context. + $this->container->get(Rpc\Context::class) + ->set(Constants::TRACE_CARRIER, Carrier::fromSpan($span)->toJson()); + } + return tap($proceedingJoinPoint->process(), function ($result) use ($span) { + if ($span && $this->switcher->isTracingExtraTagEnabled('rpc.result')) { + $span->setData(['rpc.result' => $result]); + } + }); + }, + $spanContext + ); } finally { - $span?->finish(); - - Context::destroy(static::SPAN); - Context::destroy(static::DATA); + Context::destroy(static::SPAN_CONTEXT); } } } diff --git a/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php b/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php index ac367227e..02dd51ad4 100644 --- a/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php +++ b/src/sentry/src/Tracing/Aspect/TraceAnnotationAspect.php @@ -17,9 +17,10 @@ use Hyperf\Coroutine\Coroutine; use Hyperf\Di\Aop\AbstractAspect; use Hyperf\Di\Aop\ProceedingJoinPoint; -use Sentry\SentrySdk; -use Sentry\Tracing\SpanStatus; -use Throwable; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; + +use function Hyperf\Tappable\tap; class TraceAnnotationAspect extends AbstractAspect { @@ -38,15 +39,14 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $metadata = $proceedingJoinPoint->getAnnotationMetadata(); /** @var null|Trace $annotation */ $annotation = $metadata->method[Trace::class] ?? null; - $parent = SentrySdk::getCurrentHub()->getSpan(); - if (! $annotation || ! $parent || ! $parent->getSampled()) { + if (! $annotation) { return $proceedingJoinPoint->process(); } $data = ['coroutine.id' => Coroutine::id()]; - $methodName = $proceedingJoinPoint->methodName; + if (in_array($methodName, ['__call', '__callStatic'])) { $methodName = $proceedingJoinPoint->arguments['keys']['name'] ?? $proceedingJoinPoint->methodName; $data['annotation.arguments'] = $proceedingJoinPoint->arguments['keys']['arguments'] ?? $proceedingJoinPoint->arguments['keys']; @@ -54,41 +54,22 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint) $data['annotation.arguments'] = $proceedingJoinPoint->arguments['keys']; } - $span = $this->startSpan( - op: $annotation->op ?? 'method', - description: $annotation->description ?? sprintf( - '%s::%s()', - $proceedingJoinPoint->className, - $methodName - ), - asParent: true - )?->setData($data); - - try { - $result = $proceedingJoinPoint->process(); - - if ($this->switcher->isTracingExtraTagEnabled('annotation.result')) { - $span?->setData(['annotation.result' => $result]); - } - - return $result; - } catch (Throwable $exception) { - $span?->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span?->setData(['exception.stack_trace' => (string) $exception]); - } - throw $exception; - } finally { - $span?->finish(); - - // Restore parent span - SentrySdk::getCurrentHub()->setSpan($parent); - } + return $this->trace( + function (Scope $scope) use ($proceedingJoinPoint) { + return tap($proceedingJoinPoint->process(), function ($result) use ($scope) { + if ($this->switcher->isTracingExtraTagEnabled('annotation.result')) { + $scope->getSpan()?->setData(['annotation.result' => $result]); + } + }); + }, + SpanContext::make() + ->setOp($annotation->op ?? 'method') + ->setDescription($annotation->description ?? sprintf( + '%s::%s()', + $proceedingJoinPoint->className, + $methodName + )) + ->setData($data) + ); } } diff --git a/src/sentry/src/Tracing/Listener/EventHandleListener.php b/src/sentry/src/Tracing/Listener/EventHandleListener.php index d17541275..1d138063f 100644 --- a/src/sentry/src/Tracing/Listener/EventHandleListener.php +++ b/src/sentry/src/Tracing/Listener/EventHandleListener.php @@ -42,12 +42,16 @@ use PhpAmqpLib\Wire\AMQPTable; use Psr\Container\ContainerInterface; use Sentry\SentrySdk; +use Sentry\State\Scope; +use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; +use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; use Swow\Psr7\Message\ResponsePlusInterface; use Symfony\Component\Console\Command\Command as SymfonyCommand; use function Hyperf\Coroutine\defer; +use function Sentry\continueTrace; /** * @property int $exitCode @@ -161,14 +165,11 @@ public function process(object $event): void }; } - private function handleDbQueryExecuted(DbEvent\QueryExecuted $event): void + protected function handleDbQueryExecuted(DbEvent\QueryExecuted $event): void { if (! $this->switcher->isTracingSpanEnabled('sql_queries')) { return; } - if (! SentrySdk::getCurrentHub()->getSpan()) { - return; - } $data = [ 'coroutine.id' => Coroutine::id(), @@ -202,37 +203,41 @@ private function handleDbQueryExecuted(DbEvent\QueryExecuted $event): void $startTimestamp = microtime(true) - $event->time / 1000; - $this->startSpan( - op: 'db.sql.query', - description: $event->sql, - origin: 'auto.db' - )?->setData($data) - ->setStartTimestamp($startTimestamp) - ->finish($startTimestamp + $event->time / 1000); + $this->trace( + fn () => null, + SpanContext::make() + ->setOp('db.sql.query') + ->setDescription($event->sql) + ->setOrigin('auto.db') + ->setStartTimestamp($startTimestamp) + ->setData($data) + ->setEndTimestamp($startTimestamp + $event->time / 1000) + ); } - private function handleDbTransactionBeginning(DbEvent\TransactionBeginning $event): void + protected function handleDbTransactionBeginning(DbEvent\TransactionBeginning $event): void { if (! $this->switcher->isTracingSpanEnabled('sql_transactions')) { return; } - if (! SentrySdk::getCurrentHub()->getSpan()) { - return; - } - $this->startSpan( - op: 'db.transaction', - description: 'BEGIN', - origin: 'auto.db' - )?->setData([ - 'coroutine.id' => Coroutine::id(), - 'db.system' => $event->connection->getDriverName(), - 'db.name' => $event->connection->getDatabaseName(), - 'db.pool.name' => $event->connectionName, - ]); + $this->trace( + fn () => null, + SpanContext::make() + ->setOp('db.transaction') + ->setDescription('BEGIN') + ->setOrigin('auto.db') + ->setStartTimestamp(microtime(true)) + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'db.system' => $event->connection->getDriverName(), + 'db.name' => $event->connection->getDatabaseName(), + 'db.pool.name' => $event->connectionName, + ]) + ); } - private function handleDbTransactionCommitted(DbEvent\TransactionCommitted $event): void + protected function handleDbTransactionCommitted(DbEvent\TransactionCommitted $event): void { if (! $this->switcher->isTracingSpanEnabled('sql_transactions')) { return; @@ -246,7 +251,7 @@ private function handleDbTransactionCommitted(DbEvent\TransactionCommitted $even ->finish(); } - private function handleDbTransactionRolledBack(DbEvent\TransactionRolledBack $event): void + protected function handleDbTransactionRolledBack(DbEvent\TransactionRolledBack $event): void { if (! $this->switcher->isTracingSpanEnabled('sql_transactions')) { return; @@ -260,7 +265,7 @@ private function handleDbTransactionRolledBack(DbEvent\TransactionRolledBack $ev ->finish(); } - private function handleRequestReceived(HttpEvent\RequestReceived|RpcEvent\RequestReceived $event): void + protected function handleRequestReceived(HttpEvent\RequestReceived|RpcEvent\RequestReceived $event): void { if (! $this->switcher->isTracingEnabled('request')) { return; @@ -277,29 +282,14 @@ private function handleRequestReceived(HttpEvent\RequestReceived|RpcEvent\Reques $serverName = $dispatched->serverName ?? 'http'; $path = $request->getUri()->getPath(); $method = strtoupper($request->getMethod()); - [$route, $routeParams, $routeCallback] = $this->parseRoute($dispatched); - [$name, $source] = match (strtolower($this->source)) { 'custom' => [$routeCallback, TransactionSource::custom()], 'url' => [$path, TransactionSource::url()], default => [$route, TransactionSource::route()], }; - - $transaction = $this->startRequestTransaction( - request: $request, - name: $name, - op: sprintf('%s.server', $serverName), - description: sprintf('%s %s', $method, $path), - origin: 'auto.request', - source: $source, - ); - - if (! $transaction->getSampled()) { - return; - } - $data = [ + 'coroutine.id' => Coroutine::id(), 'url.scheme' => $request->getUri()->getScheme(), 'url.path' => $path, 'http.request.method' => $method, @@ -313,18 +303,35 @@ private function handleRequestReceived(HttpEvent\RequestReceived|RpcEvent\Reques $data['rpc.context'] = $this->container->get(RpcContext::class)->getData(); } - $transaction->setData($data); + $carrier = Carrier::fromRequest($request); + $transaction = $this->startTransaction( + continueTrace($carrier->getSentryTrace(), $carrier->getBaggage()) + ->setName($name) + ->setOp(sprintf('%s.server', $serverName)) + ->setDescription(description: sprintf('%s %s', $method, $path)) + ->setOrigin('auto.request') + ->setSource($source) + ->setData($data) + ); + + if (! $transaction->getSampled()) { + return; + } - $span = $this->startSpan( - op: 'request.received', - description: 'request.received', - origin: 'auto.request.received', - asParent: true + $span = $transaction->startChild( + SpanContext::make() + ->setOp('request.received') + ->setDescription('request.received') + ->setData(['coroutine.id' => Coroutine::id()]) + ->setStatus(SpanStatus::ok()) + ->setStartTimestamp(microtime(true)) ); + SentrySdk::getCurrentHub()->setSpan($span); + defer(function () use ($transaction, $span) { // Make sure the span is finished after the request is handled - $span?->finish(); + $span->finish(); // Make sure the transaction is finished after the request is handled SentrySdk::getCurrentHub()->setSpan($transaction); @@ -334,45 +341,44 @@ private function handleRequestReceived(HttpEvent\RequestReceived|RpcEvent\Reques }); } - private function handleRequestHandled(HttpEvent\RequestHandled|RpcEvent\RequestHandled $event): void + protected function handleRequestHandled(HttpEvent\RequestHandled|RpcEvent\RequestHandled $event): void { - $transaction = SentrySdk::getCurrentHub()->getTransaction(); + $span = SentrySdk::getCurrentHub()->getSpan(); - if ( - ! $transaction - || ! $transaction->getSampled() - || ! $traceId = (string) $transaction->getTraceId() - ) { + if (! $span) { return; } - if ($event instanceof RpcEvent\RequestHandled) { - if ($this->container->has(RpcContext::class)) { - $this->container->get(RpcContext::class)->set('sentry-trace-id', $traceId); - } + $traceId = (string) $span->getTraceId(); + if ($event instanceof RpcEvent\RequestHandled && $this->container->has(RpcContext::class)) { + $this->container->get(RpcContext::class)->set('sentry-trace-id', $traceId); } elseif ($event->response instanceof ResponsePlusInterface) { $event->response->setHeader('sentry-trace-id', $traceId); } - $transaction->setHttpStatus($event->response->getStatusCode()); + $span->setStatus( + SpanStatus::createFromHttpStatusCode($event->response->getStatusCode()) + ); if ($exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) + $span->setStatus(SpanStatus::internalError()) ->setTags([ 'error' => 'true', 'exception.class' => $exception::class, 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ 'exception.message' => $exception->getMessage(), ]); if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData([ + $span->setData([ 'exception.stack_trace' => (string) $exception, ]); } } } - private function handleCommandStarting(CommandEvent\BeforeHandle $event): void + protected function handleCommandStarting(CommandEvent\BeforeHandle $event): void { if ( ! $this->switcher->isTracingEnabled('command') @@ -383,16 +389,18 @@ private function handleCommandStarting(CommandEvent\BeforeHandle $event): void $command = $event->getCommand(); - $this->continueTrace( - name: $command->getName() ?: '', - op: 'console.command', - description: $command->getDescription(), - origin: 'auto.command', - source: TransactionSource::custom() + $this->startTransaction( + TransactionContext::make() + ->setName($command->getName() ?: '') + ->setOp('console.command') + ->setDescription($command->getDescription()) + ->setOrigin('auto.command') + ->setSource(TransactionSource::custom()) + ->setData(['coroutine.id' => Coroutine::id()]) ); } - private function handleCommandFinished(CommandEvent\AfterExecute $event): void + protected function handleCommandFinished(CommandEvent\AfterExecute $event): void { $transaction = SentrySdk::getCurrentHub()->getTransaction(); @@ -400,41 +408,45 @@ private function handleCommandFinished(CommandEvent\AfterExecute $event): void return; } - $command = $event->getCommand(); - $exitCode = (fn () => $this->exitCode ?? SymfonyCommand::SUCCESS)->call($command); - - $transaction->setData([ - 'command.arguments' => (fn () => $this->input->getArguments())->call($command), - 'command.options' => (fn () => $this->input->getOptions())->call($command), - ])->setTags([ - 'command.exit_code' => (string) $exitCode, - ]); - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData([ - 'exception.stack_trace' => (string) $exception, - ]); + try { + $command = $event->getCommand(); + $exitCode = (fn () => $this->exitCode ?? SymfonyCommand::SUCCESS)->call($command); + + $transaction->setData([ + 'command.arguments' => (fn () => $this->input->getArguments())->call($command), + 'command.options' => (fn () => $this->input->getOptions())->call($command), + ])->setTags([ + 'command.exit_code' => (string) $exitCode, + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $transaction->setData([ + 'exception.stack_trace' => (string) $exception, + ]); + } } - } - - $transaction->setStatus( - $exitCode == SymfonyCommand::SUCCESS ? SpanStatus::ok() : SpanStatus::internalError() - ); - SentrySdk::getCurrentHub()->setSpan($transaction); + $transaction->setStatus( + $exitCode == SymfonyCommand::SUCCESS ? SpanStatus::ok() : SpanStatus::internalError() + ); + } finally { + SentrySdk::getCurrentHub()->setSpan($transaction); - $transaction->finish(); + $transaction->finish(); + } } - private function handleRedisCommandExecuted(RedisEvent\CommandExecuted $event): void + protected function handleRedisCommandExecuted(RedisEvent\CommandExecuted $event): void { if (! $this->switcher->isTracingSpanEnabled('redis')) { return; @@ -444,52 +456,53 @@ private function handleRedisCommandExecuted(RedisEvent\CommandExecuted $event): $config = $this->config->get('redis.' . $event->connectionName, []); $redisStatement = (string) new RedisCommand($event->command, $event->parameters); - $span = $this->startSpan( - op: 'db.redis', - description: $redisStatement, - origin: 'auto.cache.redis', - ); - - if (! $span) { - return; - } + $this->trace( + function (Scope $scope) use ($event) { + if (! $span = $scope->getSpan()) { + return; + } - $span->setData([ - 'coroutine.id' => Coroutine::id(), - 'db.system' => 'redis', - 'db.statement' => $redisStatement, - 'db.redis.connection' => $event->connectionName, - 'db.redis.database_index' => $config['db'] ?? 0, - 'db.redis.parameters' => $event->parameters, - 'db.redis.pool.name' => $event->connectionName, - 'db.redis.pool.max' => $pool->getOption()->getMaxConnections(), - 'db.redis.pool.max_idle_time' => $pool->getOption()->getMaxIdleTime(), - 'db.redis.pool.idle' => $pool->getConnectionsInChannel(), - 'db.redis.pool.using' => $pool->getCurrentConnections(), - 'duration' => $event->time * 1000, - ]); - - if ($this->switcher->isTracingExtraTagEnabled('redis.result')) { - $span->setData(['db.redis.result' => $event->result]); - } - - if ($exception = $event->throwable) { - $span->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $span->setData(['exception.stack_trace' => (string) $exception]); - } - } + if ($this->switcher->isTracingExtraTagEnabled('redis.result')) { + $span->setData(['db.redis.result' => $event->result]); + } - $span->finish(); + if ($exception = $event->throwable) { + $span->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $span->setData(['exception.stack_trace' => (string) $exception]); + } + } + }, + SpanContext::make() + ->setOp('db.redis') + ->setDescription($redisStatement) + ->setData([ + 'coroutine.id' => Coroutine::id(), + 'db.system' => 'redis', + 'db.statement' => $redisStatement, + 'db.redis.connection' => $event->connectionName, + 'db.redis.database_index' => $config['db'] ?? 0, + 'db.redis.parameters' => $event->parameters, + 'db.redis.pool.name' => $event->connectionName, + 'db.redis.pool.max' => $pool->getOption()->getMaxConnections(), + 'db.redis.pool.max_idle_time' => $pool->getOption()->getMaxIdleTime(), + 'db.redis.pool.idle' => $pool->getConnectionsInChannel(), + 'db.redis.pool.using' => $pool->getCurrentConnections(), + 'duration' => $event->time * 1000, + ]) + ->setStartTimestamp(microtime(true) - $event->time / 1000) + ); } - private function handleCrontabTaskStarting(CrontabEvent\BeforeExecute $event): void + protected function handleCrontabTaskStarting(CrontabEvent\BeforeExecute $event): void { if (! $this->switcher->isTracingEnabled('crontab')) { return; @@ -497,16 +510,18 @@ private function handleCrontabTaskStarting(CrontabEvent\BeforeExecute $event): v $crontab = $event->crontab; - $this->continueTrace( - name: $crontab->getName() ?: '', - op: 'crontab.run', - description: $crontab->getMemo(), - origin: 'auto.crontab', - source: TransactionSource::task() + $this->startTransaction( + TransactionContext::make() + ->setName($crontab->getName() ?: '') + ->setOp('crontab.run') + ->setDescription($crontab->getMemo()) + ->setOrigin('auto.crontab') + ->setSource(TransactionSource::task()) + ->setData(['coroutine.id' => Coroutine::id()]) ); } - private function handleCrontabTaskFinished(CrontabEvent\FailToExecute|CrontabEvent\AfterExecute $event): void + protected function handleCrontabTaskFinished(CrontabEvent\FailToExecute|CrontabEvent\AfterExecute $event): void { $transaction = SentrySdk::getCurrentHub()->getTransaction(); @@ -514,33 +529,37 @@ private function handleCrontabTaskFinished(CrontabEvent\FailToExecute|CrontabEve return; } - $crontab = $event->crontab; - $transaction->setTags([ - 'crontab.rule' => $crontab->getRule(), - 'crontab.type' => $crontab->getType(), - 'crontab.options.is_single' => $crontab->isSingleton(), - 'crontab.options.is_on_one_server' => $crontab->isOnOneServer(), - ]); - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData(['exception.stack_trace' => (string) $exception]); + try { + $crontab = $event->crontab; + $transaction->setTags([ + 'crontab.rule' => $crontab->getRule(), + 'crontab.type' => $crontab->getType(), + 'crontab.options.is_single' => $crontab->isSingleton(), + 'crontab.options.is_on_one_server' => $crontab->isOnOneServer(), + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } } - } - - SentrySdk::getCurrentHub()->setSpan($transaction); + } finally { + SentrySdk::getCurrentHub()->setSpan($transaction); - $transaction->finish(); + $transaction->finish(); + } } - private function handleAmqpMessageProcessing(AmqpEvent\BeforeConsume $event): void + protected function handleAmqpMessageProcessing(AmqpEvent\BeforeConsume $event): void { if (! $this->switcher->isTracingEnabled('amqp')) { return; @@ -560,18 +579,18 @@ private function handleAmqpMessageProcessing(AmqpEvent\BeforeConsume $event): vo } } - $this->continueTrace( - sentryTrace: $carrier?->getSentryTrace() ?? '', - baggage: $carrier?->getBaggage() ?? '', - name: $message::class, - op: 'queue.process', - description: $message::class, - origin: 'auto.amqp', - source: TransactionSource::custom() + $this->startTransaction( + continueTrace($carrier?->getSentryTrace() ?? '', $carrier?->getBaggage() ?? '') + ->setName($message::class) + ->setOp('queue.process') + ->setDescription($message::class) + ->setOrigin('auto.amqp') + ->setSource(TransactionSource::custom()) + ->setData(['coroutine.id' => Coroutine::id()]) ); } - private function handleAmqpMessageProcessed(AmqpEvent\AfterConsume|AmqpEvent\FailToConsume $event): void + protected function handleAmqpMessageProcessed(AmqpEvent\AfterConsume|AmqpEvent\FailToConsume $event): void { $transaction = SentrySdk::getCurrentHub()->getTransaction(); @@ -579,46 +598,50 @@ private function handleAmqpMessageProcessed(AmqpEvent\AfterConsume|AmqpEvent\Fai return; } - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER); - - /** @var ConsumerMessage $message */ - $message = $event->getMessage(); - $transaction->setData([ - 'messaging.system' => 'amqp', - 'messaging.operation' => 'process', - 'messaging.message.id' => $carrier?->get('message_id'), - 'messaging.message.body.size' => $carrier?->get('body_size'), - 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, - 'messaging.message.retry.count' => 0, - 'messaging.destination.name' => $carrier?->get('destination_name') ?: implode(', ', (array) $message->getRoutingKey()), - 'messaging.amqp.message.type' => $message->getTypeString(), - 'messaging.amqp.message.routing_key' => $message->getRoutingKey(), - 'messaging.amqp.message.exchange' => $message->getExchange(), - 'messaging.amqp.message.queue' => $message->getQueue(), - 'messaging.amqp.message.pool_name' => $message->getPoolName(), - 'messaging.amqp.message.result' => $event instanceof AmqpEvent\AfterConsume ? $event->getResult()->value : 'fail', - ]); - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData(['exception.stack_trace' => (string) $exception]); + try { + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER); + + /** @var ConsumerMessage $message */ + $message = $event->getMessage(); + $transaction->setData([ + 'messaging.system' => 'amqp', + 'messaging.operation' => 'process', + 'messaging.message.id' => $carrier?->get('message_id'), + 'messaging.message.body.size' => $carrier?->get('body_size'), + 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, + 'messaging.message.retry.count' => 0, + 'messaging.destination.name' => $carrier?->get('destination_name') ?: implode(', ', (array) $message->getRoutingKey()), + 'messaging.amqp.message.type' => $message->getTypeString(), + 'messaging.amqp.message.routing_key' => $message->getRoutingKey(), + 'messaging.amqp.message.exchange' => $message->getExchange(), + 'messaging.amqp.message.queue' => $message->getQueue(), + 'messaging.amqp.message.pool_name' => $message->getPoolName(), + 'messaging.amqp.message.result' => $event instanceof AmqpEvent\AfterConsume ? $event->getResult()->value : 'fail', + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } } - } - - SentrySdk::getCurrentHub()->setSpan($transaction); + } finally { + SentrySdk::getCurrentHub()->setSpan($transaction); - $transaction->finish(); + $transaction->finish(); + } } - private function handleKafkaMessageProcessing(KafkaEvent\BeforeConsume $event): void + protected function handleKafkaMessageProcessing(KafkaEvent\BeforeConsume $event): void { if (! $this->switcher->isTracingEnabled('kafka')) { return; @@ -638,18 +661,18 @@ private function handleKafkaMessageProcessing(KafkaEvent\BeforeConsume $event): } } - $this->continueTrace( - sentryTrace: $carrier?->getSentryTrace() ?? '', - baggage: $carrier?->getBaggage() ?? '', - name: $consumer->getTopic() . ' process', - op: 'queue.process', - description: $consumer::class, - origin: 'auto.kafka', - source: TransactionSource::custom() + $this->startTransaction( + continueTrace($carrier?->getSentryTrace() ?? '', $carrier?->getBaggage() ?? '') + ->setName($consumer->getTopic() . ' process') + ->setOp('queue.process') + ->setDescription($consumer::class) + ->setOrigin('auto.kafka') + ->setSource(TransactionSource::custom()) + ->setData(['coroutine.id' => Coroutine::id()]) ); } - private function handleKafkaMessageProcessed(KafkaEvent\AfterConsume|KafkaEvent\FailToConsume $event): void + protected function handleKafkaMessageProcessed(KafkaEvent\AfterConsume|KafkaEvent\FailToConsume $event): void { $transaction = SentrySdk::getCurrentHub()->getTransaction(); @@ -657,40 +680,44 @@ private function handleKafkaMessageProcessed(KafkaEvent\AfterConsume|KafkaEvent\ return; } - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER); - $consumer = $event->getConsumer(); - $transaction->setData([ - 'messaging.system' => 'kafka', - 'messaging.operation' => 'process', - 'messaging.message.id' => $carrier?->get('message_id'), - 'messaging.message.body.size' => $carrier?->get('body_size'), - 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, - 'messaging.message.retry.count' => 0, - 'messaging.destination.name' => $carrier?->get('destination_name') ?: (is_array($consumer->getTopic()) ? implode(',', $consumer->getTopic()) : $consumer->getTopic()), - 'messaging.kafka.consumer.group' => $consumer->getGroupId(), - 'messaging.kafka.consumer.pool' => $consumer->getPool(), - ]); - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData(['exception.stack_trace' => (string) $exception]); + try { + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER); + $consumer = $event->getConsumer(); + $transaction->setData([ + 'messaging.system' => 'kafka', + 'messaging.operation' => 'process', + 'messaging.message.id' => $carrier?->get('message_id'), + 'messaging.message.body.size' => $carrier?->get('body_size'), + 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, + 'messaging.message.retry.count' => 0, + 'messaging.destination.name' => $carrier?->get('destination_name') ?: (is_array($consumer->getTopic()) ? implode(',', $consumer->getTopic()) : $consumer->getTopic()), + 'messaging.kafka.consumer.group' => $consumer->getGroupId(), + 'messaging.kafka.consumer.pool' => $consumer->getPool(), + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } } - } - - SentrySdk::getCurrentHub()->setSpan($transaction); + } finally { + SentrySdk::getCurrentHub()->setSpan($transaction); - $transaction->finish(); + $transaction->finish(); + } } - private function handleAsyncQueueJobProcessing(AsyncQueueEvent\BeforeHandle $event): void + protected function handleAsyncQueueJobProcessing(AsyncQueueEvent\BeforeHandle $event): void { if (! $this->switcher->isTracingEnabled('async_queue')) { return; @@ -700,18 +727,18 @@ private function handleAsyncQueueJobProcessing(AsyncQueueEvent\BeforeHandle $eve $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); $job = $event->getMessage()->job(); - $this->continueTrace( - sentryTrace: $carrier?->getSentryTrace() ?? '', - baggage: $carrier?->getBaggage() ?? '', - name: $job::class, - op: 'queue.process', - description: 'async_queue: ' . $job::class, - origin: 'auto.async_queue', - source: TransactionSource::custom() + $this->startTransaction( + continueTrace($carrier?->getSentryTrace() ?? '', $carrier?->getBaggage() ?? '') + ->setName($job::class) + ->setOp('queue.process') + ->setDescription('async_queue: ' . $job::class) + ->setOrigin('auto.async_queue') + ->setSource(TransactionSource::custom()) + ->setData(['coroutine.id' => Coroutine::id()]) ); } - private function handleAsyncQueueJobProcessed(AsyncQueueEvent\AfterHandle|AsyncQueueEvent\RetryHandle|AsyncQueueEvent\FailedHandle $event): void + protected function handleAsyncQueueJobProcessed(AsyncQueueEvent\AfterHandle|AsyncQueueEvent\RetryHandle|AsyncQueueEvent\FailedHandle $event): void { $transaction = SentrySdk::getCurrentHub()->getTransaction(); @@ -719,36 +746,43 @@ private function handleAsyncQueueJobProcessed(AsyncQueueEvent\AfterHandle|AsyncQ return; } - /** @var null|Carrier $carrier */ - $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); - $transaction->setData([ - 'messaging.system' => 'async_queue', - 'messaging.operation' => 'process', - 'messaging.message.id' => $carrier?->get('message_id'), - 'messaging.message.body.size' => $carrier?->get('body_size'), - 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, - 'messaging.message.retry.count' => $event->getMessage()->getAttempts(), - 'messaging.destination.name' => $carrier?->get('destination_name') ?: 'unknown queue', - ]); - - if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { - $transaction->setStatus(SpanStatus::internalError()) - ->setTags([ - 'error' => 'true', - 'exception.class' => $exception::class, - 'exception.message' => $exception->getMessage(), - 'exception.code' => (string) $exception->getCode(), - ]); - if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { - $transaction->setData(['exception.stack_trace' => (string) $exception]); + try { + /** @var null|Carrier $carrier */ + $carrier = Context::get(Constants::TRACE_CARRIER, null, Coroutine::parentId()); + $transaction->setData([ + 'messaging.system' => 'async_queue', + 'messaging.operation' => 'process', + 'messaging.message.id' => $carrier?->get('message_id'), + 'messaging.message.body.size' => $carrier?->get('body_size'), + 'messaging.message.receive.latency' => $carrier?->has('publish_time') ? (microtime(true) - $carrier->get('publish_time')) : null, + 'messaging.message.retry.count' => $event->getMessage()->getAttempts(), + 'messaging.destination.name' => $carrier?->get('destination_name') ?: 'unknown queue', + ]); + + if (method_exists($event, 'getThrowable') && $exception = $event->getThrowable()) { + $transaction->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($this->switcher->isTracingExtraTagEnabled('exception.stack_trace')) { + $transaction->setData(['exception.stack_trace' => (string) $exception]); + } } - } - - SentrySdk::getCurrentHub()->setSpan($transaction); + } finally { + SentrySdk::getCurrentHub()->setSpan($transaction); - $transaction->finish(); + $transaction->finish(); + } } + /** + * @return array{0:string,1:array,2:string} + */ private function parseRoute(Dispatched $dispatched): array { $route = ''; diff --git a/src/sentry/src/Tracing/SpanStarter.php b/src/sentry/src/Tracing/SpanStarter.php index fad2f3398..99ef865ca 100644 --- a/src/sentry/src/Tracing/SpanStarter.php +++ b/src/sentry/src/Tracing/SpanStarter.php @@ -11,24 +11,94 @@ namespace FriendsOfHyperf\Sentry\Tracing; -use FriendsOfHyperf\Sentry\Constants; +use FriendsOfHyperf\Sentry\Switcher; use FriendsOfHyperf\Sentry\Util\Carrier; -use Hyperf\Context\ApplicationContext; -use Hyperf\Rpc\Context as RpcContext; use Psr\Http\Message\ServerRequestInterface; use Sentry\SentrySdk; use Sentry\State\HubInterface; +use Sentry\State\Scope; use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; use Sentry\Tracing\Transaction; +use Sentry\Tracing\TransactionContext; use Sentry\Tracing\TransactionSource; +use Throwable; use function Hyperf\Tappable\tap; use function Sentry\continueTrace; +use function Sentry\trace; trait SpanStarter { + protected function startTransaction(TransactionContext $transactionContext, array $customSamplingContext = []): Transaction + { + $hub = SentrySdk::setCurrentHub( + tap(clone SentrySdk::getCurrentHub(), fn (HubInterface $hub) => $hub->pushScope()) + ); + + $transaction = $hub->startTransaction($transactionContext, $customSamplingContext); + + $hub->setSpan($transaction); + + return $transaction; + } + + /** + * Execute the given callable while wrapping it in a span added as a child to the current transaction and active span. + * If there is no transaction active this is a no-op and the scope passed to the trace callable will be unused. + * + * @template T + * + * @param callable(Scope):T $trace + * @return T + */ + protected function trace(callable $trace, SpanContext $context) + { + $isTracingExtraTagEnabled = isset($this->switcher) + && $this->switcher instanceof Switcher + && $this->switcher->isTracingExtraTagEnabled('exception.stack_trace'); + + if ($context->getStatus() === null) { + $context->setStatus(SpanStatus::ok()); + } + + if ($context->getStartTimestamp() === null) { + $context->setStartTimestamp(microtime(true)); + } + + return trace( + function (Scope $scope) use ($trace, $isTracingExtraTagEnabled) { + try { + return $trace($scope); + } catch (Throwable $exception) { + $span = $scope->getSpan(); + if ($span !== null) { + $span->setStatus(SpanStatus::internalError()) + ->setTags([ + 'error' => 'true', + 'exception.class' => $exception::class, + 'exception.code' => (string) $exception->getCode(), + ]) + ->setData([ + 'exception.message' => $exception->getMessage(), + ]); + if ($isTracingExtraTagEnabled) { + $span->setData([ + 'exception.stack_trace' => (string) $exception, + ]); + } + } + throw $exception; + } + }, + $context + ); + } + + /** + * @deprecated since v3.1, will be removed in v3.2, use trace() instead. + */ protected function startSpan( ?string $op = null, ?string $description = null, @@ -39,6 +109,10 @@ protected function startSpan( return null; } + if ($parent instanceof Transaction && ! $parent->getSampled()) { + return null; + } + $spanContext = SpanContext::make()->setOp($op) ->setDescription($description) ->setOrigin($origin) @@ -51,36 +125,27 @@ protected function startSpan( ); } + /** + * @deprecated since v3.1, will be removed in v3.2, use startTransaction() instead. + */ protected function startRequestTransaction(ServerRequestInterface $request, ...$options): Transaction { - // Get sentry-trace and baggage - $sentryTrace = match (true) { - $request->hasHeader('sentry-trace') => $request->getHeaderLine('sentry-trace'), - $request->hasHeader('traceparent') => $request->getHeaderLine('traceparent'), - default => '', - }; - $baggage = $request->getHeaderLine('baggage'); - $container = $this->container ?? ApplicationContext::getContainer(); - - // Rpc Context - if ($container->has(RpcContext::class)) { - $rpcContext = $container->get(RpcContext::class); - /** @var null|string $payload */ - $payload = $rpcContext->get(Constants::TRACE_CARRIER); - if ($payload) { - $carrier = Carrier::fromJson($payload); - [$sentryTrace, $baggage] = [$carrier->getSentryTrace(), $carrier->getBaggage()]; - } - } + $carrier = Carrier::fromRequest($request); - return $this->continueTrace($sentryTrace, $baggage, ...$options); + return $this->continueTrace($carrier->getSentryTrace(), $carrier->getBaggage(), ...$options); } + /** + * @deprecated since v3.1, will be removed in v3.2, use startTransaction() instead. + */ protected function startCoroutineTransaction(Span $parent, ...$options): Transaction { return $this->continueTrace($parent->toTraceparent(), $parent->toBaggage(), ...$options); } + /** + * @deprecated since v3.1, will be removed in v3.2, use startTransaction() instead. + */ protected function continueTrace(string $sentryTrace = '', string $baggage = '', ...$options): Transaction { $hub = SentrySdk::setCurrentHub( diff --git a/src/sentry/src/Util/Carrier.php b/src/sentry/src/Util/Carrier.php index 3b39c7167..b812755cc 100644 --- a/src/sentry/src/Util/Carrier.php +++ b/src/sentry/src/Util/Carrier.php @@ -11,10 +11,14 @@ namespace FriendsOfHyperf\Sentry\Util; +use FriendsOfHyperf\Sentry\Constants; +use Hyperf\Context\ApplicationContext; use Hyperf\Contract\Arrayable; use Hyperf\Contract\Jsonable; +use Hyperf\Rpc\Context as RpcContext; use JsonException; use JsonSerializable; +use Psr\Http\Message\ServerRequestInterface; use Sentry\Tracing\Span; use Stringable; @@ -58,6 +62,34 @@ public static function fromSpan(Span $span): static ]); } + public static function fromRequest(ServerRequestInterface $request): static + { + // Get sentry-trace and baggage + $sentryTrace = match (true) { + $request->hasHeader('sentry-trace') => $request->getHeaderLine('sentry-trace'), + $request->hasHeader('traceparent') => $request->getHeaderLine('traceparent'), + default => '', + }; + $baggage = $request->getHeaderLine('baggage'); + $container = ApplicationContext::getContainer(); + + // Rpc Context + if ($container->has(RpcContext::class)) { + $rpcContext = $container->get(RpcContext::class); + /** @var null|string $payload */ + $payload = $rpcContext->get(Constants::TRACE_CARRIER); + if ($payload) { + $carrier = Carrier::fromJson($payload); + [$sentryTrace, $baggage] = [$carrier->getSentryTrace(), $carrier->getBaggage()]; + } + } + + return new static([ + 'sentry-trace' => $sentryTrace, + 'baggage' => $baggage, + ]); + } + public function with(array $data): static { $new = clone $this;