From 347a001d5d04d528ab9c1961aebe88fdb7fd4fcb Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Fri, 11 Aug 2023 23:55:03 +0100 Subject: [PATCH 01/20] Laravel Instrumentation: Hook into `\Illuminate\Contracts\Foundation\Application`. Allows for watcher registration in console Kernel contexts. --- .../Laravel/src/LaravelInstrumentation.php | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index fd888e18..eab46ddb 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -4,8 +4,8 @@ namespace OpenTelemetry\Contrib\Instrumentation\Laravel; -use Illuminate\Foundation\Application; -use Illuminate\Foundation\Http\Kernel; +use Illuminate\Contracts\Foundation\Application; +use Illuminate\Contracts\Http\Kernel; use Illuminate\Http\Request; use OpenTelemetry\API\Globals; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; @@ -93,19 +93,15 @@ public static function register(): void } ); hook( - Kernel::class, + Application::class, '__construct', - pre: static function (Kernel $kernel, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { - $app = $params[0]; - $app->booted(static function (Application $app) use ($instrumentation) { - self::registerWatchers($app, new ClientRequestWatcher($instrumentation)); - self::registerWatchers($app, new ExceptionWatcher()); - self::registerWatchers($app, new CacheWatcher()); - self::registerWatchers($app, new LogWatcher()); - self::registerWatchers($app, new QueryWatcher($instrumentation)); - }); - }, - post: null + post: static function (Application $application, array $params, mixed $returnValue, ?Throwable $exception) use ($instrumentation) { + self::registerWatchers($application, new ClientRequestWatcher($instrumentation)); + self::registerWatchers($application, new ExceptionWatcher()); + self::registerWatchers($application, new CacheWatcher()); + self::registerWatchers($application, new LogWatcher()); + self::registerWatchers($application, new QueryWatcher($instrumentation)); + } ); } From 1221735addbd2a917fbe6fdebeb9bec10b537610 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Fri, 11 Aug 2023 23:55:03 +0100 Subject: [PATCH 02/20] Added `mockery/mockery` to allow testing console commands in Laravel instrumentation. --- src/Instrumentation/Laravel/composer.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Instrumentation/Laravel/composer.json b/src/Instrumentation/Laravel/composer.json index 0a2d216f..59e31b2e 100644 --- a/src/Instrumentation/Laravel/composer.json +++ b/src/Instrumentation/Laravel/composer.json @@ -15,21 +15,22 @@ "open-telemetry/sem-conv": "^1" }, "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "guzzlehttp/guzzle": "*", + "laravel/sail": "*", "laravel/sanctum": "*", + "laravel/tinker": "*", + "mockery/mockery": "2.0.x-dev", "nunomaduro/collision": "*", - "friendsofphp/php-cs-fixer": "^3", + "open-telemetry/sdk": "^1.0", "phan/phan": "^5.0", "php-http/mock-client": "*", "phpstan/phpstan": "^1.1", "phpstan/phpstan-phpunit": "^1.0", - "psalm/plugin-phpunit": "^0.16", - "open-telemetry/sdk": "^1.0", "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.0", + "psalm/plugin-phpunit": "^0.16", "spatie/laravel-ignition": "*", - "laravel/sail": "*", - "laravel/tinker": "*", - "guzzlehttp/guzzle": "*" + "vimeo/psalm": "^4.0" }, "autoload": { "psr-4": { @@ -46,6 +47,7 @@ } }, "config": { + "sort-packages": true, "allow-plugins": { "php-http/discovery": false } From 24ae2ea606194a9e1f91ea8b95b97c18e37ee47a Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Fri, 11 Aug 2023 23:55:03 +0100 Subject: [PATCH 03/20] Added CommandWatcher to Laravel instrumentation. --- .../Laravel/src/CommandWatcher.php | 32 +++++++++++++++++++ .../Laravel/src/LaravelInstrumentation.php | 1 + 2 files changed, 33 insertions(+) create mode 100644 src/Instrumentation/Laravel/src/CommandWatcher.php diff --git a/src/Instrumentation/Laravel/src/CommandWatcher.php b/src/Instrumentation/Laravel/src/CommandWatcher.php new file mode 100644 index 00000000..35a25492 --- /dev/null +++ b/src/Instrumentation/Laravel/src/CommandWatcher.php @@ -0,0 +1,32 @@ +listen(CommandFinished::class, [$this, 'recordCommandFinished']); + } + + public function recordCommandFinished(CommandFinished $command): void + { + $scope = Context::storage()->scope(); + if (!$scope) { + return; + } + $span = Span::fromContext($scope->context()); + $span->addEvent('command finished', [ + 'command' => $command->command, + 'exit-code' => $command->exitCode, + ]); + } +} diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index eab46ddb..5043b61d 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -99,6 +99,7 @@ public static function register(): void self::registerWatchers($application, new ClientRequestWatcher($instrumentation)); self::registerWatchers($application, new ExceptionWatcher()); self::registerWatchers($application, new CacheWatcher()); + self::registerWatchers($application, new CommandWatcher()); self::registerWatchers($application, new LogWatcher()); self::registerWatchers($application, new QueryWatcher($instrumentation)); } From 3b247742c20601620ba0a43fc66a780702a74bd1 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Fri, 11 Aug 2023 23:55:03 +0100 Subject: [PATCH 04/20] Laravel instrumentation: fixed LaravelInstrumentationTest namespace. --- .../Laravel/tests/Integration/LaravelInstrumentationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php b/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php index 978cf9f2..3df20b09 100644 --- a/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Tests\Instrumentation\Laravel\tests\Integration; +namespace OpenTelemetry\Tests\Instrumentation\Laravel\Integration; use ArrayObject; use Illuminate\Foundation\Http\Kernel; From cb5eea9e38c891e45bff6e99fec7d247583ed011 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Fri, 11 Aug 2023 23:55:03 +0100 Subject: [PATCH 05/20] Laravel instrumentation: linting. --- src/Instrumentation/Laravel/src/LaravelInstrumentation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index 5043b61d..5392afce 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -102,7 +102,7 @@ public static function register(): void self::registerWatchers($application, new CommandWatcher()); self::registerWatchers($application, new LogWatcher()); self::registerWatchers($application, new QueryWatcher($instrumentation)); - } + }, ); } From 4cc513c8b8ceec14389985e68939ab14804e84bc Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Sat, 12 Aug 2023 10:50:49 +0100 Subject: [PATCH 06/20] Laravel instrumentation: remove mockery dev dependency. --- src/Instrumentation/Laravel/composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Instrumentation/Laravel/composer.json b/src/Instrumentation/Laravel/composer.json index 59e31b2e..e74f7d85 100644 --- a/src/Instrumentation/Laravel/composer.json +++ b/src/Instrumentation/Laravel/composer.json @@ -20,7 +20,6 @@ "laravel/sail": "*", "laravel/sanctum": "*", "laravel/tinker": "*", - "mockery/mockery": "2.0.x-dev", "nunomaduro/collision": "*", "open-telemetry/sdk": "^1.0", "phan/phan": "^5.0", From 7c02e60e6dc7b033589a5785123f8d1158b9bd17 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Sat, 12 Aug 2023 11:49:58 +0100 Subject: [PATCH 07/20] Laravel instrumentation: prevent real Http requests from leaking. --- .../Laravel/tests/Integration/LaravelInstrumentationTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php b/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php index 3df20b09..f98653a9 100644 --- a/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php @@ -44,6 +44,8 @@ public function setUp(): void $this->scope = Configurator::create() ->withTracerProvider($this->tracerProvider) ->activate(); + + Http::fake(); } public function tearDown(): void From f49d613258227e5e03b5c18574d2fe936c558006 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Sat, 12 Aug 2023 11:53:02 +0100 Subject: [PATCH 08/20] Laravel instrumentation: first pass at instrumenting Console\Kernel. --- .../Laravel/src/LaravelInstrumentation.php | 47 ++++++++++++-- .../tests/Integration/CommandWatcherTest.php | 64 +++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index 5392afce..1459c303 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -4,8 +4,10 @@ namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +use Illuminate\Console\Command; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Http\Kernel; +use Illuminate\Contracts\Console\Kernel as ConsoleKernel; +use Illuminate\Contracts\Http\Kernel as HttpKernel; use Illuminate\Http\Request; use OpenTelemetry\API\Globals; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; @@ -32,9 +34,9 @@ public static function register(): void { $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.laravel'); hook( - Kernel::class, + HttpKernel::class, 'handle', - pre: static function (Kernel $kernel, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { + pre: static function (HttpKernel $kernel, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { $request = ($params[0] instanceof Request) ? $params[0] : null; /** @psalm-suppress ArgumentTypeCoercion */ $builder = $instrumentation->tracer() @@ -69,7 +71,7 @@ public static function register(): void return [$request]; }, - post: static function (Kernel $kernel, array $params, ?Response $response, ?Throwable $exception) { + post: static function (HttpKernel $kernel, array $params, ?Response $response, ?Throwable $exception) { $scope = Context::storage()->scope(); if (!$scope) { return; @@ -92,6 +94,43 @@ public static function register(): void $span->end(); } ); + + hook( + ConsoleKernel::class, + 'call', + pre: static function (ConsoleKernel $kernel, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { + /** @psalm-suppress ArgumentTypeCoercion */ + $builder = $instrumentation->tracer() + ->spanBuilder(sprintf('Console %s', $params[0] ?? 'unknown')) + ->setSpanKind(SpanKind::KIND_PRODUCER) + ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) + ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) + ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) + ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); + $parent = Context::getCurrent(); + $span = $builder->startSpan(); + Context::storage()->attach($span->storeInContext($parent)); + + return $params; + }, + post: static function (ConsoleKernel $kernel, array $params, ?int $exitCode, ?Throwable $exception) { + $scope = Context::storage()->scope(); + if (!$scope) { + return; + } + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception) { + $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); + $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); + } else if ($exitCode !== Command::SUCCESS) { + $span->setStatus(StatusCode::STATUS_ERROR); + } + + $span->end(); + } + ); + hook( Application::class, '__construct', diff --git a/src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php b/src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php new file mode 100644 index 00000000..9823f145 --- /dev/null +++ b/src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php @@ -0,0 +1,64 @@ +storage = new ArrayObject(); + $tracerProvider = new TracerProvider( + new SimpleSpanProcessor( + new InMemoryExporter($this->storage) + ) + ); + + $this->scope = Configurator::create() + ->withTracerProvider($tracerProvider) + ->activate(); + } + + public function tearDown(): void + { + $this->scope->detach(); + parent::tearDown(); + } + + public function test_command_tracing(): void + { + $this->assertCount(0, $this->storage); + $exitCode = $this->withoutMockingConsoleOutput()->artisan('about'); + $this->assertEquals(Command::SUCCESS, $exitCode); + $this->assertCount(1, $this->storage); + + /** @var ImmutableSpan $span */ + $span = $this->storage->offsetGet(0); + $this->assertSame('Console about', $span->getName()); + $this->assertCount(1, $span->getEvents()); + $event = $span->getEvents()[0]; + $this->assertSame([ + 'command' => 'about', + 'exit-code' => 0, + ], $event->getAttributes()->toArray()); + } +} From da40eafba05d4c25aefbce43053b33d2ae44d9c5 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Sun, 13 Aug 2023 12:08:46 +0100 Subject: [PATCH 09/20] Laravel instrumentation: linting. --- src/Instrumentation/Laravel/src/LaravelInstrumentation.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index 1459c303..25b45655 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -5,8 +5,8 @@ namespace OpenTelemetry\Contrib\Instrumentation\Laravel; use Illuminate\Console\Command; -use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Console\Kernel as ConsoleKernel; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Http\Kernel as HttpKernel; use Illuminate\Http\Request; use OpenTelemetry\API\Globals; @@ -16,10 +16,10 @@ use OpenTelemetry\API\Trace\SpanKind; use OpenTelemetry\API\Trace\StatusCode; use OpenTelemetry\Context\Context; -use function OpenTelemetry\Instrumentation\hook; use OpenTelemetry\SemConv\TraceAttributes; use Symfony\Component\HttpFoundation\Response; use Throwable; +use function OpenTelemetry\Instrumentation\hook; class LaravelInstrumentation { @@ -123,7 +123,7 @@ public static function register(): void if ($exception) { $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); - } else if ($exitCode !== Command::SUCCESS) { + } elseif ($exitCode !== Command::SUCCESS) { $span->setStatus(StatusCode::STATUS_ERROR); } From 792a80d08fdd42ab8e3102ea5953c6256fa0c71e Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Mon, 14 Aug 2023 00:39:06 +0100 Subject: [PATCH 10/20] Laravel instrumentation: ordered_imports fix. --- src/Instrumentation/Laravel/src/LaravelInstrumentation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index 95b21cbf..b67e0dd8 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -16,10 +16,10 @@ use OpenTelemetry\API\Trace\SpanKind; use OpenTelemetry\API\Trace\StatusCode; use OpenTelemetry\Context\Context; +use function OpenTelemetry\Instrumentation\hook; use OpenTelemetry\SemConv\TraceAttributes; use Symfony\Component\HttpFoundation\Response; use Throwable; -use function OpenTelemetry\Instrumentation\hook; class LaravelInstrumentation { From 1eadc5c0dc6e1e9cd2c5ff5fb3edd181151cafa2 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Wed, 23 Aug 2023 13:02:15 +0100 Subject: [PATCH 11/20] Laravel contrib: added "ext-json" to dependencies. --- src/Instrumentation/Laravel/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Instrumentation/Laravel/composer.json b/src/Instrumentation/Laravel/composer.json index fbf39619..fc143a95 100644 --- a/src/Instrumentation/Laravel/composer.json +++ b/src/Instrumentation/Laravel/composer.json @@ -9,8 +9,9 @@ "minimum-stability": "dev", "require": { "php": "^8.0", - "laravel/framework": ">=6.0", + "ext-json": "*", "ext-opentelemetry": "*", + "laravel/framework": ">=6.0", "open-telemetry/api": "^1.0.0beta10", "open-telemetry/sem-conv": "^1" }, From f2b5e316b00f6f85308e1e2f31b27d786042797b Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Mon, 28 Aug 2023 14:03:29 +0100 Subject: [PATCH 12/20] Laravel instrumentation Console/Http split. --- .../Laravel/src/CommandWatcher.php | 32 ----- .../Laravel/src/ConsoleInstrumentation.php | 90 ++++++++++++ .../Laravel/src/HttpInstrumentation.php | 107 ++++++++++++++ .../Laravel/src/LaravelInstrumentation.php | 133 +----------------- ...est.php => ConsoleInstrumentationTest.php} | 19 ++- 5 files changed, 213 insertions(+), 168 deletions(-) delete mode 100644 src/Instrumentation/Laravel/src/CommandWatcher.php create mode 100644 src/Instrumentation/Laravel/src/ConsoleInstrumentation.php create mode 100644 src/Instrumentation/Laravel/src/HttpInstrumentation.php rename src/Instrumentation/Laravel/tests/Integration/{CommandWatcherTest.php => ConsoleInstrumentationTest.php} (75%) diff --git a/src/Instrumentation/Laravel/src/CommandWatcher.php b/src/Instrumentation/Laravel/src/CommandWatcher.php deleted file mode 100644 index 35a25492..00000000 --- a/src/Instrumentation/Laravel/src/CommandWatcher.php +++ /dev/null @@ -1,32 +0,0 @@ -listen(CommandFinished::class, [$this, 'recordCommandFinished']); - } - - public function recordCommandFinished(CommandFinished $command): void - { - $scope = Context::storage()->scope(); - if (!$scope) { - return; - } - $span = Span::fromContext($scope->context()); - $span->addEvent('command finished', [ - 'command' => $command->command, - 'exit-code' => $command->exitCode, - ]); - } -} diff --git a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php new file mode 100644 index 00000000..01185e04 --- /dev/null +++ b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php @@ -0,0 +1,90 @@ +tracer() + ->spanBuilder('Artisan handler') + ->setSpanKind(SpanKind::KIND_PRODUCER) + ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) + ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) + ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) + ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); + $parent = Context::getCurrent(); + $span = $builder->startSpan(); + Context::storage()->attach($span->storeInContext($parent)); + + return $params; + }, + post: static function (Kernel $kernel, array $params, ?int $exitCode, ?Throwable $exception) { + $scope = Context::storage()->scope(); + if (!$scope) { + return; + } + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception) { + $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); + $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); + } elseif ($exitCode !== Command::SUCCESS) { + $span->setStatus(StatusCode::STATUS_ERROR); + } else { + $span->setStatus(StatusCode::STATUS_OK); + } + + $span->end(); + } + ); + + hook( + Command::class, + 'execute', + pre: static function (Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { + $scope = Context::storage()->scope(); + $span = Span::fromContext($scope->context()); + $span->addEvent('command starting', [ + 'command' => $command->getName(), + ]); + + return $params; + }, + post: static function (Command $command, array $params, ?int $exitCode, ?Throwable $exception) { + $scope = Context::storage()->scope(); + if (!$scope) { + return; + } + $span = Span::fromContext($scope->context()); + $span->addEvent('command finished', [ + 'command' => $command->getName(), + 'exit-code' => $exitCode, + ]); + + if ($exception) { + $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); + $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); + } + } + ); + } +} diff --git a/src/Instrumentation/Laravel/src/HttpInstrumentation.php b/src/Instrumentation/Laravel/src/HttpInstrumentation.php new file mode 100644 index 00000000..1d5e9291 --- /dev/null +++ b/src/Instrumentation/Laravel/src/HttpInstrumentation.php @@ -0,0 +1,107 @@ +tracer() + ->spanBuilder(sprintf('HTTP %s', $request?->method() ?? 'unknown')) + ->setSpanKind(SpanKind::KIND_SERVER) + ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) + ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) + ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) + ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); + $parent = Context::getCurrent(); + if ($request) { + $parent = Globals::propagator()->extract($request, HeadersPropagator::instance()); + $span = $builder + ->setParent($parent) + ->setAttribute(TraceAttributes::HTTP_URL, $request->fullUrl()) + ->setAttribute(TraceAttributes::HTTP_METHOD, $request->method()) + ->setAttribute(TraceAttributes::HTTP_REQUEST_CONTENT_LENGTH, $request->header('Content-Length')) + ->setAttribute(TraceAttributes::HTTP_SCHEME, $request->getScheme()) + ->setAttribute(TraceAttributes::HTTP_FLAVOR, $request->getProtocolVersion()) + ->setAttribute(TraceAttributes::HTTP_CLIENT_IP, $request->ip()) + ->setAttribute(TraceAttributes::HTTP_TARGET, self::httpTarget($request)) + ->setAttribute(TraceAttributes::NET_HOST_NAME, self::httpHostName($request)) + ->setAttribute(TraceAttributes::NET_HOST_PORT, $request->getPort()) + ->setAttribute(TraceAttributes::NET_PEER_PORT, $request->server('REMOTE_PORT')) + ->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->userAgent()) + ->startSpan(); + $request->attributes->set(SpanInterface::class, $span); + } else { + $span = $builder->startSpan(); + } + Context::storage()->attach($span->storeInContext($parent)); + + return [$request]; + }, + post: static function (Kernel $kernel, array $params, ?Response $response, ?Throwable $exception) { + $scope = Context::storage()->scope(); + if (!$scope) { + return; + } + $scope->detach(); + $span = Span::fromContext($scope->context()); + if ($exception) { + $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); + $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); + } + if ($response) { + if ($response->getStatusCode() >= 400) { + $span->setStatus(StatusCode::STATUS_ERROR); + } + $span->setAttribute(TraceAttributes::HTTP_STATUS_CODE, $response->getStatusCode()); + $span->setAttribute(TraceAttributes::HTTP_FLAVOR, $response->getProtocolVersion()); + $span->setAttribute(TraceAttributes::HTTP_RESPONSE_CONTENT_LENGTH, $response->headers->get('Content-Length')); + } + + $span->end(); + } + ); + } + + private static function httpTarget(Request $request): string + { + $query = $request->getQueryString(); + $question = $request->getBaseUrl() . $request->getPathInfo() === '/' ? '/?' : '?'; + + return $query ? $request->path() . $question . $query : $request->path(); + } + + private static function httpHostName(Request $request): string + { + if (method_exists($request, 'host')) { + return $request->host(); + } + if (method_exists($request, 'getHost')) { + return $request->getHost(); + } + + return ''; + } +} diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index b67e0dd8..f26fab69 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -4,21 +4,9 @@ namespace OpenTelemetry\Contrib\Instrumentation\Laravel; -use Illuminate\Console\Command; -use Illuminate\Contracts\Console\Kernel as ConsoleKernel; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Http\Kernel as HttpKernel; -use Illuminate\Http\Request; -use OpenTelemetry\API\Globals; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; -use OpenTelemetry\API\Trace\Span; -use OpenTelemetry\API\Trace\SpanInterface; -use OpenTelemetry\API\Trace\SpanKind; -use OpenTelemetry\API\Trace\StatusCode; -use OpenTelemetry\Context\Context; use function OpenTelemetry\Instrumentation\hook; -use OpenTelemetry\SemConv\TraceAttributes; -use Symfony\Component\HttpFoundation\Response; use Throwable; class LaravelInstrumentation @@ -33,135 +21,20 @@ public static function registerWatchers(Application $app, Watcher $watcher) public static function register(): void { $instrumentation = new CachedInstrumentation('io.opentelemetry.contrib.php.laravel'); - hook( - HttpKernel::class, - 'handle', - pre: static function (HttpKernel $kernel, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { - $request = ($params[0] instanceof Request) ? $params[0] : null; - /** @psalm-suppress ArgumentTypeCoercion */ - $builder = $instrumentation->tracer() - ->spanBuilder(sprintf('HTTP %s', $request?->method() ?? 'unknown')) - ->setSpanKind(SpanKind::KIND_SERVER) - ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) - ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) - ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) - ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); - $parent = Context::getCurrent(); - if ($request) { - $parent = Globals::propagator()->extract($request, HeadersPropagator::instance()); - $span = $builder - ->setParent($parent) - ->setAttribute(TraceAttributes::HTTP_URL, $request->fullUrl()) - ->setAttribute(TraceAttributes::HTTP_METHOD, $request->method()) - ->setAttribute(TraceAttributes::HTTP_REQUEST_CONTENT_LENGTH, $request->header('Content-Length')) - ->setAttribute(TraceAttributes::HTTP_SCHEME, $request->getScheme()) - ->setAttribute(TraceAttributes::HTTP_FLAVOR, $request->getProtocolVersion()) - ->setAttribute(TraceAttributes::HTTP_CLIENT_IP, $request->ip()) - ->setAttribute(TraceAttributes::HTTP_TARGET, self::httpTarget($request)) - ->setAttribute(TraceAttributes::NET_HOST_NAME, self::httpHostName($request)) - ->setAttribute(TraceAttributes::NET_HOST_PORT, $request->getPort()) - ->setAttribute(TraceAttributes::NET_PEER_PORT, $request->server('REMOTE_PORT')) - ->setAttribute(TraceAttributes::USER_AGENT_ORIGINAL, $request->userAgent()) - ->startSpan(); - $request->attributes->set(SpanInterface::class, $span); - } else { - $span = $builder->startSpan(); - } - Context::storage()->attach($span->storeInContext($parent)); - - return [$request]; - }, - post: static function (HttpKernel $kernel, array $params, ?Response $response, ?Throwable $exception) { - $scope = Context::storage()->scope(); - if (!$scope) { - return; - } - $scope->detach(); - $span = Span::fromContext($scope->context()); - if ($exception) { - $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); - $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); - } - if ($response) { - if ($response->getStatusCode() >= 400) { - $span->setStatus(StatusCode::STATUS_ERROR); - } - $span->setAttribute(TraceAttributes::HTTP_STATUS_CODE, $response->getStatusCode()); - $span->setAttribute(TraceAttributes::HTTP_FLAVOR, $response->getProtocolVersion()); - $span->setAttribute(TraceAttributes::HTTP_RESPONSE_CONTENT_LENGTH, $response->headers->get('Content-Length')); - } - - $span->end(); - } - ); - - hook( - ConsoleKernel::class, - 'call', - pre: static function (ConsoleKernel $kernel, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { - /** @psalm-suppress ArgumentTypeCoercion */ - $builder = $instrumentation->tracer() - ->spanBuilder(sprintf('Console %s', $params[0] ?? 'unknown')) - ->setSpanKind(SpanKind::KIND_PRODUCER) - ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) - ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) - ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) - ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); - $parent = Context::getCurrent(); - $span = $builder->startSpan(); - Context::storage()->attach($span->storeInContext($parent)); - - return $params; - }, - post: static function (ConsoleKernel $kernel, array $params, ?int $exitCode, ?Throwable $exception) { - $scope = Context::storage()->scope(); - if (!$scope) { - return; - } - $scope->detach(); - $span = Span::fromContext($scope->context()); - if ($exception) { - $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); - $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); - } elseif ($exitCode !== Command::SUCCESS) { - $span->setStatus(StatusCode::STATUS_ERROR); - } - - $span->end(); - } - ); hook( Application::class, '__construct', post: static function (Application $application, array $params, mixed $returnValue, ?Throwable $exception) use ($instrumentation) { + self::registerWatchers($application, new CacheWatcher()); self::registerWatchers($application, new ClientRequestWatcher($instrumentation)); self::registerWatchers($application, new ExceptionWatcher()); - self::registerWatchers($application, new CacheWatcher()); - self::registerWatchers($application, new CommandWatcher()); self::registerWatchers($application, new LogWatcher()); self::registerWatchers($application, new QueryWatcher($instrumentation)); }, ); - } - - private static function httpTarget(Request $request): string - { - $query = $request->getQueryString(); - $question = $request->getBaseUrl() . $request->getPathInfo() === '/' ? '/?' : '?'; - - return $query ? $request->path() . $question . $query : $request->path(); - } - - private static function httpHostName(Request $request): string - { - if (method_exists($request, 'host')) { - return $request->host(); - } - if (method_exists($request, 'getHost')) { - return $request->getHost(); - } - return ''; + ConsoleInstrumentation::register($instrumentation); + HttpInstrumentation::register($instrumentation); } } diff --git a/src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php similarity index 75% rename from src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php rename to src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php index 9823f145..307a50e4 100644 --- a/src/Instrumentation/Laravel/tests/Integration/CommandWatcherTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php @@ -6,6 +6,7 @@ use ArrayObject; use Illuminate\Console\Command; +use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Testing\WithConsoleEvents; use OpenTelemetry\API\Instrumentation\Configurator; use OpenTelemetry\Context\ScopeInterface; @@ -15,7 +16,7 @@ use OpenTelemetry\SDK\Trace\TracerProvider; use OpenTelemetry\Tests\Instrumentation\Laravel\TestCase; -class CommandWatcherTest extends TestCase +class ConsoleInstrumentationTest extends TestCase { use WithConsoleEvents; @@ -47,18 +48,24 @@ public function tearDown(): void public function test_command_tracing(): void { $this->assertCount(0, $this->storage); - $exitCode = $this->withoutMockingConsoleOutput()->artisan('about'); + + /** @var Kernel $kernel */ + $kernel = $this->app[Kernel::class]; + $exitCode = $kernel->handle( + new \Symfony\Component\Console\Input\ArrayInput(['optimize:clear']), + new \Symfony\Component\Console\Output\NullOutput(), + ); + $this->assertEquals(Command::SUCCESS, $exitCode); $this->assertCount(1, $this->storage); /** @var ImmutableSpan $span */ $span = $this->storage->offsetGet(0); - $this->assertSame('Console about', $span->getName()); - $this->assertCount(1, $span->getEvents()); + $this->assertSame('Artisan handler', $span->getName()); + $this->assertCount(14, $span->getEvents()); $event = $span->getEvents()[0]; $this->assertSame([ - 'command' => 'about', - 'exit-code' => 0, + 'command' => 'optimize:clear', ], $event->getAttributes()->toArray()); } } From 4ed45f8de77a95e757be3dc44dd216eeb0c2b029 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Mon, 28 Aug 2023 14:35:48 +0100 Subject: [PATCH 13/20] Laravel console instrumentation checks scope before using it in command execute pre-hook. --- src/Instrumentation/Laravel/src/ConsoleInstrumentation.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php index 01185e04..e89652b1 100644 --- a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php +++ b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php @@ -62,6 +62,9 @@ public static function register(CachedInstrumentation $instrumentation): void 'execute', pre: static function (Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { $scope = Context::storage()->scope(); + if (!$scope) { + return $params; + } $span = Span::fromContext($scope->context()); $span->addEvent('command starting', [ 'command' => $command->getName(), From 632df00c4cd2bfd1b6e8d46051a9fad6bd665f87 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Mon, 28 Aug 2023 15:05:49 +0100 Subject: [PATCH 14/20] Laravel linting. --- src/Instrumentation/Laravel/src/HeadersPropagator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Instrumentation/Laravel/src/HeadersPropagator.php b/src/Instrumentation/Laravel/src/HeadersPropagator.php index f7aa407d..423990e6 100644 --- a/src/Instrumentation/Laravel/src/HeadersPropagator.php +++ b/src/Instrumentation/Laravel/src/HeadersPropagator.php @@ -24,6 +24,7 @@ public static function instance(): self public function keys($carrier): array { assert($carrier instanceof Request); + /** @psalm-suppress InvalidReturnStatement */ return $carrier->headers->keys(); } From d3e55ba8fd2db5d92e094f26bd998f15f858b793 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Mon, 28 Aug 2023 15:12:09 +0100 Subject: [PATCH 15/20] Laravel: removed now redundant WithConsoleEvents trait. --- .../Laravel/tests/Integration/ConsoleInstrumentationTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php index 307a50e4..7ff52e47 100644 --- a/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php @@ -7,7 +7,6 @@ use ArrayObject; use Illuminate\Console\Command; use Illuminate\Contracts\Console\Kernel; -use Illuminate\Foundation\Testing\WithConsoleEvents; use OpenTelemetry\API\Instrumentation\Configurator; use OpenTelemetry\Context\ScopeInterface; use OpenTelemetry\SDK\Trace\ImmutableSpan; @@ -18,8 +17,6 @@ class ConsoleInstrumentationTest extends TestCase { - use WithConsoleEvents; - private ScopeInterface $scope; private ArrayObject $storage; From 973f695846c4b47ea2e68226a3524c59a778e004 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Mon, 28 Aug 2023 15:41:48 +0100 Subject: [PATCH 16/20] Laravel: removed unused `use ($instrumentation)`. --- src/Instrumentation/Laravel/src/ConsoleInstrumentation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php index e89652b1..975bb834 100644 --- a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php +++ b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php @@ -60,7 +60,7 @@ public static function register(CachedInstrumentation $instrumentation): void hook( Command::class, 'execute', - pre: static function (Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { + pre: static function (Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) { $scope = Context::storage()->scope(); if (!$scope) { return $params; From bc4c216bd9af80cf8e30436c2bd6746004ec601d Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Sat, 2 Sep 2023 19:29:33 +0100 Subject: [PATCH 17/20] Laravel: Moved Watchers into \OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers. --- src/Instrumentation/Laravel/src/LaravelInstrumentation.php | 6 ++++++ .../Laravel/src/{ => Watchers}/CacheWatcher.php | 2 +- .../Laravel/src/{ => Watchers}/ClientRequestWatcher.php | 2 +- .../Laravel/src/{ => Watchers}/ExceptionWatcher.php | 2 +- .../Laravel/src/{ => Watchers}/LogWatcher.php | 2 +- .../Laravel/src/{ => Watchers}/QueryWatcher.php | 2 +- src/Instrumentation/Laravel/src/{ => Watchers}/Watcher.php | 2 +- 7 files changed, 12 insertions(+), 6 deletions(-) rename src/Instrumentation/Laravel/src/{ => Watchers}/CacheWatcher.php (97%) rename src/Instrumentation/Laravel/src/{ => Watchers}/ClientRequestWatcher.php (98%) rename src/Instrumentation/Laravel/src/{ => Watchers}/ExceptionWatcher.php (95%) rename src/Instrumentation/Laravel/src/{ => Watchers}/LogWatcher.php (93%) rename src/Instrumentation/Laravel/src/{ => Watchers}/QueryWatcher.php (97%) rename src/Instrumentation/Laravel/src/{ => Watchers}/Watcher.php (76%) diff --git a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php index f26fab69..cd7ea6ce 100644 --- a/src/Instrumentation/Laravel/src/LaravelInstrumentation.php +++ b/src/Instrumentation/Laravel/src/LaravelInstrumentation.php @@ -6,6 +6,12 @@ use Illuminate\Contracts\Foundation\Application; use OpenTelemetry\API\Instrumentation\CachedInstrumentation; +use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\CacheWatcher; +use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\ClientRequestWatcher; +use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\ExceptionWatcher; +use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\LogWatcher; +use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\QueryWatcher; +use OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers\Watcher; use function OpenTelemetry\Instrumentation\hook; use Throwable; diff --git a/src/Instrumentation/Laravel/src/CacheWatcher.php b/src/Instrumentation/Laravel/src/Watchers/CacheWatcher.php similarity index 97% rename from src/Instrumentation/Laravel/src/CacheWatcher.php rename to src/Instrumentation/Laravel/src/Watchers/CacheWatcher.php index b3daf710..d4761f41 100644 --- a/src/Instrumentation/Laravel/src/CacheWatcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/CacheWatcher.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers; use Illuminate\Cache\Events\CacheHit; use Illuminate\Cache\Events\CacheMissed; diff --git a/src/Instrumentation/Laravel/src/ClientRequestWatcher.php b/src/Instrumentation/Laravel/src/Watchers/ClientRequestWatcher.php similarity index 98% rename from src/Instrumentation/Laravel/src/ClientRequestWatcher.php rename to src/Instrumentation/Laravel/src/Watchers/ClientRequestWatcher.php index f7db97f7..6e2226f5 100644 --- a/src/Instrumentation/Laravel/src/ClientRequestWatcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/ClientRequestWatcher.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers; use Illuminate\Contracts\Foundation\Application; use Illuminate\Http\Client\Events\ConnectionFailed; diff --git a/src/Instrumentation/Laravel/src/ExceptionWatcher.php b/src/Instrumentation/Laravel/src/Watchers/ExceptionWatcher.php similarity index 95% rename from src/Instrumentation/Laravel/src/ExceptionWatcher.php rename to src/Instrumentation/Laravel/src/Watchers/ExceptionWatcher.php index 4ce20ed4..31bdc00d 100644 --- a/src/Instrumentation/Laravel/src/ExceptionWatcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/ExceptionWatcher.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\Events\MessageLogged; diff --git a/src/Instrumentation/Laravel/src/LogWatcher.php b/src/Instrumentation/Laravel/src/Watchers/LogWatcher.php similarity index 93% rename from src/Instrumentation/Laravel/src/LogWatcher.php rename to src/Instrumentation/Laravel/src/Watchers/LogWatcher.php index 85cd4dbc..370b501d 100644 --- a/src/Instrumentation/Laravel/src/LogWatcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/LogWatcher.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers; use Illuminate\Contracts\Foundation\Application; use Illuminate\Log\Events\MessageLogged; diff --git a/src/Instrumentation/Laravel/src/QueryWatcher.php b/src/Instrumentation/Laravel/src/Watchers/QueryWatcher.php similarity index 97% rename from src/Instrumentation/Laravel/src/QueryWatcher.php rename to src/Instrumentation/Laravel/src/Watchers/QueryWatcher.php index c3f0347b..eed90c1e 100644 --- a/src/Instrumentation/Laravel/src/QueryWatcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/QueryWatcher.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers; use Illuminate\Contracts\Foundation\Application; use Illuminate\Database\Events\QueryExecuted; diff --git a/src/Instrumentation/Laravel/src/Watcher.php b/src/Instrumentation/Laravel/src/Watchers/Watcher.php similarity index 76% rename from src/Instrumentation/Laravel/src/Watcher.php rename to src/Instrumentation/Laravel/src/Watchers/Watcher.php index 759a9bc8..67540bae 100644 --- a/src/Instrumentation/Laravel/src/Watcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/Watcher.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace OpenTelemetry\Contrib\Instrumentation\Laravel; +namespace OpenTelemetry\Contrib\Instrumentation\Laravel\Watchers; use Illuminate\Contracts\Foundation\Application; From 2c18066853a3581e982f8a887ea02ee5025c5a54 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Sun, 3 Sep 2023 11:54:18 +0100 Subject: [PATCH 18/20] Laravel: ConsoleInstrumentation updates. --- .../Laravel/src/ConsoleInstrumentation.php | 23 +++++++++++-------- .../ConsoleInstrumentationTest.php | 9 +++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php index 975bb834..ce52d194 100644 --- a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php +++ b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php @@ -60,15 +60,18 @@ public static function register(CachedInstrumentation $instrumentation): void hook( Command::class, 'execute', - pre: static function (Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) { - $scope = Context::storage()->scope(); - if (!$scope) { - return $params; - } - $span = Span::fromContext($scope->context()); - $span->addEvent('command starting', [ - 'command' => $command->getName(), - ]); + pre: static function (Command $command, array $params, string $class, string $function, ?string $filename, ?int $lineno) use ($instrumentation) { + /** @psalm-suppress ArgumentTypeCoercion */ + $builder = $instrumentation->tracer() + ->spanBuilder(sprintf('Command %s', $command->getName() ?: 'unknown')) + ->setAttribute(TraceAttributes::CODE_FUNCTION, $function) + ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) + ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) + ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); + $parent = Context::getCurrent(); + + $span = $builder->startSpan(); + Context::storage()->attach($span->storeInContext($parent)); return $params; }, @@ -77,9 +80,9 @@ public static function register(CachedInstrumentation $instrumentation): void if (!$scope) { return; } + $scope->detach(); $span = Span::fromContext($scope->context()); $span->addEvent('command finished', [ - 'command' => $command->getName(), 'exit-code' => $exitCode, ]); diff --git a/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php index 7ff52e47..5a6bef1b 100644 --- a/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php @@ -54,15 +54,12 @@ public function test_command_tracing(): void ); $this->assertEquals(Command::SUCCESS, $exitCode); - $this->assertCount(1, $this->storage); + $this->assertCount(8, $this->storage); /** @var ImmutableSpan $span */ $span = $this->storage->offsetGet(0); $this->assertSame('Artisan handler', $span->getName()); - $this->assertCount(14, $span->getEvents()); - $event = $span->getEvents()[0]; - $this->assertSame([ - 'command' => 'optimize:clear', - ], $event->getAttributes()->toArray()); + $span = $this->storage->offsetGet(1); + $this->assertSame('Command optimize:clear', $span->getName()); } } From 22dd5b0b0f61f5bbcf3b4618011f199696b0bc98 Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Wed, 20 Sep 2023 00:15:29 +0100 Subject: [PATCH 19/20] Laravel: end span post Command::execute hook. --- src/Instrumentation/Laravel/src/ConsoleInstrumentation.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php index ce52d194..05fa0688 100644 --- a/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php +++ b/src/Instrumentation/Laravel/src/ConsoleInstrumentation.php @@ -31,6 +31,7 @@ public static function register(CachedInstrumentation $instrumentation): void ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); + $parent = Context::getCurrent(); $span = $builder->startSpan(); Context::storage()->attach($span->storeInContext($parent)); @@ -42,6 +43,7 @@ public static function register(CachedInstrumentation $instrumentation): void if (!$scope) { return; } + $scope->detach(); $span = Span::fromContext($scope->context()); if ($exception) { @@ -68,8 +70,8 @@ public static function register(CachedInstrumentation $instrumentation): void ->setAttribute(TraceAttributes::CODE_NAMESPACE, $class) ->setAttribute(TraceAttributes::CODE_FILEPATH, $filename) ->setAttribute(TraceAttributes::CODE_LINENO, $lineno); - $parent = Context::getCurrent(); + $parent = Context::getCurrent(); $span = $builder->startSpan(); Context::storage()->attach($span->storeInContext($parent)); @@ -80,6 +82,7 @@ public static function register(CachedInstrumentation $instrumentation): void if (!$scope) { return; } + $scope->detach(); $span = Span::fromContext($scope->context()); $span->addEvent('command finished', [ @@ -90,6 +93,8 @@ public static function register(CachedInstrumentation $instrumentation): void $span->recordException($exception, [TraceAttributes::EXCEPTION_ESCAPED => true]); $span->setStatus(StatusCode::STATUS_ERROR, $exception->getMessage()); } + + $span->end(); } ); } From a8138ad43f7b8036da564de5b4a9a124028711bd Mon Sep 17 00:00:00 2001 From: Chris Lightfoot-Wild Date: Wed, 20 Sep 2023 15:27:38 +0100 Subject: [PATCH 20/20] Laravel: fixed ConsoleInstrumentationTest. --- .../Integration/ConsoleInstrumentationTest.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php index 5a6bef1b..c0aab5bc 100644 --- a/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/ConsoleInstrumentationTest.php @@ -54,12 +54,22 @@ public function test_command_tracing(): void ); $this->assertEquals(Command::SUCCESS, $exitCode); - $this->assertCount(8, $this->storage); + + /** + * The storage appends spans as they are marked as ended. eg: `$span->end()`. + * So in this test, `optimize:clear` calls additional commands which complete first + * and thus appear in the stack ahead of it. + * + * @see \Illuminate\Foundation\Console\OptimizeClearCommand::handle() for the additional commands/spans. + */ + $count = 8; + $this->assertCount($count, $this->storage); /** @var ImmutableSpan $span */ - $span = $this->storage->offsetGet(0); + $span = $this->storage->offsetGet(--$count); $this->assertSame('Artisan handler', $span->getName()); - $span = $this->storage->offsetGet(1); + + $span = $this->storage->offsetGet(--$count); $this->assertSame('Command optimize:clear', $span->getName()); } }