Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
90bde50
feat(sentry): support nested spans for console commands
huangdijia Oct 13, 2025
034ad93
fix(sentry): improve scope management for console command spans
huangdijia Oct 13, 2025
e45ec6a
fix(sentry): update coroutine tracing to use spans instead of transac…
huangdijia Oct 13, 2025
baadab2
fix(sentry): update span handling in CoroutineAspect and EventHandleL…
huangdijia Oct 13, 2025
74560b7
fix(sentry): refactor span handling in CoroutineAspect for improved c…
huangdijia Oct 13, 2025
c22cac4
fix(sentry): clarify purpose of commented span handling in EventHandl…
huangdijia Oct 13, 2025
6403027
feat(sentry): add CoContainer for managing Sentry context in a weak r…
huangdijia Oct 13, 2025
9e296b2
fix(sentry): refactor CoContainer method to improve span retrieval logic
huangdijia Oct 13, 2025
4d38552
fix(sentry): refactor CoContainer methods for improved clarity and or…
huangdijia Oct 13, 2025
a822835
fix(sentry): add sampling check for parent span in EventHandleListener
huangdijia Oct 13, 2025
bd15825
fix(sentry): add del method to CoContainer for removing items from th…
huangdijia Oct 13, 2025
472d6b3
fix(sentry): update getContainer method to use getOrSet for better co…
huangdijia Oct 13, 2025
2d62130
fix(sentry): refactor coroutine execution to use trace function for b…
huangdijia Oct 13, 2025
de65019
fix(sentry): adjust scope management in EventHandleListener for impro…
huangdijia Oct 13, 2025
29df54b
fix(sentry): ensure parent span is restored after coroutine processing
huangdijia Oct 13, 2025
a1219a0
fix(sentry): streamline coroutine span creation by removing unnecessa…
huangdijia Oct 13, 2025
6166452
fix(sentry): refactor callable in trace function to use static closur…
huangdijia Oct 13, 2025
390a5f9
fix(CoContainer): update pull method to require a non-null object key
huangdijia Oct 13, 2025
27e19c2
fix(EventHandleListener): streamline span tag and status setting for …
huangdijia Oct 13, 2025
9c1fbff
fix(EventHandleListener): remove unnecessary parent span sampling che…
huangdijia Oct 13, 2025
f4c0470
fix(CoroutineAspect): initialize Sentry SDK before transferring conte…
huangdijia Oct 13, 2025
c0cda26
fix(CoroutineAspect): ensure Sentry SDK initialization in new corouti…
huangdijia Oct 13, 2025
714eb36
fix(CoroutineAspect): update operation name to 'coroutine.create' for…
huangdijia Oct 13, 2025
3c0c510
fix(CoroutineAspect): update operation names for better tracing accuracy
huangdijia Oct 13, 2025
1fc3718
fix(EventHandleListener): streamline span handling by consolidating p…
huangdijia Oct 13, 2025
f91fa26
fix(EventHandleListener): add check for parent span sampling before p…
huangdijia Oct 13, 2025
77d78fd
fix(EventHandleListener): ensure proper handling of parent span lifec…
huangdijia Oct 13, 2025
306fd08
fix(EventHandleListener): update operation name to 'console.command.e…
huangdijia Oct 13, 2025
f807d7c
fix(EventHandleListener): ensure parent span is set in CoContainer on…
huangdijia Oct 13, 2025
a831503
fix(EventHandleListener): improve parent span handling by checking sp…
huangdijia Oct 13, 2025
4614ce2
fix(EventHandleListener): ensure parent span is finished before setti…
huangdijia Oct 13, 2025
386369a
fix(EventHandleListener): remove command from CoContainer if parent s…
huangdijia Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 46 additions & 67 deletions src/sentry/src/Tracing/Aspect/CoroutineAspect.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Engine\Coroutine as Co;
use Sentry\SentrySdk;
use Sentry\State\Scope;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\SpanStatus;
use Throwable;

use function FriendsOfHyperf\Sentry\startTransaction;
use function FriendsOfHyperf\Sentry\trace;
use function Hyperf\Coroutine\defer;
use function Sentry\continueTrace;

