Skip to content

Commit 79a135d

Browse files
authored
Improve tracing (#580)
1 parent 3cfb462 commit 79a135d

File tree

7 files changed

+145
-209
lines changed

7 files changed

+145
-209
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@
33
## Unreleased
44

55
- Drop support for Laravel Lumen (#579)
6+
- Set the tracing transaction name on the `Illuminate\Routing\Events\RouteMatched` instead of at the end of the request (#580)
7+
- Remove `Sentry\Integration::extractNameForRoute()`, it's alternative `Sentry\Integration::extractNameAndSourceForRoute()` is marked as `@internal` (#580)
68
- Drop support for Laravel 5.x (#581)
79

810
## 2.14.1
911

1012
- Fix not setting the correct SDK ID and version when running the `sentry:test` command (#582)
13+
- Transaction names now only show the parameterized URL (`/some/{route}`) instead of the route name or controller class (#583)
1114

1215
## 2.14.0
1316

config/sentry.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,14 @@
4545

4646
// Indicates if the tracing integrations supplied by Sentry should be loaded
4747
'default_integrations' => true,
48+
49+
// Indicates that requests without a matching route should be traced
50+
'missing_routes' => false,
4851
],
4952

5053
// @see: https://docs.sentry.io/platforms/php/configuration/options/#send-default-pii
5154
'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false),
5255

5356
'traces_sample_rate' => (float)(env('SENTRY_TRACES_SAMPLE_RATE', 0.0)),
5457

55-
'controllers_base_namespace' => env('SENTRY_CONTROLLERS_BASE_NAMESPACE', 'App\\Http\\Controllers'),
56-
5758
];

src/Sentry/Laravel/Integration.php

Lines changed: 17 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Support\Str;
77
use Sentry\SentrySdk;
88
use Sentry\Tracing\Span;
9+
use Sentry\Tracing\Transaction;
910
use Sentry\Tracing\TransactionSource;
1011
use function Sentry\addBreadcrumb;
1112
use function Sentry\configureScope;
@@ -21,11 +22,6 @@ class Integration implements IntegrationInterface
2122
*/
2223
private static $transaction;
2324

24-
/**
25-
* @var null|string
26-
*/
27-
private static $baseControllerNamespace;
28-
2925
/**
3026
* {@inheritdoc}
3127
*/
@@ -94,14 +90,6 @@ public static function setTransaction(?string $transaction): void
9490
self::$transaction = $transaction;
9591
}
9692

97-
/**
98-
* @param null|string $namespace
99-
*/
100-
public static function setControllersBaseNamespace(?string $namespace): void
101-
{
102-
self::$baseControllerNamespace = $namespace !== null ? trim($namespace, '\\') : null;
103-
}
104-
10593
/**
10694
* Block until all async events are processed for the HTTP transport.
10795
*
@@ -117,21 +105,6 @@ public static function flushEvents(): void
117105
}
118106
}
119107

120-
/**
121-
* Extract the readable name for a route.
122-
*
123-
* @param \Illuminate\Routing\Route $route
124-
*
125-
* @return string
126-
*
127-
* @internal This helper is used in various places to extra meaninful info from a Laravel Route object.
128-
* @deprecated This will be removed in version 3.0, use `extractNameAndSourceForRoute` instead.
129-
*/
130-
public static function extractNameForRoute(Route $route): string
131-
{
132-
return self::extractNameAndSourceForRoute($route)[0];
133-
}
134-
135108
/**
136109
* Extract the readable name for a route and the transaction source for where that route name came from.
137110
*
@@ -143,75 +116,10 @@ public static function extractNameForRoute(Route $route): string
143116
*/
144117
public static function extractNameAndSourceForRoute(Route $route): array
145118
{
146-
$source = null;
147-
$routeName = null;
148-
149-
// some.action (route name/alias)
150-
if ($route->getName()) {
151-
$source = TransactionSource::component();
152-
$routeName = self::extractNameForNamedRoute($route->getName());
153-
}
154-
155-
// Some\Controller@someAction (controller action)
156-
if (empty($routeName) && $route->getActionName()) {
157-
$source = TransactionSource::component();
158-
$routeName = self::extractNameForActionRoute($route->getActionName());
159-
}
160-
161-
// /some/{action} // Fallback to the route uri (with parameter placeholders)
162-
if (empty($routeName) || $routeName === 'Closure') {
163-
$source = TransactionSource::route();
164-
$routeName = '/' . ltrim($route->uri(), '/');
165-
}
166-
167-
return [$routeName, $source];
168-
}
169-
170-
/**
171-
* Take a route name and return it only if it's a usable route name.
172-
*
173-
* @param string $name
174-
*
175-
* @return string|null
176-
*/
177-
private static function extractNameForNamedRoute(string $name): ?string
178-
{
179-
// Laravel 7 route caching generates a route names if the user didn't specify one
180-
// theirselfs to optimize route matching. These route names are useless to the
181-
// developer so if we encounter a generated route name we discard the value
182-
if (Str::contains($name, 'generated::')) {
183-
return null;
184-
}
185-
186-
// If the route name ends with a `.` we assume an incomplete group name prefix
187-
// we discard this value since it will most likely not mean anything to the
188-
// developer and will be duplicated by other unnamed routes in the group
189-
if (Str::endsWith($name, '.')) {
190-
return null;
191-
}
192-
193-
return $name;
194-
}
195-
196-
/**
197-
* Take a controller action and strip away the base namespace if needed.
198-
*
199-
* @param string $action
200-
*
201-
* @return string
202-
*/
203-
private static function extractNameForActionRoute(string $action): string
204-
{
205-
$routeName = ltrim($action, '\\');
206-
207-
$baseNamespace = self::$baseControllerNamespace ?? '';
208-
209-
if (empty($baseNamespace)) {
210-
return $routeName;
211-
}
212-
213-
// Strip away the base namespace from the action name
214-
return ltrim(Str::after($routeName, $baseNamespace), '\\');
119+
return [
120+
'/' . ltrim($route->uri(), '/'),
121+
TransactionSource::route()
122+
];
215123
}
216124

217125
/**
@@ -258,6 +166,18 @@ public static function sentryBaggageMeta(): string
258166
return sprintf('<meta name="baggage" content="%s"/>', $span->toBaggage());
259167
}
260168

169+
/**
170+
* Get the current active tracing span from the scope.
171+
*
172+
* @return \Sentry\Tracing\Transaction|null
173+
*
174+
* @internal This is used internally as an easy way to retrieve the current active transaction.
175+
*/
176+
public static function currentTransaction(): ?Transaction
177+
{
178+
return SentrySdk::getCurrentHub()->getTransaction();
179+
}
180+
261181
/**
262182
* Get the current active tracing span from the scope.
263183
*

src/Sentry/Laravel/ServiceProvider.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ class ServiceProvider extends BaseServiceProvider
3333
'integrations',
3434
// This is kept for backwards compatibility and can be dropped in a future breaking release
3535
'breadcrumbs.sql_bindings',
36-
// The base namespace for controllers to strip of the beginning of controller class names
36+
37+
// This config option is no longer in use but to prevent errors when upgrading we leave it here to be discarded
3738
'controllers_base_namespace',
3839
];
3940

@@ -125,12 +126,6 @@ protected function registerArtisanCommands(): void
125126
*/
126127
protected function configureAndRegisterClient(): void
127128
{
128-
$userConfig = $this->getUserConfig();
129-
130-
if (isset($userConfig['controllers_base_namespace'])) {
131-
Integration::setControllersBaseNamespace($userConfig['controllers_base_namespace']);
132-
}
133-
134129
$this->app->bind(ClientBuilderInterface::class, function () {
135130
$basePath = base_path();
136131
$userConfig = $this->getUserConfig();
@@ -161,15 +156,15 @@ protected function configureAndRegisterClient(): void
161156
return $clientBuilder;
162157
});
163158

164-
$this->app->singleton(HubInterface::class, function ($app) {
159+
$this->app->singleton(HubInterface::class, function () {
165160
/** @var \Sentry\ClientBuilderInterface $clientBuilder */
166161
$clientBuilder = $this->app->make(ClientBuilderInterface::class);
167162

168163
$options = $clientBuilder->getOptions();
169164

170165
$userIntegrations = $this->resolveIntegrationsFromUserConfig();
171166

172-
$options->setIntegrations(function (array $integrations) use ($options, $userIntegrations, $app) {
167+
$options->setIntegrations(function (array $integrations) use ($options, $userIntegrations) {
173168
if ($options->hasDefaultIntegrations()) {
174169
// Remove the default error and fatal exception listeners to let Laravel handle those
175170
// itself. These event are still bubbling up through the documented changes in the users

src/Sentry/Laravel/Tracing/EventHandler.php

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
use Illuminate\Contracts\Container\Container;
88
use Illuminate\Contracts\Events\Dispatcher;
99
use Illuminate\Database\Events as DatabaseEvents;
10+
use Illuminate\Http\Client\Events as HttpClientEvents;
1011
use Illuminate\Queue\Events as QueueEvents;
1112
use Illuminate\Queue\Queue;
1213
use Illuminate\Queue\QueueManager;
14+
use Illuminate\Routing\Events as RoutingEvents;
1315
use RuntimeException;
1416
use Sentry\Laravel\Integration;
1517
use Sentry\SentrySdk;
@@ -30,7 +32,11 @@ class EventHandler
3032
* @var array
3133
*/
3234
protected static $eventHandlerMap = [
35+
RoutingEvents\RouteMatched::class => 'routeMatched',
3336
DatabaseEvents\QueryExecuted::class => 'queryExecuted',
37+
HttpClientEvents\RequestSending::class => 'httpClientRequestSending',
38+
HttpClientEvents\ResponseReceived::class => 'httpClientResponseReceived',
39+
HttpClientEvents\ConnectionFailed::class => 'httpClientConnectionFailed',
3440
];
3541

3642
/**
@@ -93,6 +99,20 @@ class EventHandler
9399
*/
94100
private $currentQueueJobSpan;
95101

102+
/**
103+
* Holds a reference to the parent http client request span.
104+
*
105+
* @var \Sentry\Tracing\Span|null
106+
*/
107+
private $parentHttpClientRequestSpan;
108+
109+
/**
110+
* Holds a reference to the current http client request span.
111+
*
112+
* @var \Sentry\Tracing\Span|null
113+
*/
114+
private $currentHttpClientRequestSpan;
115+
96116
/**
97117
* The backtrace helper.
98118
*
@@ -122,6 +142,7 @@ public function __construct(Container $container, BacktraceHelper $backtraceHelp
122142
/**
123143
* Attach all event handlers.
124144
*
145+
* @uses self::routeMatchedHandler()
125146
* @uses self::queryExecutedHandler()
126147
*/
127148
public function subscribe(): void
@@ -202,6 +223,20 @@ public function __call(string $method, array $arguments)
202223
}
203224
}
204225

226+
protected function routeMatchedHandler(RoutingEvents\RouteMatched $match): void
227+
{
228+
$transaction = Integration::currentTransaction();
229+
230+
if ($transaction === null) {
231+
return;
232+
}
233+
234+
[$transactionName, $transactionSource] = Integration::extractNameAndSourceForRoute($match->route);
235+
236+
$transaction->setName($transactionName);
237+
$transaction->getMetadata()->setSource($transactionSource);
238+
}
239+
205240
protected function queryExecutedHandler(DatabaseEvents\QueryExecuted $query): void
206241
{
207242
if (!$this->traceSqlQueries) {
@@ -250,6 +285,56 @@ private function resolveQueryOriginFromBacktrace(): ?string
250285
return "{$filePath}:{$firstAppFrame->getLine()}";
251286
}
252287

288+
protected function httpClientRequestSendingHandler(HttpClientEvents\RequestSending $event): void
289+
{
290+
$parentSpan = Integration::currentTracingSpan();
291+
292+
if ($parentSpan === null) {
293+
return;
294+
}
295+
296+
$context = new SpanContext;
297+
298+
$context->setOp('http.client');
299+
$context->setDescription($event->request->method() . ' ' . $event->request->url());
300+
$context->setStartTimestamp(microtime(true));
301+
302+
$this->currentHttpClientRequestSpan = $parentSpan->startChild($context);
303+
304+
$this->parentHttpClientRequestSpan = $parentSpan;
305+
306+
SentrySdk::getCurrentHub()->setSpan($this->currentHttpClientRequestSpan);
307+
}
308+
309+
protected function httpClientResponseReceivedHandler(HttpClientEvents\ResponseReceived $event): void
310+
{
311+
if ($this->currentHttpClientRequestSpan !== null) {
312+
$this->currentHttpClientRequestSpan->setHttpStatus($event->response->status());
313+
$this->afterHttpClientRequest();
314+
}
315+
}
316+
317+
protected function httpClientConnectionFailedHandler(HttpClientEvents\ConnectionFailed $event): void
318+
{
319+
if ($this->currentHttpClientRequestSpan !== null) {
320+
$this->currentHttpClientRequestSpan->setStatus(SpanStatus::internalError());
321+
$this->afterHttpClientRequest();
322+
}
323+
}
324+
325+
private function afterHttpClientRequest(): void
326+
{
327+
if ($this->currentHttpClientRequestSpan === null) {
328+
return;
329+
}
330+
331+
$this->currentHttpClientRequestSpan->finish();
332+
$this->currentHttpClientRequestSpan = null;
333+
334+
SentrySdk::getCurrentHub()->setSpan($this->parentHttpClientRequestSpan);
335+
$this->parentHttpClientRequestSpan = null;
336+
}
337+
253338
protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event): void
254339
{
255340
$parentSpan = Integration::currentTracingSpan();
@@ -278,24 +363,18 @@ protected function queueJobProcessingHandler(QueueEvents\JobProcessing $event):
278363
$context = new SpanContext;
279364
}
280365

366+
$resolvedJobName = $event->job->resolveName();
367+
281368
$job = [
282369
'job' => $event->job->getName(),
283370
'queue' => $event->job->getQueue(),
371+
'resolved' => $event->job->resolveName(),
284372
'attempts' => $event->job->attempts(),
285373
'connection' => $event->connectionName,
286374
];
287375

288-
// Resolve name exists only from Laravel 5.3+
289-
$resolvedJobName = method_exists($event->job, 'resolveName')
290-
? $event->job->resolveName()
291-
: null;
292-
293-
if ($resolvedJobName !== null) {
294-
$job['resolved'] = $resolvedJobName;
295-
}
296-
297376
if ($context instanceof TransactionContext) {
298-
$context->setName($resolvedJobName ?? $event->job->getName());
377+
$context->setName($resolvedJobName);
299378
$context->setSource(TransactionSource::task());
300379
}
301380

0 commit comments

Comments
 (0)