Skip to content

Commit 9ddda95

Browse files
committed
Add spans for Redis commands
1 parent 8dfc097 commit 9ddda95

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

config/sentry.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
// Capture views as spans
4848
'views' => true,
4949

50+
// Capture Redis operations as spans (this enables Redis events in Laravel)
51+
'redis_commands' => env('SENTRY_TRACE_REDIS_COMMANDS', false),
52+
53+
// Try to find out where the Redis command originated from and add it to the command spans
54+
'redis_origin' => true,
55+
5056
// Capture HTTP client requests as spans
5157
'http_client_requests' => true,
5258

src/Sentry/Laravel/Tracing/EventHandler.php

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Illuminate\Queue\Events as QueueEvents;
1010
use Illuminate\Queue\Queue;
1111
use Illuminate\Queue\QueueManager;
12+
use Illuminate\Redis\Events as RedisEvents;
1213
use Illuminate\Routing\Events as RoutingEvents;
1314
use RuntimeException;
1415
use Sentry\Laravel\Integration;
@@ -31,6 +32,7 @@ class EventHandler
3132
*/
3233
protected static $eventHandlerMap = [
3334
RoutingEvents\RouteMatched::class => 'routeMatched',
35+
RedisEvents\CommandExecuted::class => 'redisCommandExecuted',
3436
DatabaseEvents\QueryExecuted::class => 'queryExecuted',
3537
HttpClientEvents\RequestSending::class => 'httpClientRequestSending',
3638
HttpClientEvents\ResponseReceived::class => 'httpClientResponseReceived',
@@ -59,12 +61,26 @@ class EventHandler
5961
private $traceSqlQueries;
6062

6163
/**
62-
* Indicates if we should we add SQL query origin data to query spans.
64+
* Indicates if we should we add SQL query origin data to the query spans.
6365
*
6466
* @var bool
6567
*/
6668
private $traceSqlQueryOrigins;
6769

70+
/**
71+
* Indicates if we should we add Redis commands as spans.
72+
*
73+
* @var bool
74+
*/
75+
private $traceRedisCommands;
76+
77+
/**
78+
* Indicates if we should we add Redis command origin data to the command spans.
79+
*
80+
* @var bool
81+
*/
82+
private $traceRedisCommandOrigins;
83+
6884
/**
6985
* Indicates if we should trace queue job spans.
7086
*
@@ -115,6 +131,9 @@ public function __construct(array $config, BacktraceHelper $backtraceHelper)
115131
$this->traceSqlQueries = ($config['sql_queries'] ?? true) === true;
116132
$this->traceSqlQueryOrigins = ($config['sql_origin'] ?? true) === true;
117133

134+
$this->traceRedisCommands = ($config['redis_commands'] ?? false) === true;
135+
$this->traceRedisCommandOrigins = ($config['redis_origin'] ?? true) === true;
136+
118137
$this->traceHttpClientRequests = ($config['http_client_requests'] ?? true) === true;
119138

120139
$this->traceQueueJobs = ($config['queue_jobs'] ?? false) === true;
@@ -128,6 +147,7 @@ public function __construct(array $config, BacktraceHelper $backtraceHelper)
128147
*
129148
* @uses self::routeMatchedHandler()
130149
* @uses self::queryExecutedHandler()
150+
* @uses self::redisCommandExecutedHandler()
131151
* @uses self::transactionBeginningHandler()
132152
* @uses self::transactionCommittedHandler()
133153
* @uses self::transactionRolledBackHandler()
@@ -176,7 +196,7 @@ public function subscribeQueueEvents(Dispatcher $dispatcher, QueueManager $queue
176196
* Pass through the event and capture any errors.
177197
*
178198
* @param string $method
179-
* @param array $arguments
199+
* @param array $arguments
180200
*/
181201
public function __call(string $method, array $arguments)
182202
{
@@ -227,7 +247,7 @@ protected function queryExecutedHandler(DatabaseEvents\QueryExecuted $query): vo
227247
$context->setEndTimestamp($context->getStartTimestamp() + $query->time / 1000);
228248

229249
if ($this->traceSqlQueryOrigins) {
230-
$queryOrigin = $this->resolveQueryOriginFromBacktrace();
250+
$queryOrigin = $this->resolveEventOriginFromBacktrace();
231251

232252
if ($queryOrigin !== null) {
233253
$context->setData(['sql.origin' => $queryOrigin]);
@@ -238,11 +258,11 @@ protected function queryExecutedHandler(DatabaseEvents\QueryExecuted $query): vo
238258
}
239259

240260
/**
241-
* Try to find the origin of the SQL query that was just executed.
261+
* Try to find the origin of the event we are currently recording.
242262
*
243263
* @return string|null
244264
*/
245-
private function resolveQueryOriginFromBacktrace(): ?string
265+
private function resolveEventOriginFromBacktrace(): ?string
246266
{
247267
$firstAppFrame = $this->backtraceHelper->findFirstInAppFrameForBacktrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS));
248268

@@ -255,6 +275,45 @@ private function resolveQueryOriginFromBacktrace(): ?string
255275
return "{$filePath}:{$firstAppFrame->getLine()}";
256276
}
257277

278+
protected function redisCommandExecutedHandler(RedisEvents\CommandExecuted $event): void
279+
{
280+
if (!$this->traceRedisCommands) {
281+
return;
282+
}
283+
284+
$parentSpan = SentrySdk::getCurrentHub()->getSpan();
285+
286+
// If there is no tracing span active there is no need to handle the event
287+
if ($parentSpan === null) {
288+
return;
289+
}
290+
291+
// @TODO: Do we need/want to prefix all data with `db.redis`? It's already in the `db.redis` op so maybe it's not needed?
292+
$data = [
293+
'db.redis.connection' => $event->connectionName,
294+
// @TODO: Sending all the parameters should possibly be locked behind `send_default_pii`? It does contain sensitive data but also Redis integration is opt-in so maybe it's fine?
295+
'db.redis.parameters' => $event->parameters,
296+
];
297+
298+
$context = new SpanContext();
299+
$context->setOp('db.redis');
300+
$context->setDescription(strtoupper($event->command) . ' ' . ($event->parameters[0] ?? null));
301+
$context->setStartTimestamp(microtime(true) - $event->time / 1000);
302+
$context->setEndTimestamp($context->getStartTimestamp() + $event->time / 1000);
303+
304+
if ($this->traceRedisCommandOrigins) {
305+
$commandOrigin = $this->resolveEventOriginFromBacktrace();
306+
307+
if ($commandOrigin !== null) {
308+
$data['db.redis.origin'] = $commandOrigin;
309+
}
310+
}
311+
312+
$context->setData($data);
313+
314+
$parentSpan->startChild($context);
315+
}
316+
258317
protected function transactionBeginningHandler(DatabaseEvents\TransactionBeginning $event): void
259318
{
260319
$parentSpan = SentrySdk::getCurrentHub()->getSpan();
@@ -316,7 +375,7 @@ protected function httpClientResponseReceivedHandler(HttpClientEvents\ResponseRe
316375
if (!$this->traceHttpClientRequests) {
317376
return;
318377
}
319-
378+
320379
$span = $this->popSpan();
321380

322381
if ($span !== null) {

src/Sentry/Laravel/Tracing/ServiceProvider.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Illuminate\Contracts\View\Engine;
99
use Illuminate\Contracts\View\View;
1010
use Illuminate\Foundation\Http\Kernel as HttpKernel;
11+
use Illuminate\Redis\RedisManager;
1112
use Illuminate\Routing\Contracts\CallableDispatcher;
1213
use Illuminate\Routing\Contracts\ControllerDispatcher;
1314
use Illuminate\View\Engines\EngineResolver;
@@ -86,6 +87,13 @@ private function bindEvents(array $tracingConfig): void
8687
} catch (BindingResolutionException $e) {
8788
// If we cannot resolve the event dispatcher we also cannot listen to events
8889
}
90+
91+
// Enable Redis events in the framework if the user has enabled it
92+
if (($tracingConfig['redis_commands'] ?? false) === true) {
93+
$this->app->afterResolving(RedisManager::class, static function (RedisManager $redis): void {
94+
$redis->enableEvents();
95+
});
96+
}
8997
}
9098

9199
private function bindViewEngine($tracingConfig): void

0 commit comments

Comments
 (0)