-
Notifications
You must be signed in to change notification settings - Fork 187
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #412 from facade/collect-job-info
Add support for collecting job info
- Loading branch information
Showing
5 changed files
with
360 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<?php | ||
|
||
namespace Facade\Ignition\JobRecorder; | ||
|
||
use Exception; | ||
use Illuminate\Contracts\Encryption\Encrypter; | ||
use Illuminate\Contracts\Foundation\Application; | ||
use Illuminate\Queue\Events\JobExceptionOccurred; | ||
use Illuminate\Queue\Jobs\Job; | ||
use Illuminate\Support\Str; | ||
use ReflectionClass; | ||
use ReflectionProperty; | ||
use RuntimeException; | ||
|
||
class JobRecorder | ||
{ | ||
/** @var \Illuminate\Contracts\Foundation\Application */ | ||
protected $app; | ||
|
||
/** @var \Illuminate\Queue\Jobs\Job|null */ | ||
protected $job = null; | ||
|
||
public function __construct(Application $app) | ||
{ | ||
$this->app = $app; | ||
} | ||
|
||
public function register(): self | ||
{ | ||
$this->app['events']->listen(JobExceptionOccurred::class, [$this, 'record']); | ||
|
||
return $this; | ||
} | ||
|
||
public function record(JobExceptionOccurred $event): void | ||
{ | ||
$this->job = $event->job; | ||
} | ||
|
||
public function toArray(): array | ||
{ | ||
if ($this->job === null) { | ||
return []; | ||
} | ||
|
||
return array_filter([ | ||
'name' => $this->job->resolveName(), | ||
'connection' => $this->job->getConnectionName(), | ||
'queue' => $this->job->getQueue(), | ||
'properties' => $this->getJobProperties(), | ||
]); | ||
} | ||
|
||
public function getJob(): ?Job | ||
{ | ||
return $this->job; | ||
} | ||
|
||
public function reset(): void | ||
{ | ||
$this->job = null; | ||
} | ||
|
||
protected function getJobProperties(): array | ||
{ | ||
$payload = $this->job->payload(); | ||
|
||
if (! array_key_exists('data', $payload)) { | ||
return []; | ||
} | ||
|
||
try { | ||
$job = $this->getCommand($payload['data']); | ||
} catch (Exception $exception) { | ||
return []; | ||
} | ||
|
||
$defaultProperties = [ | ||
'job', | ||
'closure', | ||
'connection', | ||
'queue', | ||
]; | ||
|
||
return collect((new ReflectionClass($job))->getProperties()) | ||
->reject(function (ReflectionProperty $property) use ($defaultProperties) { | ||
return in_array($property->name, $defaultProperties); | ||
}) | ||
->mapWithKeys(function (ReflectionProperty $property) use ($job) { | ||
$property->setAccessible(true); | ||
|
||
return [$property->name => $property->getValue($job)]; | ||
}) | ||
->toArray(); | ||
} | ||
|
||
// Taken from Illuminate\Queue\CallQueuedHandler | ||
protected function getCommand(array $data): object | ||
{ | ||
if (Str::startsWith($data['command'], 'O:')) { | ||
return unserialize($data['command']); | ||
} | ||
|
||
if ($this->app->bound(Encrypter::class)) { | ||
return unserialize($this->app[Encrypter::class]->decrypt($data['command'])); | ||
} | ||
|
||
throw new RuntimeException('Unable to extract job payload.'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace Facade\Ignition\Middleware; | ||
|
||
use Facade\FlareClient\Report; | ||
use Facade\Ignition\JobRecorder\JobRecorder; | ||
|
||
class AddJobInformation | ||
{ | ||
/** @var \Facade\Ignition\JobRecorder\JobRecorder */ | ||
protected $jobRecorder; | ||
|
||
public function __construct(JobRecorder $jobRecorder) | ||
{ | ||
$this->jobRecorder = $jobRecorder; | ||
} | ||
|
||
public function handle(Report $report, $next) | ||
{ | ||
if ($this->jobRecorder->getJob()) { | ||
$report->group('job', $this->jobRecorder->toArray()); | ||
} | ||
|
||
return $next($report); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
<?php | ||
|
||
namespace Facade\Ignition\Tests\JobRecorder; | ||
|
||
use Exception; | ||
use Facade\Ignition\JobRecorder\JobRecorder; | ||
use Facade\Ignition\Tests\stubs\jobs\QueueableJob; | ||
use Facade\Ignition\Tests\TestCase; | ||
use Illuminate\Container\Container; | ||
use Illuminate\Queue\Events\JobExceptionOccurred; | ||
use Illuminate\Queue\Jobs\RedisJob; | ||
use Illuminate\Queue\Queue; | ||
use Illuminate\Queue\RedisQueue; | ||
|
||
class JobRecorderTest extends TestCase | ||
{ | ||
/** @test */ | ||
public function it_can_record_a_failed_job() | ||
{ | ||
$recorder = (new JobRecorder($this->app)); | ||
|
||
$job = new QueueableJob([]); | ||
|
||
$recorder->record($this->createEvent( | ||
'redis', | ||
'default', | ||
$job | ||
)); | ||
|
||
$recorded = $recorder->toArray(); | ||
|
||
$this->assertEquals('Facade\Ignition\Tests\stubs\jobs\QueueableJob', $recorded['name']); | ||
$this->assertEquals('redis', $recorded['connection']); | ||
$this->assertEquals('default', $recorded['queue']); | ||
$this->assertNotEmpty($recorded['properties']); | ||
$this->assertEquals([], $recorded['properties']['data']); | ||
} | ||
|
||
/** @test */ | ||
public function it_can_record_a_failed_job_with_data() | ||
{ | ||
$recorder = (new JobRecorder($this->app)); | ||
|
||
$job = new QueueableJob([ | ||
'int' => 42, | ||
'boolean' => true, | ||
]); | ||
|
||
$recorder->record($this->createEvent( | ||
'redis', | ||
'default', | ||
$job | ||
)); | ||
|
||
$recorded = $recorder->toArray(); | ||
|
||
$this->assertEquals('Facade\Ignition\Tests\stubs\jobs\QueueableJob', $recorded['name']); | ||
$this->assertEquals('redis', $recorded['connection']); | ||
$this->assertEquals('default', $recorded['queue']); | ||
$this->assertNotEmpty($recorded['properties']); | ||
$this->assertEquals([ | ||
'int' => 42, | ||
'boolean' => true, | ||
], $recorded['properties']['data']); | ||
} | ||
|
||
/** @test */ | ||
public function it_can_record_a_closure_job() | ||
{ | ||
$recorder = (new JobRecorder($this->app)); | ||
|
||
$data = [ | ||
'int' => 42, | ||
'boolean' => true, | ||
]; | ||
|
||
$job = function () use ($data) { | ||
}; | ||
|
||
$recorder->record($this->createEvent( | ||
'redis', | ||
'default', | ||
$job | ||
)); | ||
|
||
$recorded = $recorder->toArray(); | ||
|
||
$this->assertEquals('Closure (JobRecorderTest.php:77)', $recorded['name']); | ||
$this->assertEquals('redis', $recorded['connection']); | ||
$this->assertEquals('default', $recorded['queue']); | ||
$this->assertNotEmpty($recorded['properties']); | ||
} | ||
|
||
/** @test */ | ||
public function it_can_handle_a_job_with_an_unserializeable_payload() | ||
{ | ||
$recorder = (new JobRecorder($this->app)); | ||
|
||
$payload = json_encode([ | ||
'job' => 'Fake Job Name', | ||
]); | ||
|
||
$event = new JobExceptionOccurred( | ||
'redis', | ||
new RedisJob( | ||
app(Container::class), | ||
app(RedisQueue::class), | ||
$payload, | ||
$payload, | ||
'redis', | ||
'default' | ||
), | ||
new Exception() | ||
); | ||
|
||
$recorder->record($event); | ||
|
||
$recorded = $recorder->toArray(); | ||
|
||
$this->assertEquals('Fake Job Name', $recorded['name']); | ||
$this->assertEquals('redis', $recorded['connection']); | ||
$this->assertEquals('default', $recorded['queue']); | ||
} | ||
|
||
/** | ||
* @param string $connection | ||
* @param \Illuminate\Contracts\Queue\ShouldQueue|\Closure $job | ||
* | ||
* @return \Illuminate\Queue\Events\JobExceptionOccurred | ||
*/ | ||
private function createEvent( | ||
string $connection, | ||
string $queue, | ||
$job | ||
): JobExceptionOccurred { | ||
$fakeQueue = new class extends Queue { | ||
public function getPayload($job, $connection): string | ||
{ | ||
return $this->createPayload($job, $connection); | ||
} | ||
}; | ||
|
||
$payload = $fakeQueue->getPayload($job, $connection); | ||
|
||
return new JobExceptionOccurred( | ||
$connection, | ||
new RedisJob( | ||
app(Container::class), | ||
app(RedisQueue::class), | ||
$payload, | ||
$payload, | ||
$connection, | ||
$queue | ||
), | ||
new Exception() | ||
); | ||
} | ||
} |
Oops, something went wrong.