99use Illuminate \Queue \Events as QueueEvents ;
1010use Illuminate \Queue \Queue ;
1111use Illuminate \Queue \QueueManager ;
12+ use Illuminate \Redis \Events as RedisEvents ;
1213use Illuminate \Routing \Events as RoutingEvents ;
1314use RuntimeException ;
1415use 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 ) {
0 commit comments