Skip to content

Commit dbd8b4f

Browse files
tm1000stayallive
andauthored
Add Laravel Octane support, fixes #479 (#495)
Co-authored-by: Alex Bouma <[email protected]>
1 parent 3d41ca5 commit dbd8b4f

File tree

7 files changed

+242
-106
lines changed

7 files changed

+242
-106
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- Add support for Laravel Octane (#495)
56
- Fix bug in Sentry log channel handler checking an undefined variable resulting in an error (#515)
67
- Add `action_level` configuration option for Sentry log channel which configures a Monolog `FingersCrossedHandler` (#516)
78

src/Sentry/Laravel/EventHandler.php

Lines changed: 209 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Illuminate\Queue\Events\JobProcessing;
1818
use Illuminate\Queue\Events\WorkerStopping;
1919
use Illuminate\Queue\QueueManager;
20+
use Laravel\Octane\Events as Octane;
2021
use Illuminate\Routing\Events\RouteMatched;
2122
use Illuminate\Routing\Route;
2223
use RuntimeException;
@@ -32,13 +33,13 @@ class EventHandler
3233
* @var array
3334
*/
3435
protected static $eventHandlerMap = [
35-
'router.matched' => 'routerMatched', // Until Laravel 5.1
36+
'router.matched' => 'routerMatched', // Until Laravel 5.1
3637
'Illuminate\Routing\Events\RouteMatched' => 'routeMatched', // Since Laravel 5.2
3738

38-
'illuminate.query' => 'query', // Until Laravel 5.1
39+
'illuminate.query' => 'query', // Until Laravel 5.1
3940
'Illuminate\Database\Events\QueryExecuted' => 'queryExecuted', // Since Laravel 5.2
4041

41-
'illuminate.log' => 'log', // Until Laravel 5.3
42+
'illuminate.log' => 'log', // Until Laravel 5.3
4243
'Illuminate\Log\Events\MessageLogged' => 'messageLogged', // Since Laravel 5.4
4344

4445
'Illuminate\Console\Events\CommandStarting' => 'commandStarting', // Since Laravel 5.5
@@ -60,10 +61,29 @@ class EventHandler
6061
* @var array
6162
*/
6263
protected static $queueEventHandlerMap = [
63-
'Illuminate\Queue\Events\JobProcessing' => 'queueJobProcessing', // Since Laravel 5.2
64-
'Illuminate\Queue\Events\JobProcessed' => 'queueJobProcessed', // Since Laravel 5.2
64+
'Illuminate\Queue\Events\JobProcessing' => 'queueJobProcessing', // Since Laravel 5.2
65+
'Illuminate\Queue\Events\JobProcessed' => 'queueJobProcessed', // Since Laravel 5.2
6566
'Illuminate\Queue\Events\JobExceptionOccurred' => 'queueJobExceptionOccurred', // Since Laravel 5.2
66-
'Illuminate\Queue\Events\WorkerStopping' => 'queueWorkerStopping', // Since Laravel 5.2
67+
'Illuminate\Queue\Events\WorkerStopping' => 'queueWorkerStopping', // Since Laravel 5.2
68+
];
69+
70+
/**
71+
* Map Octane event handlers to events.
72+
*
73+
* @var array
74+
*/
75+
protected static $octaneEventHandlerMap = [
76+
'Laravel\Octane\Events\RequestReceived' => 'octaneRequestReceived',
77+
'Laravel\Octane\Events\RequestTerminated' => 'octaneRequestTerminated',
78+
79+
'Laravel\Octane\Events\TaskReceived' => 'octaneTaskReceived',
80+
'Laravel\Octane\Events\TaskTerminated' => 'octaneTaskTerminated',
81+
82+
'Laravel\Octane\Events\TickReceived' => 'octaneTickReceived',
83+
'Laravel\Octane\Events\TickTerminated' => 'octaneTickTerminated',
84+
85+
'Laravel\Octane\Events\WorkerErrorOccurred' => 'octaneWorkerErrorOccurred',
86+
'Laravel\Octane\Events\WorkerStopping' => 'octaneWorkerStopping',
6787
];
6888

6989
/**
@@ -108,13 +128,34 @@ class EventHandler
108128
*/
109129
private $recordCommandInfo;
110130

131+
/**
132+
* Indicates if we should we add tick info to the breadcrumbs.
133+
*
134+
* @var bool
135+
*/
136+
private $recordOctaneTickInfo;
137+
138+
/**
139+
* Indicates if we should we add task info to the breadcrumbs.
140+
*
141+
* @var bool
142+
*/
143+
private $recordOctaneTaskInfo;
144+
111145
/**
112146
* Indicates if we pushed a scope for the queue.
113147
*
114148
* @var bool
115149
*/
116150
private $pushedQueueScope = false;
117151

152+
/**
153+
* Indicates if we pushed a scope for Octane.
154+
*
155+
* @var bool
156+
*/
157+
private $pushedOctaneScope = false;
158+
118159
/**
119160
* EventHandler constructor.
120161
*
@@ -125,11 +166,13 @@ public function __construct(Container $container, array $config)
125166
{
126167
$this->container = $container;
127168

128-
$this->recordSqlQueries = ($config['breadcrumbs.sql_queries'] ?? $config['breadcrumbs']['sql_queries'] ?? true) === true;
129-
$this->recordSqlBindings = ($config['breadcrumbs.sql_bindings'] ?? $config['breadcrumbs']['sql_bindings'] ?? false) === true;
130-
$this->recordLaravelLogs = ($config['breadcrumbs.logs'] ?? $config['breadcrumbs']['logs'] ?? true) === true;
131-
$this->recordQueueInfo = ($config['breadcrumbs.queue_info'] ?? $config['breadcrumbs']['queue_info'] ?? true) === true;
132-
$this->recordCommandInfo = ($config['breadcrumbs.command_info'] ?? $config['breadcrumbs']['command_info'] ?? true) === true;
169+
$this->recordSqlQueries = ($config['breadcrumbs.sql_queries'] ?? $config['breadcrumbs']['sql_queries'] ?? true) === true;
170+
$this->recordSqlBindings = ($config['breadcrumbs.sql_bindings'] ?? $config['breadcrumbs']['sql_bindings'] ?? false) === true;
171+
$this->recordLaravelLogs = ($config['breadcrumbs.logs'] ?? $config['breadcrumbs']['logs'] ?? true) === true;
172+
$this->recordQueueInfo = ($config['breadcrumbs.queue_info'] ?? $config['breadcrumbs']['queue_info'] ?? true) === true;
173+
$this->recordCommandInfo = ($config['breadcrumbs.command_info'] ?? $config['breadcrumbs']['command_info'] ?? true) === true;
174+
$this->recordOctaneTickInfo = ($config['breadcrumbs.octane_tick_info'] ?? $config['breadcrumbs']['octane_tick_info'] ?? true) === true;
175+
$this->recordOctaneTaskInfo = ($config['breadcrumbs.octane_task_info'] ?? $config['breadcrumbs']['octane_task_info'] ?? true) === true;
133176
}
134177

135178
/**
@@ -166,6 +209,23 @@ public function subscribeAuthEvents(): void
166209
}
167210
}
168211

212+
/**
213+
* Attach all queue event handlers.
214+
*/
215+
public function subscribeOctaneEvents(): void
216+
{
217+
/** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
218+
try {
219+
$dispatcher = $this->container->make(Dispatcher::class);
220+
221+
foreach (static::$octaneEventHandlerMap as $eventName => $handler) {
222+
$dispatcher->listen($eventName, [$this, $handler]);
223+
}
224+
} catch (BindingResolutionException $e) {
225+
// If we cannot resolve the event dispatcher we also cannot listen to events
226+
}
227+
}
228+
169229
/**
170230
* Attach all queue event handlers.
171231
*
@@ -174,8 +234,9 @@ public function subscribeAuthEvents(): void
174234
public function subscribeQueueEvents(QueueManager $queue): void
175235
{
176236
$queue->looping(function () {
177-
$this->cleanupScopeForQueuedJob();
178-
$this->afterQueuedJob();
237+
$this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedQueueScope);
238+
239+
$this->pushedQueueScope = false;
179240
});
180241

181242
/** @var \Illuminate\Contracts\Events\Dispatcher $dispatcher */
@@ -351,33 +412,6 @@ private function addLogBreadcrumb(string $level, ?string $message, array $contex
351412
));
352413
}
353414

354-
/**
355-
* Translates common log levels to Sentry breadcrumb levels.
356-
*
357-
* @param string $level Log level. Maybe any standard.
358-
*
359-
* @return string Breadcrumb level.
360-
*/
361-
protected function logLevelToBreadcrumbLevel(string $level): string
362-
{
363-
switch (strtolower($level)) {
364-
case 'debug':
365-
return Breadcrumb::LEVEL_DEBUG;
366-
case 'warning':
367-
return Breadcrumb::LEVEL_WARNING;
368-
case 'error':
369-
return Breadcrumb::LEVEL_ERROR;
370-
case 'critical':
371-
case 'alert':
372-
case 'emergency':
373-
return Breadcrumb::LEVEL_FATAL;
374-
case 'info':
375-
case 'notice':
376-
default:
377-
return Breadcrumb::LEVEL_INFO;
378-
}
379-
}
380-
381415
/**
382416
* Since Laravel 5.3
383417
*
@@ -416,16 +450,20 @@ protected function authenticatedHandler(Authenticated $event)
416450
*/
417451
protected function queueJobProcessingHandler(JobProcessing $event)
418452
{
419-
$this->prepareScopeForQueuedJob();
453+
$this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedQueueScope);
454+
455+
$this->prepareScopeForTaskWithinLongRunningProcess();
456+
457+
$this->pushedQueueScope = true;
420458

421459
if (!$this->recordQueueInfo) {
422460
return;
423461
}
424462

425463
$job = [
426-
'job' => $event->job->getName(),
427-
'queue' => $event->job->getQueue(),
428-
'attempts' => $event->job->attempts(),
464+
'job' => $event->job->getName(),
465+
'queue' => $event->job->getQueue(),
466+
'attempts' => $event->job->attempts(),
429467
'connection' => $event->connectionName,
430468
];
431469

@@ -450,7 +488,7 @@ protected function queueJobProcessingHandler(JobProcessing $event)
450488
*/
451489
protected function queueJobExceptionOccurredHandler(JobExceptionOccurred $event)
452490
{
453-
$this->afterQueuedJob();
491+
$this->afterTaskWithinLongRunningProcess();
454492
}
455493

456494
/**
@@ -460,7 +498,7 @@ protected function queueJobExceptionOccurredHandler(JobExceptionOccurred $event)
460498
*/
461499
protected function queueJobProcessedHandler(JobProcessed $event)
462500
{
463-
$this->afterQueuedJob();
501+
$this->afterTaskWithinLongRunningProcess();
464502
}
465503

466504
/**
@@ -531,34 +569,149 @@ protected function commandFinishedHandler(CommandFinished $event)
531569
Integration::flushEvents();
532570
}
533571

534-
private function afterQueuedJob(): void
572+
protected function octaneRequestReceivedHandler(Octane\RequestReceived $event): void
573+
{
574+
$this->prepareScopeForOctane();
575+
}
576+
577+
protected function octaneRequestTerminatedHandler(Octane\RequestTerminated $event): void
578+
{
579+
$this->cleanupScopeForOctane();
580+
}
581+
582+
protected function octaneTaskReceivedHandler(Octane\TaskReceived $event): void
583+
{
584+
$this->prepareScopeForOctane();
585+
586+
if (!$this->recordOctaneTaskInfo) {
587+
return;
588+
}
589+
590+
Integration::addBreadcrumb(new Breadcrumb(
591+
Breadcrumb::LEVEL_INFO,
592+
Breadcrumb::TYPE_DEFAULT,
593+
'octane.task',
594+
'Processing Octane task'
595+
));
596+
}
597+
598+
protected function octaneTaskTerminatedHandler(Octane\TaskTerminated $event): void
599+
{
600+
$this->cleanupScopeForOctane();
601+
}
602+
603+
protected function octaneTickReceivedHandler(Octane\TickReceived $event): void
604+
{
605+
$this->prepareScopeForOctane();
606+
607+
if (!$this->recordOctaneTickInfo) {
608+
return;
609+
}
610+
611+
Integration::addBreadcrumb(new Breadcrumb(
612+
Breadcrumb::LEVEL_INFO,
613+
Breadcrumb::TYPE_DEFAULT,
614+
'octane.tick',
615+
'Processing Octane tick'
616+
));
617+
}
618+
619+
protected function octaneTickTerminatedHandler(Octane\TickTerminated $event): void
620+
{
621+
$this->cleanupScopeForOctane();
622+
}
623+
624+
protected function octaneWorkerErrorOccurredHandler(Octane\WorkerErrorOccurred $event): void
625+
{
626+
$this->afterTaskWithinLongRunningProcess();
627+
}
628+
629+
protected function octaneWorkerStoppingHandler(Octane\WorkerStopping $event): void
630+
{
631+
$this->afterTaskWithinLongRunningProcess();
632+
}
633+
634+
private function prepareScopeForOctane(): void
635+
{
636+
$this->cleanupScopeForOctane();
637+
638+
$this->prepareScopeForTaskWithinLongRunningProcess();
639+
640+
$this->pushedOctaneScope = true;
641+
}
642+
643+
private function cleanupScopeForOctane(): void
644+
{
645+
$this->cleanupScopeForTaskWithinLongRunningProcessWhen($this->pushedOctaneScope);
646+
647+
$this->pushedOctaneScope = false;
648+
}
649+
650+
/**
651+
* Translates common log levels to Sentry breadcrumb levels.
652+
*
653+
* @param string $level Log level. Maybe any standard.
654+
*
655+
* @return string Breadcrumb level.
656+
*/
657+
private function logLevelToBreadcrumbLevel(string $level): string
658+
{
659+
switch (strtolower($level)) {
660+
case 'debug':
661+
return Breadcrumb::LEVEL_DEBUG;
662+
case 'warning':
663+
return Breadcrumb::LEVEL_WARNING;
664+
case 'error':
665+
return Breadcrumb::LEVEL_ERROR;
666+
case 'critical':
667+
case 'alert':
668+
case 'emergency':
669+
return Breadcrumb::LEVEL_FATAL;
670+
case 'info':
671+
case 'notice':
672+
default:
673+
return Breadcrumb::LEVEL_INFO;
674+
}
675+
}
676+
677+
/**
678+
* Should be called after a task within a long running process has ended so events can be flushed.
679+
*/
680+
private function afterTaskWithinLongRunningProcess(): void
535681
{
536682
// Flush any and all events that were possibly generated by queue jobs
537683
Integration::flushEvents();
538684
}
539685

540-
private function prepareScopeForQueuedJob(): void
686+
/**
687+
* Should be called before starting a task within a long running process, this is done to prevent
688+
* the task to have effect on the scope for the next task to run within the long running process.
689+
*/
690+
private function prepareScopeForTaskWithinLongRunningProcess(): void
541691
{
542-
$this->cleanupScopeForQueuedJob();
543-
544692
SentrySdk::getCurrentHub()->pushScope();
545693

546-
$this->pushedQueueScope = true;
547-
548694
// When a job starts, we want to make sure the scope is cleared of breadcrumbs
549695
SentrySdk::getCurrentHub()->configureScope(static function (Scope $scope) {
550696
$scope->clearBreadcrumbs();
551697
});
552698
}
553699

554-
private function cleanupScopeForQueuedJob(): void
700+
/**
701+
* Cleanup a previously prepared scope.
702+
*
703+
* @param bool $when Only cleanup the scope when this is true.
704+
*
705+
* @see prepareScopeForTaskWithinLongRunningProcess
706+
*/
707+
private function cleanupScopeForTaskWithinLongRunningProcessWhen(bool $when): void
555708
{
556-
if (!$this->pushedQueueScope) {
709+
if (!$when) {
557710
return;
558711
}
559712

560-
SentrySdk::getCurrentHub()->popScope();
713+
$this->afterTaskWithinLongRunningProcess();
561714

562-
$this->pushedQueueScope = false;
715+
SentrySdk::getCurrentHub()->popScope();
563716
}
564717
}

0 commit comments

Comments
 (0)