Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Illuminate/Foundation/Cloud/FailedJobProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,14 @@ public function log($connection, $queue, $payload, $exception)
'started_at' => $processingJobDetails['started_at']->toDateTimeString('microsecond'),
'attempts' => $processingJobDetails['attempts'],
'payload' => $payload,
'exception' => (string) mb_convert_encoding($exception, 'UTF-8'),
'exception_preview' => Str::limit(
value: $exception->getMessage()
? $exception::class.': '.$exception->getMessage().' in '.$exception->getFile().':'.$exception->getLine()
: $exception::class.' in '.$exception->getFile().':'.$exception->getLine(),
limit: 1000,
end: '[truncated due size...]'),
'job_name' => $payload['displayName'] ?? '',
'exception' => (string) $exception,
Comment thread
timacdonald marked this conversation as resolved.
Outdated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mb_convert_encoding is redundant here. Our event class handles converting everything to UTF-8.

]);

$this->queue->finishProcessingJob(timestamp: $timestamp);
Expand Down
153 changes: 143 additions & 10 deletions tests/Foundation/Cloud/QueueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,8 @@ public function testItEmitsFailedJobEvents()
$job = $queue->pop();
$job->fail();
Str::createUuidsUsingSequence([Uuid::fromString('00dc709e-90c4-70c2-87c8-9b7127d20e8f')]);
$failedJobProvider->log('cloud', 'default', ['payload' => 'here'], new RuntimeException('Whoops!'));
$line = __LINE__ + 1;
$failedJobProvider->log('cloud', 'default', ['payload' => 'here', 'displayName' => 'App\\Jobs\\ProcessPodcast'], new RuntimeException('Whoops!'));
Str::createUuidsNormally();
$queue->pop();

Expand All @@ -497,7 +498,10 @@ public function testItEmitsFailedJobEvents()
'attempts' => 1,
'payload' => [
'payload' => 'here',
'displayName' => 'App\\Jobs\\ProcessPodcast',
],
'exception_preview' => 'RuntimeException: Whoops! in '.__FILE__.':'.$line,
'job_name' => 'App\\Jobs\\ProcessPodcast',
],
[
'_cloud_event' => 'queue',
Expand All @@ -509,6 +513,128 @@ public function testItEmitsFailedJobEvents()
], $eventsFake->emitted);
}

public function testItEmitsFailedJobEventsWithExceptionPreviewWithMessage()
{
$this->travelTo('2000-01-02 03:04:05.060708');
$eventsFake = $this->fakeEvents();
[$queue, $agent] = $this->fakeQueue();
$failerFake = $this->fakeFailer();
$failedJobProvider = new FailedJobProvider($failerFake, $eventsFake, $this->app['encrypter']);
$failedJobProvider->setQueue($queue);
$this->app[FailedJobProvider::class] = $failedJobProvider;

$agent->pushJob();
$job = $queue->pop();
$job->fail();
Str::createUuidsUsingSequence([Uuid::fromString('00dc709e-90c4-70c2-87c8-9b7127d20e8f')]);
$line = __LINE__ + 1;
$failedJobProvider->log('cloud', 'default', ['payload' => 'here'], new RuntimeException('Whoops!'));
Str::createUuidsNormally();
$queue->pop();

$this->assertSame(
'RuntimeException: Whoops! in '.__FILE__.':'.$line,
$eventsFake->emitted[1]['exception_preview'],
);
}

public function testItEmitsFailedJobEventsWithExceptionPreviewWithoutMessage()
{
$this->travelTo('2000-01-02 03:04:05.060708');
$eventsFake = $this->fakeEvents();
[$queue, $agent] = $this->fakeQueue();
$failerFake = $this->fakeFailer();
$failedJobProvider = new FailedJobProvider($failerFake, $eventsFake, $this->app['encrypter']);
$failedJobProvider->setQueue($queue);
$this->app[FailedJobProvider::class] = $failedJobProvider;

$agent->pushJob();
$job = $queue->pop();
$job->fail();
Str::createUuidsUsingSequence([Uuid::fromString('00dc709e-90c4-70c2-87c8-9b7127d20e8f')]);
$line = __LINE__ + 1;
$failedJobProvider->log('cloud', 'default', ['payload' => 'here'], new RuntimeException);
Str::createUuidsNormally();
$queue->pop();

$this->assertSame(
'RuntimeException in '.__FILE__.':'.$line,
$eventsFake->emitted[1]['exception_preview'],
);
}

