From bd6c014e38110fdf0d01da9a584db21b818b43a2 Mon Sep 17 00:00:00 2001 From: Robin Houtevelts Date: Wed, 3 Jul 2024 12:46:51 +0200 Subject: [PATCH] Add CommandCollector (#54) --- config/xray.php | 7 ++ src/Collectors/CommandCollector.php | 36 +++++++++ src/XrayServiceProvider.php | 5 ++ tests/Collectors/CommandCollectorTest.php | 96 +++++++++++++++++++++++ 4 files changed, 144 insertions(+) create mode 100644 src/Collectors/CommandCollector.php create mode 100644 tests/Collectors/CommandCollectorTest.php diff --git a/config/xray.php b/config/xray.php index ab5259e..ed569a1 100755 --- a/config/xray.php +++ b/config/xray.php @@ -55,6 +55,13 @@ */ 'view' => env('XRAY_VIEW', true), + /* + |-------------------------------------------------------------------------- + | Trace Commands + |-------------------------------------------------------------------------- + */ + 'command' => env('XRAY_COMMAND', true), + /* |-------------------------------------------------------------------------- | Trace Routes diff --git a/src/Collectors/CommandCollector.php b/src/Collectors/CommandCollector.php new file mode 100644 index 0000000..4eedfdc --- /dev/null +++ b/src/Collectors/CommandCollector.php @@ -0,0 +1,36 @@ +app['events']->listen(CommandStarting::class, function (CommandStarting $event) { + if ($this->app->runningInConsole()) { + $this->initCliTracer($event->command); + } + $name = 'Command ' . $event->command; + + $this->addCustomSegment( + (new JobSegment())->setName($name)->setPayload(['arguments' => $event->input->getArguments(), 'options' => $event->input->getOptions()]), + $name, + ); + }); + + $this->app['events']->listen(CommandFinished::class, function (CommandFinished $event) { + $name = 'Command ' . $event->command; + if ($this->hasAddedSegment($name)) { + $this->getSegment($name)->setResult($event->exitCode === 0); + $this->endSegment($name); + $this->submitCliTracer(); + } + }); + } +} diff --git a/src/XrayServiceProvider.php b/src/XrayServiceProvider.php index acf497d..7203f81 100755 --- a/src/XrayServiceProvider.php +++ b/src/XrayServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider; use Napp\Xray\Collectors\CacheCollector; +use Napp\Xray\Collectors\CommandCollector; use Napp\Xray\Collectors\DatabaseQueryCollector; use Napp\Xray\Collectors\FrameworkCollector; use Napp\Xray\Collectors\JobCollector; @@ -65,6 +66,10 @@ protected function registerCollectors(): void app(JobCollector::class); } + if (config('xray.command')) { + app(CommandCollector::class); + } + if (config('xray.view')) { app(ViewCollector::class); } diff --git a/tests/Collectors/CommandCollectorTest.php b/tests/Collectors/CommandCollectorTest.php new file mode 100644 index 0000000..62724b1 --- /dev/null +++ b/tests/Collectors/CommandCollectorTest.php @@ -0,0 +1,96 @@ +bind('config', function() { + return new Repository([ + 'xray' => [ + 'enabled' => true, + 'submitter' => CommandCollectorSegmentSubmitter::class, + ], + ]); + }); + Container::setInstance($mockApplication); + CommandCollectorSegmentSubmitter::$submittedSegments = []; + + // given a CommandCollector object + $commandCollector = new CommandCollector($mockApplication); + // with a tracer that forces sampling + $commandCollector->tracer()->setSampled(true); + + // and the events our collector listens to + $startedEvent = new CommandStarting("foobar", new ArrayInput([]), new NullOutput()); + $finishedEvent = new CommandFinished("foobar", new ArrayInput([]), new NullOutput(), 0); + + // after dispatching the events on the mock application + $mockApplication->events->dispatch($startedEvent); + $mockApplication->events->dispatch($finishedEvent); + + // then a trace should have been submitted + $this->assertCount(1, CommandCollectorSegmentSubmitter::$submittedSegments); + $trace = json_decode(json_encode(CommandCollectorSegmentSubmitter::$submittedSegments[0]), true); + + // with a segment named "Command foobar" + $this->assertEquals("Command foobar", $trace["subsegments"][0]["name"]); + } +} + +class CommandCollectorTestMockApplication extends Application { + + public $events; + public function __construct() + { + $this->events = new CommandCollectorTestMockEvents(); + $this->instance('events', $this->events); + } +} + +class CommandCollectorTestMockEvents { + protected $listeners = []; + + public function listen(string $event, $callback) { + $this->listeners[$event] = $callback; + } + + public function dispatch($eventObject) { + $eventClass = get_class($eventObject); + + if(!array_key_exists($eventClass, $this->listeners)) { + throw new \Exception("Unit test exception, $eventClass was never registered"); + } + + $eventCallback = $this->listeners[$eventClass]; + + $eventCallback($eventObject); + } +} + +class CommandCollectorSegmentSubmitter implements SegmentSubmitter +{ + + public static $submittedSegments = []; + + public function submitSegment(Segment $segment): void + { + self::$submittedSegments[] = $segment; + } + +}