Expand Down Expand Up @@ -52,76 +52,55 @@ public function process(ProceedingJoinPoint $proceedingJoinPoint)

$callingOnFunction = CoroutineBacktraceHelper::foundCallingOnFunction();

// Only trace the top-level coroutine creation.
if (! $callingOnFunction) {
return $proceedingJoinPoint->process();
}

// Get the current transaction from the current scope.
$transaction = SentrySdk::getCurrentHub()->getTransaction();

// If there's no active transaction, skip tracing.
if (! $transaction?->getSampled()) {
return $proceedingJoinPoint->process();
}
return trace(
function (Scope $scope) use ($proceedingJoinPoint, $callingOnFunction) {
if ($callingOnFunction && $span = $scope->getSpan()) {
$cid = Co::id();
$keys = $this->keys;
$callable = $proceedingJoinPoint->arguments['keys']['callable'];

// Transfer the Sentry context to the new coroutine.
$proceedingJoinPoint->arguments['keys']['callable'] = function () use ($callable, $span, $callingOnFunction, $cid, $keys) {
SentrySdk::init(); // Ensure Sentry is initialized in the new coroutine.

$from = Co::getContextFor($cid);
$current = Co::getContextFor();

foreach ($keys as $key) {
if (isset($from[$key]) && ! isset($current[$key])) {
$current[$key] = $from[$key];
}
}

$transaction = startTransaction(
continueTrace($span->toTraceparent(), $span->toBaggage())
->setName('coroutine')
->setOp('coroutine.prepare')
->setDescription($callingOnFunction)
->setOrigin('auto.coroutine')
);

defer(function () use ($transaction) {
$transaction->finish();
Integration::flushEvents();
});

return trace(
fn () => $callable(),
SpanContext::make()
->setOp('coroutine.execute')
->setDescription($callingOnFunction)
->setOrigin('auto.coroutine')
);
};
}

// Start a span for the coroutine creation.
$parent = $transaction->startChild(
return $proceedingJoinPoint->process();
},
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'];

// 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();

foreach ($keys as $key) {
if (isset($from[$key]) && ! isset($current[$key])) {
$current[$key] = $from[$key];
}
}

$coTransaction = startTransaction(
continueTrace($parent->toTraceparent(), $parent->toBaggage())
->setName('coroutine')
->setOp('coroutine.execute')
->setDescription($callingOnFunction)
->setOrigin('auto.coroutine')
);

defer(function () use ($coTransaction) {
// Set the transaction on the current scope to ensure it's the active one.
SentrySdk::getCurrentHub()->setSpan($coTransaction);

// Finish the transaction when the coroutine ends.
$coTransaction->finish();

// Flush events
Integration::flushEvents();
});

try {
$callable();
} catch (Throwable $exception) {
$coTransaction->setStatus(SpanStatus::internalError());

throw $exception;
}
};

try {
return $proceedingJoinPoint->process();
} finally {
$parent->finish();
}
}
}
82 changes: 54 additions & 28 deletions src/sentry/src/Tracing/Listener/EventHandleListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use FriendsOfHyperf\Sentry\Feature;
use FriendsOfHyperf\Sentry\Integration;
use FriendsOfHyperf\Sentry\Util\Carrier;
use FriendsOfHyperf\Sentry\Util\CoContainer;
use FriendsOfHyperf\Sentry\Util\SqlParser;
use FriendsOfHyperf\Support\RedisCommand;
use Hyperf\Amqp\Event as AmqpEvent;
Expand Down Expand Up @@ -43,6 +44,7 @@
use Psr\Container\ContainerInterface;
use Sentry\SentrySdk;
use Sentry\State\Scope;
use Sentry\Tracing\Span;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\SpanStatus;
use Sentry\Tracing\TransactionContext;
Expand Down Expand Up @@ -376,61 +378,85 @@ protected function handleCommandStarting(CommandEvent\BeforeHandle $event): void

$command = $event->getCommand();