public function testItSanitizesInvalidUtf8InTheExceptionField()
{
$this->travelTo('2000-01-02 03:04:05.060708');
$eventsFake = $this->fakeEvents();
[$queue, $agent] = $this->fakeQueue();
$failerFake = $this->fakeFailer();
$failedJobProvider = new FailedJobProvider($failerFake, $eventsFake, $this->app['encrypter']);
$failedJobProvider->setQueue($queue);
$this->app[FailedJobProvider::class] = $failedJobProvider;

$agent->pushJob();
$job = $queue->pop();
$job->fail();
Str::createUuidsUsingSequence([Uuid::fromString('00dc709e-90c4-70c2-87c8-9b7127d20e8f')]);
$failedJobProvider->log('cloud', 'default', ['payload' => 'here'], new RuntimeException("Bad byte: \xFF"));
Str::createUuidsNormally();
$queue->pop();

$this->assertTrue(mb_check_encoding($eventsFake->stream, 'UTF-8'));
$this->assertTrue(mb_check_encoding($eventsFake->emitted[1]['exception'], 'UTF-8'));
$this->assertStringContainsString('Bad byte: �', $eventsFake->emitted[1]['exception']);
$this->assertStringContainsString('Bad byte: �', $eventsFake->emitted[1]['exception_preview']);
}

public function testItEmitsFailedJobEventsWithJobDisplayName()
{
$this->travelTo('2000-01-02 03:04:05.060708');
$eventsFake = $this->fakeEvents();
[$queue, $agent] = $this->fakeQueue();
$failerFake = $this->fakeFailer();
$failedJobProvider = new FailedJobProvider($failerFake, $eventsFake, $this->app['encrypter']);
$failedJobProvider->setQueue($queue);
$this->app[FailedJobProvider::class] = $failedJobProvider;

$agent->pushJob();
$job = $queue->pop();
$job->fail();
Str::createUuidsUsingSequence([Uuid::fromString('00dc709e-90c4-70c2-87c8-9b7127d20e8f')]);
$failedJobProvider->log('cloud', 'default', ['displayName' => 'App\\Jobs\\ProcessPodcast'], new RuntimeException('Whoops!'));
Str::createUuidsNormally();
$queue->pop();

$this->assertSame(
'App\\Jobs\\ProcessPodcast',
$eventsFake->emitted[1]['job_name'],
);
}

public function testItEmitsFailedJobEventsWithoutJobDisplayName()
{
$this->travelTo('2000-01-02 03:04:05.060708');
$eventsFake = $this->fakeEvents();
[$queue, $agent] = $this->fakeQueue();
$failerFake = $this->fakeFailer();
$failedJobProvider = new FailedJobProvider($failerFake, $eventsFake, $this->app['encrypter']);
$failedJobProvider->setQueue($queue);
$this->app[FailedJobProvider::class] = $failedJobProvider;

$agent->pushJob();
$job = $queue->pop();
$job->fail();
Str::createUuidsUsingSequence([Uuid::fromString('00dc709e-90c4-70c2-87c8-9b7127d20e8f')]);
$failedJobProvider->log('cloud', 'default', ['payload' => 'here'], new RuntimeException('Whoops!'));
Str::createUuidsNormally();
$queue->pop();

$this->assertSame(
'',
$eventsFake->emitted[1]['job_name'],
);
}

public function testItEmitsReleasedJobEvents()
{
$this->travelTo('2000-01-02 03:04:05.060708');
Expand Down Expand Up @@ -1637,16 +1763,23 @@ private function fakeEvents()
{
return $this->app->instance(Events::class, new class('test-socket') extends Events
{
public array $emitted = [];
public array $emitted = [];
public string $stream = '';

public function emitMany(array $payloads): void
{
$this->emitted = [
...$this->emitted,
...$payloads,
];
}
});
protected function connected(): bool
{
return true;
}

protected function write(string $payload): void
{
$this->stream .= $payload;

foreach (explode("\n", rtrim($payload, "\n")) as $write) {
$this->emitted[] = json_decode($write, associative: true);
}
Comment thread
timacdonald marked this conversation as resolved.
Outdated
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This refactor allows us to see what was really sent to the socket after being encoded. This helps with our UTF-8 assertions.

});
}

/**
Expand Down
Loading