diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96e945f4..a5eefcec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
- Drop support for Laravel 5.x (#581)
- Remove `Sentry\Integration::extractNameForRoute()`, it's alternative `Sentry\Integration::extractNameAndSourceForRoute()` is marked as `@internal` (#580)
- Remove extracting route name or controller for transaction names (#583). This unifies the transaction names to a more concise format.
+- Remove internal `Sentry\Integration::currentTracingSpan()`, use `SentrySdk::getCurrentHub()->getSpan()` if you were using this internal method. (#592)
**Other changes**
@@ -19,6 +20,9 @@
- Set the tracing transaction name on the `Illuminate\Routing\Events\RouteMatched` instead of at the end of the request (#580)
- Add tracing span for Laravel HTTP client requests (#585)
- Simplify Sentry meta tag retrieval, by adding `Sentry\Laravel\Integration::sentryMeta()` (#586)
+- Fix tracing with nested queue jobs (mostly when running jobs in the `sync` driver) (#592)
+- Add a `http.route` span, this span indicates the time that is spent inside a controller method or route closure (#593)
+- Add a `db.transaction` span, this span indicates the time that is spent inside a database transaction (#594)
## 2.14.2
diff --git a/src/Sentry/Laravel/EventHandler.php b/src/Sentry/Laravel/EventHandler.php
index 41d87a06..e2f32924 100644
--- a/src/Sentry/Laravel/EventHandler.php
+++ b/src/Sentry/Laravel/EventHandler.php
@@ -140,9 +140,9 @@ class EventHandler
/**
* Indicates if we pushed a scope for the queue.
*
- * @var bool
+ * @var int
*/
- private $pushedQueueScope = false;
+ private $pushedQueueScopeCount = 0;
/**
* Indicates if we pushed a scope for Octane.
@@ -173,76 +173,40 @@ public function __construct(Container $container, array $config)
/**
* Attach all event handlers.
*/
- public function subscribe(): void
+ public function subscribe(Dispatcher $dispatcher): void
{
- /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
- try {
- $dispatcher = $this->container->make(Dispatcher::class);
-
- foreach (static::$eventHandlerMap as $eventName => $handler) {
- $dispatcher->listen($eventName, [$this, $handler]);
- }
- } catch (BindingResolutionException $e) {
- // If we cannot resolve the event dispatcher we also cannot listen to events
+ foreach (static::$eventHandlerMap as $eventName => $handler) {
+ $dispatcher->listen($eventName, [$this, $handler]);
}
}
/**
* Attach all authentication event handlers.
*/
- public function subscribeAuthEvents(): void
+ public function subscribeAuthEvents(Dispatcher $dispatcher): void
{
- /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
- try {
- $dispatcher = $this->container->make(Dispatcher::class);
-
- foreach (static::$authEventHandlerMap as $eventName => $handler) {
- $dispatcher->listen($eventName, [$this, $handler]);
- }
- } catch (BindingResolutionException $e) {
- // If we cannot resolve the event dispatcher we also cannot listen to events
+ foreach (static::$authEventHandlerMap as $eventName => $handler) {
+ $dispatcher->listen($eventName, [$this, $handler]);
}
}
/**
* Attach all queue event handlers.
*/
- public function subscribeOctaneEvents(): void
+ public function subscribeOctaneEvents(Dispatcher $dispatcher): void
{
- /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
- try {
- $dispatcher = $this->container->make(Dispatcher::class);
-
- foreach (static::$octaneEventHandlerMap as $eventName => $handler) {
- $dispatcher->listen($eventName, [$this, $handler]);
- }
- } catch (BindingResolutionException $e) {
- // If we cannot resolve the event dispatcher we also cannot listen to events
+ foreach (static::$octaneEventHandlerMap as $eventName => $handler) {
+ $dispatcher->listen($eventName, [$this, $handler]);
}
}
/**
* Attach all queue event handlers.
- *
- * @param \Illuminate\Queue\QueueManager $queue
*/
- public function subscribeQueueEvents(QueueManager $queue): void
+ public function subscribeQueueEvents(Dispatcher $dispatcher): void
{
- $queue->looping(function () {
- $this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedQueueScope);
-
- $this->pushedQueueScope = false;
- });
-
- /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
- try {
- $dispatcher = $this->container->make(Dispatcher::class);
-
- foreach (static::$queueEventHandlerMap as $eventName => $handler) {
- $dispatcher->listen($eventName, [$this, $handler]);
- }
- } catch (BindingResolutionException $e) {
- // If we cannot resolve the event dispatcher we also cannot listen to events
+ foreach (static::$queueEventHandlerMap as $eventName => $handler) {
+ $dispatcher->listen($eventName, [$this, $handler]);
}
}
@@ -382,11 +346,9 @@ private function configureUserScopeFromModel($authUser): void
protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event): void
{
- $this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedQueueScope);
-
$this->prepareScopeForTaskWithinLongRunningProcess();
- $this->pushedQueueScope = true;
+ ++$this->pushedQueueScopeCount;
if (!$this->recordQueueInfo) {
return;
@@ -415,11 +377,15 @@ protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event):
protected function queueJobExceptionOccurredHandler(QueueEvents\JobExceptionOccurred $event): void
{
+ $this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedQueueScopeCount > 0);
+
$this->afterTaskWithinLongRunningProcess();
}
protected function queueJobProcessedHandler(QueueEvents\JobProcessed $event): void
{
+ $this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedQueueScopeCount > 0);
+
$this->afterTaskWithinLongRunningProcess();
}
diff --git a/src/Sentry/Laravel/Integration.php b/src/Sentry/Laravel/Integration.php
index 8f683781..a9df0948 100644
--- a/src/Sentry/Laravel/Integration.php
+++ b/src/Sentry/Laravel/Integration.php
@@ -3,10 +3,7 @@
namespace Sentry\Laravel;
use Illuminate\Routing\Route;
-use Illuminate\Support\Str;
use Sentry\SentrySdk;
-use Sentry\Tracing\Span;
-use Sentry\Tracing\Transaction;
use Sentry\Tracing\TransactionSource;
use function Sentry\addBreadcrumb;
use function Sentry\configureScope;
@@ -118,7 +115,7 @@ public static function extractNameAndSourceForRoute(Route $route): array
{
return [
'/' . ltrim($route->uri(), '/'),
- TransactionSource::route()
+ TransactionSource::route(),
];
}
@@ -140,7 +137,7 @@ public static function sentryMeta(): string
*/
public static function sentryTracingMeta(): string
{
- $span = self::currentTracingSpan();
+ $span = SentrySdk::getCurrentHub()->getSpan();
if ($span === null) {
return '';
@@ -157,7 +154,7 @@ public static function sentryTracingMeta(): string
*/
public static function sentryBaggageMeta(): string
{
- $span = self::currentTracingSpan();
+ $span = SentrySdk::getCurrentHub()->getSpan();
if ($span === null) {
return '';
@@ -165,28 +162,4 @@ public static function sentryBaggageMeta(): string
return sprintf('', $span->toBaggage());
}
-
- /**
- * Get the current active tracing span from the scope.
- *
- * @return \Sentry\Tracing\Transaction|null
- *
- * @internal This is used internally as an easy way to retrieve the current active transaction.
- */
- public static function currentTransaction(): ?Transaction
- {
- return SentrySdk::getCurrentHub()->getTransaction();
- }
-
- /**
- * Get the current active tracing span from the scope.
- *
- * @return \Sentry\Tracing\Span|null
- *
- * @internal This is used internally as an easy way to retrieve the current active tracing span.
- */
- public static function currentTracingSpan(): ?Span
- {
- return SentrySdk::getCurrentHub()->getSpan();
- }
}
diff --git a/src/Sentry/Laravel/ServiceProvider.php b/src/Sentry/Laravel/ServiceProvider.php
index 5b8bf12a..5e01a2c6 100644
--- a/src/Sentry/Laravel/ServiceProvider.php
+++ b/src/Sentry/Laravel/ServiceProvider.php
@@ -2,6 +2,8 @@
namespace Sentry\Laravel;
+use Illuminate\Contracts\Container\BindingResolutionException;
+use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Http\Kernel as HttpKernelInterface;
use Illuminate\Foundation\Application as Laravel;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
@@ -95,18 +97,25 @@ protected function bindEvents(): void
$handler = new EventHandler($this->app, $userConfig);
- $handler->subscribe();
+ try {
+ /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
+ $dispatcher = $this->app->make(Dispatcher::class);
- if ($this->app->bound('octane')) {
- $handler->subscribeOctaneEvents();
- }
+ $handler->subscribe($dispatcher);
- if ($this->app->bound('queue')) {
- $handler->subscribeQueueEvents($this->app->make('queue'));
- }
+ if ($this->app->bound('octane')) {
+ $handler->subscribeOctaneEvents($dispatcher);
+ }
- if (isset($userConfig['send_default_pii']) && $userConfig['send_default_pii'] !== false) {
- $handler->subscribeAuthEvents();
+ if ($this->app->bound('queue')) {
+ $handler->subscribeQueueEvents($dispatcher, $this->app->make('queue'));
+ }
+
+ if (isset($userConfig['send_default_pii']) && $userConfig['send_default_pii'] !== false) {
+ $handler->subscribeAuthEvents($dispatcher);
+ }
+ } catch (BindingResolutionException $e) {
+ // If we cannot resolve the event dispatcher we also cannot listen to events
}
}
diff --git a/src/Sentry/Laravel/Tracing/EventHandler.php b/src/Sentry/Laravel/Tracing/EventHandler.php
index d971d32a..3850d540 100644
--- a/src/Sentry/Laravel/Tracing/EventHandler.php
+++ b/src/Sentry/Laravel/Tracing/EventHandler.php
@@ -3,8 +3,6 @@
namespace Sentry\Laravel\Tracing;
use Exception;
-use Illuminate\Contracts\Container\BindingResolutionException;
-use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Database\Events as DatabaseEvents;
use Illuminate\Http\Client\Events as HttpClientEvents;
@@ -15,6 +13,7 @@
use RuntimeException;
use Sentry\Laravel\Integration;
use Sentry\SentrySdk;
+use Sentry\Tracing\Span;
use Sentry\Tracing\SpanContext;
use Sentry\Tracing\SpanStatus;
use Sentry\Tracing\TransactionContext;
@@ -22,9 +21,8 @@
class EventHandler
{
- public const QUEUE_PAYLOAD_TRACE_PARENT_DATA = 'sentry_trace_parent_data';
-
public const QUEUE_PAYLOAD_BAGGAGE_DATA = 'sentry_baggage_data';
+ public const QUEUE_PAYLOAD_TRACE_PARENT_DATA = 'sentry_trace_parent_data';
/**
* Map event handlers to events.
@@ -37,6 +35,9 @@ class EventHandler
HttpClientEvents\RequestSending::class => 'httpClientRequestSending',
HttpClientEvents\ResponseReceived::class => 'httpClientResponseReceived',
HttpClientEvents\ConnectionFailed::class => 'httpClientConnectionFailed',
+ DatabaseEvents\TransactionBeginning::class => 'transactionBeginning',
+ DatabaseEvents\TransactionCommitted::class => 'transactionCommitted',
+ DatabaseEvents\TransactionRolledBack::class => 'transactionRolledBack',
];
/**
@@ -50,13 +51,6 @@ class EventHandler
QueueEvents\JobExceptionOccurred::class => 'queueJobExceptionOccurred',
];
- /**
- * The Laravel container.
- *
- * @var \Illuminate\Contracts\Container\Container
- */
- private $container;
-
/**
* Indicates if we should we add SQL queries as spans.
*
@@ -86,32 +80,18 @@ class EventHandler
private $traceQueueJobsAsTransactions;
/**
- * Holds a reference to the parent queue job span.
- *
- * @var \Sentry\Tracing\Span|null
- */
- private $parentQueueJobSpan;
-
- /**
- * Holds a reference to the current queue job span or transaction.
+ * Hold the stack of parent spans that need to be put back on the scope.
*
- * @var \Sentry\Tracing\Transaction|\Sentry\Tracing\Span|null
+ * @var array
*/
- private $currentQueueJobSpan;
+ private $parentSpanStack = [];
/**
- * Holds a reference to the parent http client request span.
+ * Hold the stack of current spans that need to be finished still.
*
- * @var \Sentry\Tracing\Span|null
+ * @var array
*/
- private $parentHttpClientRequestSpan;
-
- /**
- * Holds a reference to the current http client request span.
- *
- * @var \Sentry\Tracing\Span|null
- */
- private $currentHttpClientRequestSpan;
+ private $currentSpanStack = [];
/**
* The backtrace helper.
@@ -122,21 +102,16 @@ class EventHandler
/**
* EventHandler constructor.
- *
- * @param \Illuminate\Contracts\Container\Container $container
- * @param \Sentry\Laravel\Tracing\BacktraceHelper $backtraceHelper
- * @param array $config
*/
- public function __construct(Container $container, BacktraceHelper $backtraceHelper, array $config)
+ public function __construct(array $config, BacktraceHelper $backtraceHelper)
{
- $this->container = $container;
- $this->backtraceHelper = $backtraceHelper;
-
$this->traceSqlQueries = ($config['sql_queries'] ?? true) === true;
$this->traceSqlQueryOrigins = ($config['sql_origin'] ?? true) === true;
$this->traceQueueJobs = ($config['queue_jobs'] ?? false) === true;
$this->traceQueueJobsAsTransactions = ($config['queue_job_transactions'] ?? false) === true;
+
+ $this->backtraceHelper = $backtraceHelper;
}
/**
@@ -144,31 +119,28 @@ public function __construct(Container $container, BacktraceHelper $backtraceHelp
*
* @uses self::routeMatchedHandler()
* @uses self::queryExecutedHandler()
+ * @uses self::transactionBeginningHandler()
+ * @uses self::transactionCommittedHandler()
+ * @uses self::transactionRolledBackHandler()
+ * @uses self::httpClientRequestSendingHandler()
+ * @uses self::httpClientResponseReceivedHandler()
+ * @uses self::httpClientConnectionFailedHandler()
*/
- public function subscribe(): void
+ public function subscribe(Dispatcher $dispatcher): void
{
- try {
- /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
- $dispatcher = $this->container->make(Dispatcher::class);
-
- foreach (static::$eventHandlerMap as $eventName => $handler) {
- $dispatcher->listen($eventName, [$this, $handler]);
- }
- } catch (BindingResolutionException $e) {
- // If we cannot resolve the event dispatcher we also cannot listen to events
+ foreach (static::$eventHandlerMap as $eventName => $handler) {
+ $dispatcher->listen($eventName, [$this, $handler]);
}
}
/**
* Attach all queue event handlers.
*
- * @param \Illuminate\Queue\QueueManager $queue
- *
* @uses self::queueJobProcessingHandler()
* @uses self::queueJobProcessedHandler()
* @uses self::queueJobExceptionOccurredHandler()
*/
- public function subscribeQueueEvents(QueueManager $queue): void
+ public function subscribeQueueEvents(Dispatcher $dispatcher, QueueManager $queue): void
{
// If both types of queue job tracing is disabled also do not register the events
if (!$this->traceQueueJobs && !$this->traceQueueJobsAsTransactions) {
@@ -176,7 +148,7 @@ public function subscribeQueueEvents(QueueManager $queue): void
}
Queue::createPayloadUsing(static function (?string $connection, ?string $queue, ?array $payload): ?array {
- $currentSpan = Integration::currentTracingSpan();
+ $currentSpan = SentrySdk::getCurrentHub()->getSpan();
if ($currentSpan !== null && $payload !== null) {
$payload[self::QUEUE_PAYLOAD_TRACE_PARENT_DATA] = $currentSpan->toTraceparent();
@@ -186,19 +158,8 @@ public function subscribeQueueEvents(QueueManager $queue): void
return $payload;
});
- $queue->looping(function () {
- $this->afterQueuedJob();
- });
-
- try {
- /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
- $dispatcher = $this->container->make(Dispatcher::class);
-
- foreach (static::$queueEventHandlerMap as $eventName => $handler) {
- $dispatcher->listen($eventName, [$this, $handler]);
- }
- } catch (BindingResolutionException $e) {
- // If we cannot resolve the event dispatcher we also cannot listen to events
+ foreach (static::$queueEventHandlerMap as $eventName => $handler) {
+ $dispatcher->listen($eventName, [$this, $handler]);
}
}
@@ -218,14 +179,14 @@ public function __call(string $method, array $arguments)
try {
call_user_func_array([$this, $handlerMethod], $arguments);
- } catch (Exception $exception) {
- // Ignore
+ } catch (Exception $e) {
+ // Ignore to prevent bubbling up errors in the SDK
}
}
protected function routeMatchedHandler(RoutingEvents\RouteMatched $match): void
{
- $transaction = Integration::currentTransaction();
+ $transaction = SentrySdk::getCurrentHub()->getTransaction();
if ($transaction === null) {
return;
@@ -243,7 +204,7 @@ protected function queryExecutedHandler(DatabaseEvents\QueryExecuted $query): vo
return;
}
- $parentSpan = Integration::currentTracingSpan();
+ $parentSpan = SentrySdk::getCurrentHub()->getSpan();
// If there is no tracing span active there is no need to handle the event
if ($parentSpan === null) {
@@ -285,59 +246,79 @@ private function resolveQueryOriginFromBacktrace(): ?string
return "{$filePath}:{$firstAppFrame->getLine()}";
}
- protected function httpClientRequestSendingHandler(HttpClientEvents\RequestSending $event): void
+ protected function transactionBeginningHandler(DatabaseEvents\TransactionBeginning $event): void
{
- $parentSpan = Integration::currentTracingSpan();
+ $parentSpan = SentrySdk::getCurrentHub()->getSpan();
if ($parentSpan === null) {
return;
}
$context = new SpanContext;
+ $context->setOp('db.transaction');
- $context->setOp('http.client');
- $context->setDescription($event->request->method() . ' ' . $event->request->url());
- $context->setStartTimestamp(microtime(true));
-
- $this->currentHttpClientRequestSpan = $parentSpan->startChild($context);
+ $this->pushSpan($parentSpan->startChild($context));
+ }
- $this->parentHttpClientRequestSpan = $parentSpan;
+ protected function transactionCommittedHandler(DatabaseEvents\TransactionCommitted $event): void
+ {
+ $span = $this->popSpan();
- SentrySdk::getCurrentHub()->setSpan($this->currentHttpClientRequestSpan);
+ if ($span !== null) {
+ $span->finish();
+ $span->setStatus(SpanStatus::ok());
+ }
}
- protected function httpClientResponseReceivedHandler(HttpClientEvents\ResponseReceived $event): void
+ protected function transactionRolledBackHandler(DatabaseEvents\TransactionRolledBack $event): void
{
- if ($this->currentHttpClientRequestSpan !== null) {
- $this->currentHttpClientRequestSpan->setHttpStatus($event->response->status());
- $this->afterHttpClientRequest();
+ $span = $this->popSpan();
+
+ if ($span !== null) {
+ $span->finish();
+ $span->setStatus(SpanStatus::internalError());
}
}
- protected function httpClientConnectionFailedHandler(HttpClientEvents\ConnectionFailed $event): void
+ protected function httpClientRequestSendingHandler(HttpClientEvents\RequestSending $event): void
{
- if ($this->currentHttpClientRequestSpan !== null) {
- $this->currentHttpClientRequestSpan->setStatus(SpanStatus::internalError());
- $this->afterHttpClientRequest();
+ $parentSpan = SentrySdk::getCurrentHub()->getSpan();
+
+ if ($parentSpan === null) {
+ return;
}
+
+ $context = new SpanContext;
+
+ $context->setOp('http.client');
+ $context->setDescription($event->request->method() . ' ' . $event->request->url());
+
+ $this->pushSpan($parentSpan->startChild($context));
}
- private function afterHttpClientRequest(): void
+ protected function httpClientResponseReceivedHandler(HttpClientEvents\ResponseReceived $event): void
{
- if ($this->currentHttpClientRequestSpan === null) {
- return;
+ $span = $this->popSpan();
+
+ if ($span !== null) {
+ $span->finish();
+ $span->setHttpStatus($event->response->status());
}
+ }
- $this->currentHttpClientRequestSpan->finish();
- $this->currentHttpClientRequestSpan = null;
+ protected function httpClientConnectionFailedHandler(HttpClientEvents\ConnectionFailed $event): void
+ {
+ $span = $this->popSpan();
- SentrySdk::getCurrentHub()->setSpan($this->parentHttpClientRequestSpan);
- $this->parentHttpClientRequestSpan = null;
+ if ($span !== null) {
+ $span->finish();
+ $span->setStatus(SpanStatus::internalError());
+ }
}
protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event): void
{
- $parentSpan = Integration::currentTracingSpan();
+ $parentSpan = SentrySdk::getCurrentHub()->getSpan();
// If there is no tracing span active and we don't trace jobs as transactions there is no need to handle the event
if ($parentSpan === null && !$this->traceQueueJobsAsTransactions) {
@@ -368,7 +349,7 @@ protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event):
$job = [
'job' => $event->job->getName(),
'queue' => $event->job->getQueue(),
- 'resolved' => $event->job->resolveName(),
+ 'resolved' => $resolvedJobName,
'attempts' => $event->job->attempts(),
'connection' => $event->connectionName,
];
@@ -384,14 +365,12 @@ protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event):
// When the parent span is null we start a new transaction otherwise we start a child of the current span
if ($parentSpan === null) {
- $this->currentQueueJobSpan = SentrySdk::getCurrentHub()->startTransaction($context);
+ $span = SentrySdk::getCurrentHub()->startTransaction($context);
} else {
- $this->currentQueueJobSpan = $parentSpan->startChild($context);
+ $span = $parentSpan->startChild($context);
}
- $this->parentQueueJobSpan = $parentSpan;
-
- SentrySdk::getCurrentHub()->setSpan($this->currentQueueJobSpan);
+ $this->pushSpan($span);
}
protected function queueJobExceptionOccurredHandler(QueueEvents\JobExceptionOccurred $event): void
@@ -406,15 +385,37 @@ protected function queueJobProcessedHandler(QueueEvents\JobProcessed $event): vo
private function afterQueuedJob(?SpanStatus $status = null): void
{
- if ($this->currentQueueJobSpan === null) {
+ $span = $this->popSpan();
+
+ if ($span === null) {
return;
}
- $this->currentQueueJobSpan->setStatus($status);
- $this->currentQueueJobSpan->finish();
- $this->currentQueueJobSpan = null;
+ $span->setStatus($status);
+ $span->finish();
+ }
+
+ private function pushSpan(Span $span): void
+ {
+ $hub = SentrySdk::getCurrentHub();
+
+ $this->parentSpanStack[] = $hub->getSpan();
+
+ $hub->setSpan($span);
+
+ $this->currentSpanStack[] = $span;
+ }
+
+ private function popSpan(): ?Span
+ {
+ if (count($this->currentSpanStack) === 0) {
+ return null;
+ }
+
+ $parent = array_pop($this->parentSpanStack);
+
+ SentrySdk::getCurrentHub()->setSpan($parent);
- SentrySdk::getCurrentHub()->setSpan($this->parentQueueJobSpan);
- $this->parentQueueJobSpan = null;
+ return array_pop($this->currentSpanStack);
}
}
diff --git a/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php b/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php
index dc443a35..3a36257a 100644
--- a/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php
+++ b/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php
@@ -57,7 +57,7 @@ public function setupOnce(): void
public function handleStartRequest(StartRequest $startRequest): void
{
- $this->previousSpan = Integration::currentTracingSpan();
+ $this->previousSpan = SentrySdk::getCurrentHub()->getSpan();
if ($this->previousSpan === null) {
return;
diff --git a/src/Sentry/Laravel/Tracing/Routing/TracingCallableDispatcherTracing.php b/src/Sentry/Laravel/Tracing/Routing/TracingCallableDispatcherTracing.php
new file mode 100644
index 00000000..7ccb1062
--- /dev/null
+++ b/src/Sentry/Laravel/Tracing/Routing/TracingCallableDispatcherTracing.php
@@ -0,0 +1,24 @@
+dispatcher = $dispatcher;
+ }
+
+ public function dispatch(Route $route, $callable)
+ {
+ return $this->wrapRouteDispatch(function () use ($route, $callable) {
+ return $this->dispatcher->dispatch($route, $callable);
+ }, $route);
+ }
+}
diff --git a/src/Sentry/Laravel/Tracing/Routing/TracingControllerDispatcherTracing.php b/src/Sentry/Laravel/Tracing/Routing/TracingControllerDispatcherTracing.php
new file mode 100644
index 00000000..1cedda2c
--- /dev/null
+++ b/src/Sentry/Laravel/Tracing/Routing/TracingControllerDispatcherTracing.php
@@ -0,0 +1,29 @@
+dispatcher = $dispatcher;
+ }
+
+ public function dispatch(Route $route, $controller, $method)
+ {
+ return $this->wrapRouteDispatch(function () use ($route, $controller, $method) {
+ return $this->dispatcher->dispatch($route, $controller, $method);
+ }, $route);
+ }
+
+ public function getMiddleware($controller, $method)
+ {
+ return $this->dispatcher->getMiddleware($controller, $method);
+ }
+}
diff --git a/src/Sentry/Laravel/Tracing/Routing/TracingRoutingDispatcher.php b/src/Sentry/Laravel/Tracing/Routing/TracingRoutingDispatcher.php
new file mode 100644
index 00000000..98212973
--- /dev/null
+++ b/src/Sentry/Laravel/Tracing/Routing/TracingRoutingDispatcher.php
@@ -0,0 +1,36 @@
+getSpan();
+
+ // When there is no active transaction we can skip doing anything and just immediately return the callable
+ if ($parentSpan === null) {
+ return $dispatch();
+ }
+
+ $context = new SpanContext;
+ $context->setOp('http.route');
+ $context->setDescription($route->getActionName());
+
+ $span = $parentSpan->startChild($context);
+
+ SentrySdk::getCurrentHub()->setSpan($span);
+
+ try {
+ return $dispatch();
+ } finally {
+ $span->finish();
+
+ SentrySdk::getCurrentHub()->setSpan($parentSpan);
+ }
+ }
+}
diff --git a/src/Sentry/Laravel/Tracing/ServiceProvider.php b/src/Sentry/Laravel/Tracing/ServiceProvider.php
index 336e6025..cd56d2d4 100644
--- a/src/Sentry/Laravel/Tracing/ServiceProvider.php
+++ b/src/Sentry/Laravel/Tracing/ServiceProvider.php
@@ -2,14 +2,20 @@
namespace Sentry\Laravel\Tracing;
+use Illuminate\Contracts\Container\BindingResolutionException;
+use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Http\Kernel as HttpKernelInterface;
use Illuminate\Contracts\View\Engine;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
+use Illuminate\Routing\Contracts\CallableDispatcher;
+use Illuminate\Routing\Contracts\ControllerDispatcher;
use Illuminate\View\Engines\EngineResolver;
use Illuminate\View\Factory as ViewFactory;
use InvalidArgumentException;
use Sentry\Laravel\BaseServiceProvider;
+use Sentry\Laravel\Tracing\Routing\TracingCallableDispatcherTracing;
+use Sentry\Laravel\Tracing\Routing\TracingControllerDispatcherTracing;
use Sentry\Serializer\RepresentationSerializer;
class ServiceProvider extends BaseServiceProvider
@@ -27,6 +33,8 @@ public function boot(): void
$this->bindViewEngine($tracingConfig);
+ $this->decorateRoutingDispatchers();
+
if ($this->app->bound(HttpKernelInterface::class)) {
/** @var \Illuminate\Foundation\Http\Kernel $httpKernel */
$httpKernel = $this->app->make(HttpKernelInterface::class);
@@ -59,17 +67,21 @@ public function register(): void
private function bindEvents(array $tracingConfig): void
{
$handler = new EventHandler(
- $this->app,
- $this->app->make(BacktraceHelper::class),
- $tracingConfig
+ $tracingConfig,
+ $this->app->make(BacktraceHelper::class)
);
- $handler->subscribe();
+ try {
+ /** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
+ $dispatcher = $this->app->make(Dispatcher::class);
+
+ $handler->subscribe($dispatcher);
- if ($this->app->bound('queue')) {
- $handler->subscribeQueueEvents(
- $this->app->make('queue')
- );
+ if ($this->app->bound('queue')) {
+ $handler->subscribeQueueEvents($dispatcher, $this->app->make('queue'));
+ }
+ } catch (BindingResolutionException $e) {
+ // If we cannot resolve the event dispatcher we also cannot listen to events
}
}
@@ -113,4 +125,15 @@ private function wrapViewEngine(Engine $realEngine): Engine
return new ViewEngineDecorator($realEngine, $viewFactory);
}
+
+ private function decorateRoutingDispatchers(): void
+ {
+ $this->app->extend(CallableDispatcher::class, static function (CallableDispatcher $dispatcher) {
+ return new TracingCallableDispatcherTracing($dispatcher);
+ });
+
+ $this->app->extend(ControllerDispatcher::class, static function (ControllerDispatcher $dispatcher) {
+ return new TracingControllerDispatcherTracing($dispatcher);
+ });
+ }
}
diff --git a/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php b/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php
index 79e262b5..a153243f 100644
--- a/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php
+++ b/src/Sentry/Laravel/Tracing/ViewEngineDecorator.php
@@ -4,7 +4,6 @@
use Illuminate\Contracts\View\Engine;
use Illuminate\View\Factory;
-use Sentry\Laravel\Integration;
use Sentry\SentrySdk;
use Sentry\Tracing\SpanContext;
@@ -29,7 +28,7 @@ public function __construct(Engine $engine, Factory $viewFactory)
*/
public function get($path, array $data = []): string
{
- $parentSpan = Integration::currentTracingSpan();
+ $parentSpan = SentrySdk::getCurrentHub()->getSpan();
if ($parentSpan === null) {
return $this->engine->get($path, $data);
diff --git a/test/Sentry/Tracing/EventHandlerTest.php b/test/Sentry/Tracing/EventHandlerTest.php
index f71faac8..4e67b9cb 100644
--- a/test/Sentry/Tracing/EventHandlerTest.php
+++ b/test/Sentry/Tracing/EventHandlerTest.php
@@ -17,7 +17,7 @@ public function test_missing_event_handler_throws_exception()
{
$this->safeExpectException(RuntimeException::class);
- $handler = new EventHandler($this->app, $this->app->make(BacktraceHelper::class), []);
+ $handler = new EventHandler([], $this->app->make(BacktraceHelper::class));
$handler->thisIsNotAHandlerAndShouldThrowAnException();
}
@@ -31,7 +31,7 @@ public function test_all_mapped_event_handlers_exist()
private function tryAllEventHandlerMethods(array $methods): void
{
- $handler = new EventHandler($this->app, $this->app->make(BacktraceHelper::class), []);
+ $handler = new EventHandler([], $this->app->make(BacktraceHelper::class));
$methods = array_map(static function ($method) {
return "{$method}Handler";