$transaction = startTransaction(
TransactionContext::make()
->setName($command->getName() ?: '<unnamed command>')
->setOp('console.command')
->setDescription($command->getDescription())
->setOrigin('auto.console')
if (! $parentSpan = SentrySdk::getCurrentHub()->getSpan()) {
$parentSpan = startTransaction(
TransactionContext::make()
->setName($command->getName() ?: '<unnamed command>')
->setOp('console.command')
->setDescription($command->getDescription())
->setOrigin('auto.console')
->setData([
'command.arguments' => (fn () => $this->input->getArguments())->call($command),
'command.options' => (fn () => $this->input->getOptions())->call($command),
])
->setTags([
'command.name' => $command->getName(),
])
->setSource(TransactionSource::task())
);

CoContainer::set($command, $parentSpan);
}

if (! $parentSpan->getSampled()) {
CoContainer::del($command);
return;
}

$scope = SentrySdk::getCurrentHub()->pushScope();
$span = $parentSpan->startChild(
SpanContext::make()
->setOp('console.command.execute')
->setDescription($command->getName() ?: $command->getDescription())
->setData([
'command.arguments' => (fn () => $this->input->getArguments())->call($command),
'command.options' => (fn () => $this->input->getOptions())->call($command),
])
->setTags([
'command.name' => $command->getName(),
])
->setSource(TransactionSource::task())
);

Coroutine::inCoroutine() && defer(function () use ($transaction) {
// Make sure the transaction is finished after the command is executed
SentrySdk::getCurrentHub()->setSpan($transaction);

// Finish transaction
$transaction->finish();

// Flush events
Integration::flushEvents();
});
$scope->setSpan($span);
}

protected function handleCommandFinished(CommandEvent\AfterExecute $event): void
{
$transaction = SentrySdk::getCurrentHub()->getTransaction();
$span = SentrySdk::getCurrentHub()->getSpan();

if (! $transaction?->getSampled()) {
if (! $span instanceof Span) {
return;
}

$command = $event->getCommand();
$sampled = $span->getSampled();

try {
$command = $event->getCommand();
if (! $sampled) {
return;
}

/** @var int $exitCode */
$exitCode = (fn () => $this->exitCode ?? SymfonyCommand::SUCCESS)->call($command);

$transaction->setTags([
$span->setTags([
'command.exit_code' => (string) $exitCode,
]);

$transaction->setStatus(
])->setStatus(
$event->getThrowable() || $exitCode !== SymfonyCommand::SUCCESS
? SpanStatus::internalError()
: SpanStatus::ok()
);
} finally {
if (! Coroutine::inCoroutine()) {
SentrySdk::getCurrentHub()->setSpan($transaction);
if ($sampled) {
$span->finish();
Integration::flushEvents();
}

$parentSpan = CoContainer::pull($command);

$transaction->finish();
if ($parentSpan instanceof Span) {
$parentSpan->finish();
SentrySdk::getCurrentHub()->setSpan($parentSpan);
SentrySdk::getCurrentHub()->popScope();
}
}
}
Expand Down
75 changes: 75 additions & 0 deletions src/sentry/src/Util/CoContainer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

declare(strict_types=1);
/**
* This file is part of friendsofhyperf/components.
*
* @link https://github.com/friendsofhyperf/components
* @document https://github.com/friendsofhyperf/components/blob/main/README.md
* @contact huangdijia@gmail.com
*/

namespace FriendsOfHyperf\Sentry\Util;

use Hyperf\Context\Context;
use WeakMap;

/**
* @template TKey of object
* @template TValue of object
*/
class CoContainer
{
public const CONTEXT_KEY = 'sentry.context.container';

/**
* @param TKey $key
* @param TValue $value
* @return TValue
*/
public static function set(object $key, object $value): object
{
self::getContainer()[$key] = $value;

return $value;
}

/**
* @param TKey $key
* @return null|TValue
*/
public static function get(object $key): ?object
{
$container = self::getContainer();

return $container[$key] ?? null;
}

/**
* @param TKey $key
* @return null|TValue
*/
public static function pull(object $key): ?object
{
$container = self::getContainer();

if (! isset($container[$key])) {
return null;
}

$value = $container[$key];
unset($container[$key]);

return $value;
}

public static function del(object $key): void
{
unset(self::getContainer()[$key]);
}

private static function getContainer(): WeakMap
{
return Context::getOrSet(self::CONTEXT_KEY, fn () => new WeakMap());
}
}