diff --git a/readme.md b/readme.md index 2cbd217..585d5c2 100644 --- a/readme.md +++ b/readme.md @@ -113,6 +113,19 @@ foreach ($results->all() as $result) { $html = $healthz->html(); ``` +**Enable integration with the Laravel Exception Handler** + +It allows to report Healthz failure and warning exceptions to the Laravel log exceptions or send them to an external service like Flare, Bugsnag or Sentry. + +This feature is disabled by default, here how to enable it for Healthz failure and warning exceptions: +```php +$healthz->setReportFailure(true); +$healthz->setReportWarning(true); +``` +NOTE: The [`report()`](https://laravel.com/docs/8.x/errors#the-report-helper) Laravel helper must be present, otherwise this feature does nothing + +For more informations see the Laravel Docs: https://laravel.com/docs/8.x/errors + ---------------------------------------------------------------------------- ## Check configuration diff --git a/src/Healthz.php b/src/Healthz.php index 9cac6f8..b327f98 100644 --- a/src/Healthz.php +++ b/src/Healthz.php @@ -19,6 +19,16 @@ class Healthz Stack::push as stackPush; } + /** + * @var bool + */ + protected $reportFailure = false; + + /** + * @var bool + */ + protected $reportWarning = false; + /** * @param HealthCheck[] $healthChecks */ @@ -56,6 +66,18 @@ public function run(): ResultStack } catch (Exception $e) { $check->setStatus($e->getMessage()); $resultCode = $e instanceof HealthWarningException ? HealthResult::RESULT_WARNING : HealthResult::RESULT_FAILURE; + + if ($resultCode === HealthResult::RESULT_FAILURE && + $this->shouldReportFailure() + ) { + $this->reportException($e); + } + + if ($resultCode === HealthResult::RESULT_WARNING && + $this->shouldReportWarning() + ) { + $this->reportException($e); + } } $results[] = new HealthResult($resultCode, $check); @@ -86,4 +108,65 @@ public function html(ResultStack $results=null): string return $twig->render('healthz', ['results' => $results->all()]); } + + /** + * @return bool + */ + public function shouldReportFailure() + { + return $this->reportFailure; + } + + /** + * If set to true, report a HealthFailureException to the Laravel Exception Handler + * + * @param bool $value + * + * @return $this + */ + public function setReportFailure(bool $value) + { + $this->reportFailure = $value; + + return $this; + } + + /** + * @return bool + */ + public function shouldReportWarning() + { + return $this->reportWarning; + } + + /** + * If set to true, report a HealthWarningException to the Laravel Exception Handler + * + * @param bool $value + * + * @return $this + */ + public function setReportWarning(bool $value) + { + $this->reportWarning = $value; + + return $this; + } + + /** + * Report an exception to the Laravel Exception Handler + * + * @param \Throwable $exception + * @return void + */ + protected function reportException($e) + { + if (function_exists('report')) { + try { + report($e); + } catch (\Throwable $e) { + // silent failed + } + } + } } diff --git a/tests/HealthzTest.php b/tests/HealthzTest.php index ac366c0..8db075b 100644 --- a/tests/HealthzTest.php +++ b/tests/HealthzTest.php @@ -57,12 +57,50 @@ public function run_health_checks_and_return_result_stack() $this->check1->shouldReceive('run'); # warning - $this->check2->shouldReceive('run')->andThrow(new HealthWarningException('warning')); + $warningException = new HealthWarningException('warning'); + $this->check2->shouldReceive('run')->andThrow($warningException); $this->check2->shouldReceive('setStatus')->with('warning')->once(); + self::$functions->shouldNotReceive('report')->with($warningException); # failure - $this->check3->shouldReceive('run')->andThrow(new Exception('failure')); + $failureException = new Exception('failure'); + $this->check3->shouldReceive('run')->andThrow($failureException); $this->check3->shouldReceive('setStatus')->with('failure')->once(); + self::$functions->shouldNotReceive('report')->with($failureException); + + $result = $this->healthz->run(); + $this->assertInstanceOf(ResultStack::class, $result); + $this->assertTrue($result->all()[0]->passed()); + $this->assertTrue($result->all()[1]->warned()); + $this->assertTrue($result->all()[2]->failed()); + } + + /** @test */ + public function enable_laravel_exception_handler_then_run_health_checks_and_return_result_stack() + { + $this->healthz = Mockery::spy($this->healthz); + $this->healthz->push($this->check3); + + # success + $this->check1->shouldReceive('run'); + + # warning + $warningException = new HealthWarningException('warning'); + $this->check2->shouldReceive('run')->andThrow($warningException); + $this->check2->shouldReceive('setStatus')->with('warning')->once(); + self::$functions->shouldReceive('report')->with($warningException)->once(); + + # failure + $failureException = new Exception('failure'); + $this->check3->shouldReceive('run')->andThrow($failureException); + $this->check3->shouldReceive('setStatus')->with('failure')->once(); + self::$functions->shouldReceive('report')->with($failureException)->once(); + + $this->healthz->setReportWarning(true); + $this->healthz->shouldHaveReceived('setReportWarning')->with(true)->once(); + + $this->healthz->setReportFailure(true); + $this->healthz->shouldHaveReceived('setReportFailure')->with(true)->once(); $result = $this->healthz->run(); $this->assertInstanceOf(ResultStack::class, $result); diff --git a/tests/TestCase.php b/tests/TestCase.php index 24e31ac..9ebec84 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,8 +1,28 @@ report($exception); +} + class TestCase extends \PHPUnit\Framework\TestCase { + public static $functions; + + public function setUp(): void + { + parent::setUp(); + self::$functions = Mockery::mock(); + } + public function tearDown(): void { Mockery::close();