From c730c12b9f8defba796a0fe440a6a8cf3fc7686d Mon Sep 17 00:00:00 2001 From: Shueh Chou Lu Date: Tue, 20 Jul 2021 13:39:58 +0800 Subject: [PATCH] feat: catch possible error and log it (#12) --- config/xray.php | 24 +++++++++++++++++++++-- src/Collectors/DatabaseQueryCollector.php | 9 +++++++-- src/Collectors/RouteCollector.php | 20 +++++++++++-------- src/Collectors/SegmentCollector.php | 22 +++++++++++++++++++++ src/Collectors/ViewCollector.php | 4 +--- tests/Collectors/SegmentCollectorTest.php | 20 +++++++++++++++++++ 6 files changed, 84 insertions(+), 15 deletions(-) diff --git a/config/xray.php b/config/xray.php index ef73731..bf5eb92 100755 --- a/config/xray.php +++ b/config/xray.php @@ -5,16 +5,36 @@ return [ 'name' => env('AWS_XRAY_SERVICE_NAME'), - 'route_filters' => explode(',', env('AWS_XRAY_ROUTE_FILTERS', '')), - 'enabled' => env('AWS_XRAY_ENABLED', true), + /* + |-------------------------------------------------------------------------- + | Ignoring any possible error + |-------------------------------------------------------------------------- + | + | In production, it will possibly need to ignore error from Xray. + | + */ + 'ignore_error' => env('AWS_XRAY_IGNORE_ERROR', false), + + /* + |-------------------------------------------------------------------------- + | Ignored routes + |-------------------------------------------------------------------------- + | + | Comma separated value to ignore record Xray. + | Default will allow all routes to trace. + | + */ + 'route_filters' => explode(',', env('AWS_XRAY_ROUTE_FILTERS', '')), + /* |-------------------------------------------------------------------------- | Submission method |-------------------------------------------------------------------------- | | This is where you can set the data submission method. + | If [AWS_XRAY_DAEMON_HOST] is set, it will automatically using [DaemonSegmentSubmitter] | Supported classes: "APISegmentSubmitter", "DaemonSegmentSubmitter" | */ diff --git a/src/Collectors/DatabaseQueryCollector.php b/src/Collectors/DatabaseQueryCollector.php index 4b890f1..b9beb5e 100644 --- a/src/Collectors/DatabaseQueryCollector.php +++ b/src/Collectors/DatabaseQueryCollector.php @@ -4,6 +4,7 @@ namespace Napp\Xray\Collectors; +use Exception; use Illuminate\Database\Connection; use Illuminate\Database\Events\QueryExecuted; use Illuminate\Database\Query\Expression; @@ -16,8 +17,12 @@ class DatabaseQueryCollector extends EventsCollector public function registerEventListeners(): void { $this->app->events->listen(QueryExecuted::class, function (QueryExecuted $query) { - $sql = $query->sql instanceof Expression ? $query->sql->getValue() : $query->sql; - $this->handleQueryReport($sql, $query->bindings, $query->time, $query->connection); + try { + $sql = $query->sql instanceof Expression ? $query->sql->getValue() : $query->sql; + $this->handleQueryReport($sql, $query->bindings, $query->time, $query->connection); + } catch (Exception $e) { + $this->handleException($e); + } }); $this->bindingsEnabled = config('xray.db_bindings'); diff --git a/src/Collectors/RouteCollector.php b/src/Collectors/RouteCollector.php index 9f008ff..c4ad385 100644 --- a/src/Collectors/RouteCollector.php +++ b/src/Collectors/RouteCollector.php @@ -17,18 +17,22 @@ public function registerEventListeners(): void // Time between route resolution and request handled $this->app['events']->listen(RouteMatched::class, function ($event) { - $this->addSegment('request handled') - ->addAnnotation('controller', $this->getController()); - $this->getSegment('route matching')->end(); - }); - - $this->app['events']->listen(RequestHandled::class, function () { + $this->endSegment('route matching'); // Some middlewares might return a response // before the RouteMatched has been dispatched - if ($this->hasAddedSegment('request handled')) { - $this->endSegment('request handled'); + // end the segment first avoid missing end time + try { + $this->addSegment('request handled') + ->addAnnotation('controller', $this->getController()) + ->end(); + } catch (\Exception $e) { + $this->handleException($e); } }); + + $this->app['events']->listen(RequestHandled::class, function () { + $this->endSegment('request handled'); + }); } protected function getController(): string diff --git a/src/Collectors/SegmentCollector.php b/src/Collectors/SegmentCollector.php index e12ca65..f2d74d1 100644 --- a/src/Collectors/SegmentCollector.php +++ b/src/Collectors/SegmentCollector.php @@ -6,6 +6,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Log; use Napp\Xray\Segments\TimeSegment; use Napp\Xray\Segments\Trace; use Pkerrigan\Xray\HttpSegment; @@ -172,4 +173,25 @@ public function submitCliTracer(): void $tracer::flush(); } + + /** + * Record and log the exception + * + * If [ignore_error] is set to false, it will rethrow the exception + * + * @throws \Exception iif set [ignore_error] to false + * + * @param \Exception $e + * @return void + */ + public function handleException(\Exception $e) + { + Log::warning($e->getMessage(), ['exception' => $e]); + $this->current() + ->addAnnotation('xrayError', $e->getMessage()) + ->addMetadata('xrayDatabaseQueryTrace', $e->getTraceAsString()); + if (!config('xray.ignore_error')) { + throw $e; + } + } } diff --git a/src/Collectors/ViewCollector.php b/src/Collectors/ViewCollector.php index 3fa73db..86bb0e8 100644 --- a/src/Collectors/ViewCollector.php +++ b/src/Collectors/ViewCollector.php @@ -15,9 +15,7 @@ public function registerEventListeners(): void $this->app['events']->listen('composing:*', function ($view, $data = []) { $viewName = substr($view, 11); - if ($this->hasAddedSegment('View ' . $viewName)) { - $this->endSegment('View ' . $viewName); - } + $this->endSegment('View ' . $viewName); }); } } diff --git a/tests/Collectors/SegmentCollectorTest.php b/tests/Collectors/SegmentCollectorTest.php index f93f276..48bd232 100644 --- a/tests/Collectors/SegmentCollectorTest.php +++ b/tests/Collectors/SegmentCollectorTest.php @@ -64,6 +64,26 @@ public function test_end_empty_segments_wont_throw_exception() $this->assertTrue(true); } + public function test_handle_exception() + { + // should not rethrow exception + $this->app['config']->set('xray.ignore_error', true); + $exception = new \Exception('message', 0); + $collector = new SegmentCollector(); + + $collector->handleException($exception); + + $serialized = $collector->current()->jsonSerialize(); + $this->assertEquals('message', $serialized['annotations']['xrayError']); + $this->assertNotEmpty(($serialized['metadata']['xrayDatabaseQueryTrace'])); + + // should rethrow exception + $this->app['config']->set('xray.ignore_error', false); + $this->expectException(\Exception::class); + + $collector->handleException($exception); + } + protected function createRequest() { $request = $this->createMock(Request::class);