From e04f998297f3ecee472ca2e97842725d84049f49 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Fri, 26 Apr 2024 13:58:07 +0200 Subject: [PATCH 1/5] Add support for better registering flare in laravel 11 --- src/Commands/TestCommand.php | 76 ++++++++++++++++++++++++++++++------ src/Facades/Flare.php | 2 +- src/Support/LaravelFlare.php | 17 ++++++++ 3 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 src/Support/LaravelFlare.php diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index cf0832ec..3c3a5bc4 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -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\LaravelIgnition\Support\LaravelFlare; class TestCommand extends Command { @@ -44,24 +52,26 @@ protected function checkFlareKey(): self public function checkFlareLogger(): self { - $defaultLogChannel = $this->config->get('logging.default'); + if (! $this->hasFlareReportableCallback()) { + $defaultLogChannel = $this->config->get('logging.default'); - $activeStack = $this->config->get("logging.channels.{$defaultLogChannel}"); + $activeStack = $this->config->get("logging.channels.{$defaultLogChannel}"); - if (is_null($activeStack)) { - $this->info("❌ The default logging channel `{$defaultLogChannel}` is not configured in the `logging` config file"); - } + if (is_null($activeStack)) { + $this->info("❌ 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"); - } + if (! isset($activeStack['channels']) || ! in_array('flare', $activeStack['channels'])) { + $this->info("❌ 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'); - } + if (is_null($this->config->get('logging.channels.flare'))) { + $this->info('❌ 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`.'); + 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`.'); + } } if ($this->config->get('ignition.with_stack_frame_arguments') && ini_get('zend.exception_ignore_args')) { @@ -73,6 +83,46 @@ public function checkFlareLogger(): self return $this; } + protected function hasFlareReportableCallback(): bool + { + if (version_compare(app()->version(), '11.0.0', '<')) { + return false; + } + + 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() === Flare::class; + } + } catch (ReflectionException $exception) { + return false; + } + + return false; + } + protected function sendTestException(): void { $testException = new Exception('This is an exception to test if the integration with Flare works.'); diff --git a/src/Facades/Flare.php b/src/Facades/Flare.php index a31f272e..a09070e3 100644 --- a/src/Facades/Flare.php +++ b/src/Facades/Flare.php @@ -16,7 +16,7 @@ class Flare extends Facade { protected static function getFacadeAccessor() { - return \Spatie\FlareClient\Flare::class; + return \Spatie\LaravelIgnition\Support\LaravelFlare::class; } public static function sentReports(): SentReports diff --git a/src/Support/LaravelFlare.php b/src/Support/LaravelFlare.php new file mode 100644 index 00000000..a9cd05c3 --- /dev/null +++ b/src/Support/LaravelFlare.php @@ -0,0 +1,17 @@ +reportable(static function (Throwable $exception): Flare { + return app(Flare::class)->report($exception); + }); + } +} From 514260fd2c848c4891487718eefe50120aca6306 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Mon, 29 Apr 2024 14:24:10 +0200 Subject: [PATCH 2/5] Add support for laravel 11 error handling --- src/Commands/TestCommand.php | 93 ++++++++++++++++++++++++++---------- src/Facades/Flare.php | 16 ++++++- src/Support/LaravelFlare.php | 17 ------- 3 files changed, 84 insertions(+), 42 deletions(-) delete mode 100644 src/Support/LaravelFlare.php diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index 3c3a5bc4..89f9594a 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -16,7 +16,6 @@ use ReflectionProperty; use Spatie\FlareClient\Flare; use Spatie\FlareClient\Http\Exceptions\BadResponseCode; -use Spatie\LaravelIgnition\Support\LaravelFlare; class TestCommand extends Command { @@ -52,26 +51,12 @@ protected function checkFlareKey(): self public function checkFlareLogger(): self { - if (! $this->hasFlareReportableCallback()) { - $defaultLogChannel = $this->config->get('logging.default'); + $configuredCorrectly = $this->shouldUseReportableCallbackLogger() + ? $this->checkFlareReportableCallbackLogger() + : $this->checkFlareConfigLogger(); - $activeStack = $this->config->get("logging.channels.{$defaultLogChannel}"); - - if (is_null($activeStack)) { - $this->info("❌ 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"); - } - - if (is_null($this->config->get('logging.channels.flare'))) { - $this->info('❌ 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`.'); - } + if($configuredCorrectly === false) { + die(); } if ($this->config->get('ignition.with_stack_frame_arguments') && ini_get('zend.exception_ignore_args')) { @@ -83,12 +68,72 @@ public function checkFlareLogger(): self return $this; } - protected function hasFlareReportableCallback(): bool + protected function shouldUseReportableCallbackLogger(): bool { - if (version_compare(app()->version(), '11.0.0', '<')) { - return false; + return version_compare(app()->version(), '11.0.0', '>='); + } + + protected function checkFlareConfigLogger(): bool + { + $failures = $this->resolveFlareConfigLoggerFailures(); + + foreach ($failures as $failure) { + $this->info($failure); } + return empty($failures); + } + + protected function resolveFlareConfigLoggerFailures(): array + { + $defaultLogChannel = $this->config->get('logging.default'); + + $activeStack = $this->config->get("logging.channels.{$defaultLogChannel}"); + + $failures = []; + + if (is_null($activeStack)) { + $failures[] = "❌ The default logging channel `{$defaultLogChannel}` is not configured in the `logging` config file"; + } + + if (! isset($activeStack['channels']) || ! in_array('flare', $activeStack['channels'])) { + $failures[] = "❌ The logging channel `{$defaultLogChannel}` does not contain the 'flare' channel"; + } + + if (is_null($this->config->get('logging.channels.flare'))) { + $failures[] = '❌ There is no logging channel named `flare` in the `logging` config file'; + } + + if ($this->config->get('logging.channels.flare.driver') !== 'flare') { + $failures[] = '❌ The `flare` logging channel defined in the `logging` config file is not set to `flare`.'; + } + + return $failures; + } + + protected function checkFlareReportableCallbackLogger(): bool + { + if ($this->hasFlareReportableCallbackLogger()) { + return true; + } + + if(empty($this->resolveFlareConfigLoggerFailures())){ + return true; + } + + $this->info('❌ The Flare logging driver was not configured correctly.'); + $this->newLine(); + $this->info('Please ensure the following code is present in your `bootstrap/app.php` file:'); + $this->newLine(); + $this->info('->withExceptions(function (Exceptions $exceptions) {'); + $this->info(' Flare::handles($exceptions);'); + $this->info('})->create();'); + + return false; + } + + protected function hasFlareReportableCallbackLogger(): bool + { try { $handler = app(Handler::class); @@ -110,7 +155,7 @@ protected function hasFlareReportableCallback(): bool $reflection = new ReflectionClosure($callback); $closureReturnTypeReflection = $reflection->getReturnType(); - if(! $closureReturnTypeReflection instanceof ReflectionNamedType){ + if (! $closureReturnTypeReflection instanceof ReflectionNamedType) { return false; } diff --git a/src/Facades/Flare.php b/src/Facades/Flare.php index a09070e3..18e860ef 100644 --- a/src/Facades/Flare.php +++ b/src/Facades/Flare.php @@ -2,8 +2,11 @@ namespace Spatie\LaravelIgnition\Facades; +use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Support\Facades\Facade; +use Spatie\FlareClient\Flare as FlareClient; use Spatie\LaravelIgnition\Support\SentReports; +use Throwable; /** * @method static void glow(string $name, string $messageLevel = \Spatie\FlareClient\Enums\MessageLevels::INFO, array $metaData = []) @@ -16,7 +19,18 @@ class Flare extends Facade { protected static function getFacadeAccessor() { - return \Spatie\LaravelIgnition\Support\LaravelFlare::class; + return FlareClient::class; + } + + public static function handles(Exceptions $exceptions): void + { + $exceptions->reportable(static function (Throwable $exception): FlareClient { + $flare = app(FlareClient::class); + + $flare->report($exception); + + return $flare; + }); } public static function sentReports(): SentReports diff --git a/src/Support/LaravelFlare.php b/src/Support/LaravelFlare.php deleted file mode 100644 index a9cd05c3..00000000 --- a/src/Support/LaravelFlare.php +++ /dev/null @@ -1,17 +0,0 @@ -reportable(static function (Throwable $exception): Flare { - return app(Flare::class)->report($exception); - }); - } -} From 15967418dfe3a491af79aa9b0fabe331be29fb3b Mon Sep 17 00:00:00 2001 From: rubenvanassche Date: Mon, 29 Apr 2024 12:25:34 +0000 Subject: [PATCH 3/5] Fix styling --- src/Commands/TestCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index 89f9594a..48120f2b 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -117,7 +117,7 @@ protected function checkFlareReportableCallbackLogger(): bool return true; } - if(empty($this->resolveFlareConfigLoggerFailures())){ + if(empty($this->resolveFlareConfigLoggerFailures())) { return true; } From 1cc2595ed1eae41753443a8bbb689e3578b25ae2 Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Mon, 29 Apr 2024 14:30:46 +0200 Subject: [PATCH 4/5] Naming cleanup --- src/Commands/TestCommand.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index 48120f2b..a182f8e8 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -52,8 +52,8 @@ protected function checkFlareKey(): self public function checkFlareLogger(): self { $configuredCorrectly = $this->shouldUseReportableCallbackLogger() - ? $this->checkFlareReportableCallbackLogger() - : $this->checkFlareConfigLogger(); + ? $this->isValidReportableCallbackFlareLogger() + : $this->isValidConfigFlareLogger(); if($configuredCorrectly === false) { die(); @@ -73,9 +73,9 @@ protected function shouldUseReportableCallbackLogger(): bool return version_compare(app()->version(), '11.0.0', '>='); } - protected function checkFlareConfigLogger(): bool + protected function isValidConfigFlareLogger(): bool { - $failures = $this->resolveFlareConfigLoggerFailures(); + $failures = $this->resolveConfigFlareLoggerFailures(); foreach ($failures as $failure) { $this->info($failure); @@ -84,7 +84,8 @@ protected function checkFlareConfigLogger(): bool return empty($failures); } - protected function resolveFlareConfigLoggerFailures(): array + /** @return string[] */ + protected function resolveConfigFlareLoggerFailures(): array { $defaultLogChannel = $this->config->get('logging.default'); @@ -111,13 +112,13 @@ protected function resolveFlareConfigLoggerFailures(): array return $failures; } - protected function checkFlareReportableCallbackLogger(): bool + protected function isValidReportableCallbackFlareLogger(): bool { - if ($this->hasFlareReportableCallbackLogger()) { + if ($this->hasReportableCallbackFlareLogger()) { return true; } - if(empty($this->resolveFlareConfigLoggerFailures())) { + if(empty($this->resolveConfigFlareLoggerFailures())) { return true; } @@ -132,7 +133,7 @@ protected function checkFlareReportableCallbackLogger(): bool return false; } - protected function hasFlareReportableCallbackLogger(): bool + protected function hasReportableCallbackFlareLogger(): bool { try { $handler = app(Handler::class); From 44331597ba32036202fde2fa3fc5a00dd498ce9e Mon Sep 17 00:00:00 2001 From: Ruben Van Assche Date: Mon, 29 Apr 2024 16:53:56 +0200 Subject: [PATCH 5/5] Use Ignition for logging --- src/Commands/TestCommand.php | 15 ++++++++++++--- src/Exceptions/InvalidConfig.php | 2 +- src/Facades/Flare.php | 7 ++++--- src/Support/FlareLogHandler.php | 1 + 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Commands/TestCommand.php b/src/Commands/TestCommand.php index a182f8e8..7a504548 100644 --- a/src/Commands/TestCommand.php +++ b/src/Commands/TestCommand.php @@ -16,6 +16,7 @@ use ReflectionProperty; use Spatie\FlareClient\Flare; use Spatie\FlareClient\Http\Exceptions\BadResponseCode; +use Spatie\Ignition\Ignition; class TestCommand extends Command { @@ -114,11 +115,19 @@ protected function resolveConfigFlareLoggerFailures(): array protected function isValidReportableCallbackFlareLogger(): bool { - if ($this->hasReportableCallbackFlareLogger()) { + $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.'); + } + + if ($hasReportableCallbackFlareLogger) { return true; } - if(empty($this->resolveConfigFlareLoggerFailures())) { + if(empty($configLoggerFailures)) { return true; } @@ -160,7 +169,7 @@ protected function hasReportableCallbackFlareLogger(): bool return false; } - return $closureReturnTypeReflection->getName() === Flare::class; + return $closureReturnTypeReflection->getName() === Ignition::class; } } catch (ReflectionException $exception) { return false; diff --git a/src/Exceptions/InvalidConfig.php b/src/Exceptions/InvalidConfig.php index bb1bc688..81c870c8 100644 --- a/src/Exceptions/InvalidConfig.php +++ b/src/Exceptions/InvalidConfig.php @@ -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}."); } } diff --git a/src/Facades/Flare.php b/src/Facades/Flare.php index 18e860ef..e2718c3a 100644 --- a/src/Facades/Flare.php +++ b/src/Facades/Flare.php @@ -5,6 +5,7 @@ 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; @@ -24,10 +25,10 @@ protected static function getFacadeAccessor() public static function handles(Exceptions $exceptions): void { - $exceptions->reportable(static function (Throwable $exception): FlareClient { - $flare = app(FlareClient::class); + $exceptions->reportable(static function (Throwable $exception): Ignition { + $flare = app(Ignition::class); - $flare->report($exception); + $flare->handleException($exception); return $flare; }); diff --git a/src/Support/FlareLogHandler.php b/src/Support/FlareLogHandler.php index a9b3b71d..14dc7fe8 100644 --- a/src/Support/FlareLogHandler.php +++ b/src/Support/FlareLogHandler.php @@ -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']);