Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Laravel 11 exception handling #195

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
121 changes: 113 additions & 8 deletions src/Commands/TestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@

namespace Spatie\LaravelIgnition\Commands;

use Closure;
use Composer\InstalledVersions;
use Exception;
use Illuminate\Config\Repository;
use Illuminate\Console\Command;
use Illuminate\Foundation\Exceptions\Handler;
use Illuminate\Foundation\Exceptions\ReportableHandler;
use Illuminate\Log\LogManager;
use Laravel\SerializableClosure\Support\ReflectionClosure;
use ReflectionException;
use ReflectionNamedType;
use ReflectionProperty;
use Spatie\FlareClient\Flare;
use Spatie\FlareClient\Http\Exceptions\BadResponseCode;
use Spatie\Ignition\Ignition;

class TestCommand extends Command
{
Expand Down Expand Up @@ -43,34 +51,131 @@ protected function checkFlareKey(): self
}

public function checkFlareLogger(): self
{
$configuredCorrectly = $this->shouldUseReportableCallbackLogger()
? $this->isValidReportableCallbackFlareLogger()
: $this->isValidConfigFlareLogger();

if($configuredCorrectly === false) {
die();
}

if ($this->config->get('ignition.with_stack_frame_arguments') && ini_get('zend.exception_ignore_args')) {
$this->info('⚠️ The `zend.exception_ignore_args` php ini setting is enabled. This will prevent Flare from showing stack trace arguments.');
}

$this->info('✅ The Flare logging driver was configured correctly.');

return $this;
}

protected function shouldUseReportableCallbackLogger(): bool
{
return version_compare(app()->version(), '11.0.0', '>=');
}

protected function isValidConfigFlareLogger(): bool
{
$failures = $this->resolveConfigFlareLoggerFailures();

foreach ($failures as $failure) {
$this->info($failure);
}

return empty($failures);
}

/** @return string[] */
protected function resolveConfigFlareLoggerFailures(): array
{
$defaultLogChannel = $this->config->get('logging.default');

$activeStack = $this->config->get("logging.channels.{$defaultLogChannel}");

$failures = [];

if (is_null($activeStack)) {
$this->info("❌ The default logging channel `{$defaultLogChannel}` is not configured in the `logging` config file");
$failures[] = "❌ The default logging channel `{$defaultLogChannel}` is not configured in the `logging` config file";
}

if (! isset($activeStack['channels']) || ! in_array('flare', $activeStack['channels'])) {
$this->info("❌ The logging channel `{$defaultLogChannel}` does not contain the 'flare' channel");
$failures[] = "❌ The logging channel `{$defaultLogChannel}` does not contain the 'flare' channel";
}

if (is_null($this->config->get('logging.channels.flare'))) {
$this->info('❌ There is no logging channel named `flare` in the `logging` config file');
$failures[] = '❌ There is no logging channel named `flare` in the `logging` config file';
}

if ($this->config->get('logging.channels.flare.driver') !== 'flare') {
$this->info('❌ The `flare` logging channel defined in the `logging` config file is not set to `flare`.');
$failures[] = '❌ The `flare` logging channel defined in the `logging` config file is not set to `flare`.';
}

if ($this->config->get('ignition.with_stack_frame_arguments') && ini_get('zend.exception_ignore_args')) {
$this->info('⚠️ The `zend.exception_ignore_args` php ini setting is enabled. This will prevent Flare from showing stack trace arguments.');
return $failures;
}

protected function isValidReportableCallbackFlareLogger(): bool
{
$configLoggerFailures = $this->resolveConfigFlareLoggerFailures();

$hasReportableCallbackFlareLogger = $this->hasReportableCallbackFlareLogger();

if(empty($configLoggerFailures) && $hasReportableCallbackFlareLogger) {
$this->info('❌ The Flare logger was defined in your Laravel `logging.php` config file and `bootstrap/app.php` file which can cause duplicate errors. Please remove the Flare logger from your `logging.php` config file.');
}

$this->info('✅ The Flare logging driver was configured correctly.');
if ($hasReportableCallbackFlareLogger) {
return true;
}

return $this;
if(empty($configLoggerFailures)) {
return true;
}

$this->info('❌ The Flare logging driver was not configured correctly.');
$this->newLine();
$this->info('<fg=default;bg=default>Please ensure the following code is present in your `<fg=green>bootstrap/app.php</>` file:</>');
$this->newLine();
$this->info('<fg=default;bg=default>-><fg=green>withExceptions</>(<fg=blue>function</> (<fg=red>Exceptions</> $exceptions) {</>');
$this->info('<fg=default;bg=default> <fg=red>Flare</>::<fg=green>handles</>($exceptions);</>');
$this->info('<fg=default;bg=default>})-><fg=green>create</>();</>');

return false;
}

protected function hasReportableCallbackFlareLogger(): bool
{
try {
$handler = app(Handler::class);

$reflection = new ReflectionProperty($handler, 'reportCallbacks');
$reportCallbacks = $reflection->getValue($handler);

foreach ($reportCallbacks as $reportCallback) {
if (! $reportCallback instanceof ReportableHandler) {
continue;
}

$reflection = new ReflectionProperty($reportCallback, 'callback');
$callback = $reflection->getValue($reportCallback);

if (! $callback instanceof Closure) {
return false;
}

$reflection = new ReflectionClosure($callback);
$closureReturnTypeReflection = $reflection->getReturnType();

if (! $closureReturnTypeReflection instanceof ReflectionNamedType) {
return false;
}

return $closureReturnTypeReflection->getName() === Ignition::class;
}
} catch (ReflectionException $exception) {
return false;
}

return false;
}

protected function sendTestException(): void
Expand Down
2 changes: 1 addition & 1 deletion src/Exceptions/InvalidConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ public function getSolution(): Solution

return BaseSolution::create()
->setSolutionTitle('You provided an invalid log level')
->setSolutionDescription("Please change the log level in your `config/logging.php` file. Valid log levels are {$validLogLevelsString}.");
->setSolutionDescription("Please change the log level in your `config/logging.php` file or in `bootstrap/app.php`. Valid log levels are {$validLogLevelsString}.");
}
}
17 changes: 16 additions & 1 deletion src/Facades/Flare.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

namespace Spatie\LaravelIgnition\Facades;

use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Support\Facades\Facade;
use Spatie\FlareClient\Flare as FlareClient;
use Spatie\Ignition\Ignition;
use Spatie\LaravelIgnition\Support\SentReports;
use Throwable;

/**
* @method static void glow(string $name, string $messageLevel = \Spatie\FlareClient\Enums\MessageLevels::INFO, array $metaData = [])
Expand All @@ -16,7 +20,18 @@ class Flare extends Facade
{
protected static function getFacadeAccessor()
{
return \Spatie\FlareClient\Flare::class;
return FlareClient::class;
}

public static function handles(Exceptions $exceptions): void
{
$exceptions->reportable(static function (Throwable $exception): Ignition {
$flare = app(Ignition::class);

$flare->handleException($exception);

return $flare;
});
}

public static function sentReports(): SentReports
Expand Down
1 change: 1 addition & 0 deletions src/Support/FlareLogHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ protected function write(LogRecord $record): void
if (! $this->shouldReport($record->toArray())) {
return;
}

if ($this->hasException($record->toArray())) {
$report = $this->flare->report($record['context']['exception']);

Expand Down
Loading