diff --git a/composer.json b/composer.json index cd152ece..e95d2a4e 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "require": { "php": "^7.2 | ^8.0", "illuminate/support": "5.0 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0", - "sentry/sentry": "^3.3", + "sentry/sentry": "^3.9", "sentry/sdk": "^3.1", "symfony/psr-http-message-bridge": "^1.0 | ^2.0", "nyholm/psr7": "^1.0" diff --git a/src/Sentry/Laravel/Console/TestCommand.php b/src/Sentry/Laravel/Console/TestCommand.php index 0623d5fd..7cf52127 100644 --- a/src/Sentry/Laravel/Console/TestCommand.php +++ b/src/Sentry/Laravel/Console/TestCommand.php @@ -140,6 +140,7 @@ public function log($level, $message, array $context = []): void $transactionContext = new TransactionContext(); $transactionContext->setSampled(true); $transactionContext->setName('Sentry Test Transaction'); + $transactionContext->setSource(TransactionSource::custom()); $transactionContext->setOp('sentry.test'); $transaction = $hub->startTransaction($transactionContext); diff --git a/src/Sentry/Laravel/EventHandler.php b/src/Sentry/Laravel/EventHandler.php index f9f32bfe..8db2bb1d 100644 --- a/src/Sentry/Laravel/EventHandler.php +++ b/src/Sentry/Laravel/EventHandler.php @@ -283,7 +283,7 @@ public function __call($method, $arguments) */ protected function routerMatchedHandler(Route $route) { - $routeName = Integration::extractNameForRoute($route); + [$routeName] = Integration::extractNameAndSourceForRoute($route); Integration::addBreadcrumb(new Breadcrumb( Breadcrumb::LEVEL_INFO, diff --git a/src/Sentry/Laravel/Integration.php b/src/Sentry/Laravel/Integration.php index f400aaf5..905d3622 100644 --- a/src/Sentry/Laravel/Integration.php +++ b/src/Sentry/Laravel/Integration.php @@ -6,6 +6,7 @@ use Illuminate\Support\Str; use Sentry\SentrySdk; use Sentry\Tracing\Span; +use Sentry\Tracing\TransactionSource; use function Sentry\addBreadcrumb; use function Sentry\configureScope; use Sentry\Breadcrumb; @@ -122,27 +123,48 @@ public static function flushEvents(): void * @param \Illuminate\Routing\Route $route * * @return string + * + * @internal This helper is used in various places to extra meaninful info from a Laravel Route object. + * @deprecated This will be removed in version 3.0, use `extractNameAndSourceForRoute` instead. */ public static function extractNameForRoute(Route $route): string { + return self::extractNameAndSourceForRoute($route)[0]; + } + + /** + * Extract the readable name for a route and the transaction source for where that route name came from. + * + * @param \Illuminate\Routing\Route $route + * + * @return array{0: string, 1: \Sentry\Tracing\TransactionSource} + * + * @internal This helper is used in various places to extra meaninful info from a Laravel Route object. + */ + public static function extractNameAndSourceForRoute(Route $route): array + { + $source = null; $routeName = null; - // someaction (route name/alias) + // some.action (route name/alias) if ($route->getName()) { + $source = TransactionSource::component(); $routeName = self::extractNameForNamedRoute($route->getName()); } // Some\Controller@someAction (controller action) if (empty($routeName) && $route->getActionName()) { + $source = TransactionSource::component(); $routeName = self::extractNameForActionRoute($route->getActionName()); } - // /someaction // Fallback to the url + // /some/{action} // Fallback to the route uri (with parameter placeholders) if (empty($routeName) || $routeName === 'Closure') { + $source = TransactionSource::route(); $routeName = '/' . ltrim($route->uri(), '/'); } - return $routeName; + return [$routeName, $source]; } /** @@ -152,24 +174,45 @@ public static function extractNameForRoute(Route $route): string * @param string $path The path of the request * * @return string + * + * @internal This helper is used in various places to extra meaninful info from a Lumen route data. + * @deprecated This will be removed in version 3.0, use `extractNameAndSourceForLumenRoute` instead. */ public static function extractNameForLumenRoute(array $routeData, string $path): string { + return self::extractNameAndSourceForLumenRoute($routeData, $path)[0]; + } + + /** + * Extract the readable name for a Lumen route and the transaction source for where that route name came from. + * + * @param array $routeData The array of route data + * @param string $path The path of the request + * + * @return array{0: string, 1: \Sentry\Tracing\TransactionSource} + * + * @internal This helper is used in various places to extra meaninful info from a Lumen route data. + */ + public static function extractNameAndSourceForLumenRoute(array $routeData, string $path): array + { + $source = null; $routeName = null; $route = $routeData[1] ?? []; - // someaction (route name/alias) + // some.action (route name/alias) if (!empty($route['as'])) { + $source = TransactionSource::component(); $routeName = self::extractNameForNamedRoute($route['as']); } // Some\Controller@someAction (controller action) if (empty($routeName) && !empty($route['uses'])) { + $source = TransactionSource::component(); $routeName = self::extractNameForActionRoute($route['uses']); } - // /someaction // Fallback to the url + // /some/{action} // Fallback to the route uri (with parameter placeholders) if (empty($routeName) || $routeName === 'Closure') { $routeUri = array_reduce( array_keys($routeData[2]), @@ -179,10 +222,11 @@ static function ($carry, $key) use ($routeData) { $path ); + $source = TransactionSource::url(); $routeName = '/' . ltrim($routeUri, '/'); } - return $routeName; + return [$routeName, $source]; } /** @@ -247,7 +291,25 @@ public static function sentryTracingMeta(): string } $content = sprintf('', $span->toTraceparent()); - // $content .= sprintf('', $span->getDescription()); + + return $content; + } + + /** + * Retrieve the meta tags with baggage information to link this request to front-end requests. + * This propagates the Dynamic Sampling Context. + * + * @return string + */ + public static function sentryBaggageMeta(): string + { + $span = self::currentTracingSpan(); + + if ($span === null) { + return ''; + } + + $content = sprintf('', $span->toBaggage()); return $content; } diff --git a/src/Sentry/Laravel/Tracing/EventHandler.php b/src/Sentry/Laravel/Tracing/EventHandler.php index 054dc62e..1fafb3bd 100644 --- a/src/Sentry/Laravel/Tracing/EventHandler.php +++ b/src/Sentry/Laravel/Tracing/EventHandler.php @@ -16,11 +16,14 @@ use Sentry\Tracing\SpanContext; use Sentry\Tracing\SpanStatus; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionSource; class EventHandler { public const QUEUE_PAYLOAD_TRACE_PARENT_DATA = 'sentry_trace_parent_data'; + public const QUEUE_PAYLOAD_BAGGAGE_DATA = 'sentry_baggage_data'; + /** * Map event handlers to events. * @@ -153,6 +156,7 @@ public function subscribeQueueEvents(QueueManager $queue): void if ($currentSpan !== null && $payload !== null) { $payload[self::QUEUE_PAYLOAD_TRACE_PARENT_DATA] = $currentSpan->toTraceparent(); + $payload[self::QUEUE_PAYLOAD_BAGGAGE_DATA] = $currentSpan->toBaggage(); } return $payload; @@ -293,11 +297,10 @@ protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event) } if ($parentSpan === null) { + $baggage = $event->job->payload()[self::QUEUE_PAYLOAD_BAGGAGE_DATA] ?? null; $traceParent = $event->job->payload()[self::QUEUE_PAYLOAD_TRACE_PARENT_DATA] ?? null; - $context = $traceParent === null - ? new TransactionContext - : TransactionContext::fromSentryTrace($traceParent); + $context = TransactionContext::fromHeaders($traceParent ?? '', $baggage ?? ''); // If the parent transaction was not sampled we also stop the queue job from being recorded if ($context->getParentSampled() === false) { @@ -325,6 +328,7 @@ protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event) if ($context instanceof TransactionContext) { $context->setName($resolvedJobName ?? $event->job->getName()); + $context->setSource(TransactionSource::task()); } $context->setOp('queue.process'); diff --git a/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php b/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php index a05f113c..dc443a35 100644 --- a/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php +++ b/src/Sentry/Laravel/Tracing/Integrations/LighthouseIntegration.php @@ -13,6 +13,7 @@ use Sentry\Laravel\Integration; use Sentry\SentrySdk; use Sentry\Tracing\SpanContext; +use Sentry\Tracing\TransactionSource; class LighthouseIntegration implements IntegrationInterface { @@ -157,7 +158,7 @@ private function updateTransactionName(): void return; } - array_walk($groupedOperations, static function (array &$operations, string $operationType) { + array_walk($groupedOperations, static function (&$operations, string $operationType) { sort($operations, SORT_STRING); $operations = "{$operationType}{" . implode(',', $operations) . '}'; @@ -168,6 +169,7 @@ private function updateTransactionName(): void $transactionName = 'lighthouse?' . implode('&', $groupedOperations); $transaction->setName($transactionName); + $transaction->getMetadata()->setSource(TransactionSource::custom()); Integration::setTransaction($transactionName); } diff --git a/src/Sentry/Laravel/Tracing/Middleware.php b/src/Sentry/Laravel/Tracing/Middleware.php index 24eb8341..589d1353 100644 --- a/src/Sentry/Laravel/Tracing/Middleware.php +++ b/src/Sentry/Laravel/Tracing/Middleware.php @@ -11,6 +11,7 @@ use Sentry\Tracing\Span; use Sentry\Tracing\SpanContext; use Sentry\Tracing\TransactionContext; +use Sentry\Tracing\TransactionSource; use Symfony\Component\HttpFoundation\Response; class Middleware @@ -102,11 +103,11 @@ public function setBootedTimestamp(?float $timestamp = null): void private function startTransaction(Request $request, HubInterface $sentry): void { $requestStartTime = $request->server('REQUEST_TIME_FLOAT', microtime(true)); - $sentryTraceHeader = $request->header('sentry-trace'); - $context = $sentryTraceHeader - ? TransactionContext::fromSentryTrace($sentryTraceHeader) - : new TransactionContext; + $context = TransactionContext::fromHeaders( + $request->header('sentry-trace', ''), + $request->header('baggage', '') + ); $context->setOp('http.server'); $context->setData([ @@ -180,9 +181,9 @@ private function hydrateRequestData(Request $request): void $route = $request->route(); if ($route instanceof Route) { - $this->updateTransactionNameIfDefault( - Integration::extractNameForRoute($route) - ); + [$transactionName, $transactionSource] = Integration::extractNameAndSourceForRoute($route); + + $this->updateTransactionNameIfDefault($transactionName, $transactionSource); $this->transaction->setData([ 'name' => $route->getName(), @@ -190,9 +191,9 @@ private function hydrateRequestData(Request $request): void 'method' => $request->getMethod(), ]); } elseif (is_array($route) && count($route) === 3) { - $this->updateTransactionNameIfDefault( - Integration::extractNameForLumenRoute($route, $request->path()) - ); + [$transactionName, $transactionSource] = Integration::extractNameAndSourceForLumenRoute($route, $request->path()); + + $this->updateTransactionNameIfDefault($transactionName, $transactionSource); $action = $route[1] ?? []; @@ -203,7 +204,7 @@ private function hydrateRequestData(Request $request): void ]); } - $this->updateTransactionNameIfDefault('/' . ltrim($request->path(), '/')); + $this->updateTransactionNameIfDefault('/' . ltrim($request->path(), '/'), TransactionSource::url()); } private function hydrateResponseData(Response $response): void @@ -211,7 +212,7 @@ private function hydrateResponseData(Response $response): void $this->transaction->setHttpStatus($response->getStatusCode()); } - private function updateTransactionNameIfDefault(?string $name): void + private function updateTransactionNameIfDefault(?string $name, ?TransactionSource $source): void { // Ignore empty names (and `null`) for caller convenience if (empty($name)) { @@ -226,5 +227,6 @@ private function updateTransactionNameIfDefault(?string $name): void } $this->transaction->setName($name); + $this->transaction->getMetadata()->setSource($source ?? TransactionSource::custom()); } }