Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added instrumentation for artisan console commands #286

Merged
merged 3 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@ jobs:
uses: octokit/[email protected]
id: get_latest_ext_release
with:
route: GET /repos/{owner}/{repo}/releases/latest
owner: scoutapp
repo: scout-apm-php-ext
route: GET /repos/scoutapp/scout-apm-php-ext/releases/latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: "Install scoutapm extension"
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ coverage: ## generate code coverage reports
$(PHP_PATH) vendor/bin/phpunit --testsuite unit --coverage-html build/coverage-html --coverage-text $(OPTS)

deps-install: ## Install the currently-locked set of dependencies
git restore composer.lock
rm -Rf vendor
$(PHP_PATH) $(COMPOSER_PATH) install

Expand Down
18 changes: 6 additions & 12 deletions known-issues.xml
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,9 @@
</MixedAssignment>
</file>
<file src="src/Laravel/Providers/ScoutApmServiceProvider.php">
<InvalidArgument occurrences="2">
<InvalidArgument occurrences="1">
<code>$app-&gt;make(self::CACHE_SERVICE_KEY)</code>
<code>$app-&gt;make(self::CONFIG_SERVICE_KEY)</code>
</InvalidArgument>
<MixedArgument occurrences="1">
<code>$app-&gt;make(self::CONFIG_SERVICE_KEY)-&gt;get(ConfigKey::LOG_LEVEL)</code>
</MixedArgument>
<UndefinedClass occurrences="1">
<code>$app-&gt;make(self::CONFIG_SERVICE_KEY)</code>
</UndefinedClass>
<UnusedClosureParam occurrences="1">
<code>$event</code>
</UnusedClosureParam>
</file>
<file src="src/Logger/FilteredLogLevelDecorator.php">
<MixedArrayOffset occurrences="1">
Expand Down Expand Up @@ -302,7 +292,11 @@
<PossiblyInvalidArgument occurrences="1">
<code>$this-&gt;application</code>
</PossiblyInvalidArgument>
<UndefinedClass occurrences="6">
<UndefinedClass occurrences="10">
<code>$events</code>
<code>$events</code>
<code>$events</code>
<code>$events</code>
<code>$events</code>
<code>$events</code>
<code>$events</code>
Expand Down
7 changes: 7 additions & 0 deletions src/Helper/Superglobals/Superglobals.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,11 @@ public function env(): array;
* @return array<string, string>
*/
public function server(): array;

/**
* @internal This is not covered by BC promise
*
* @return list<string>
*/
public function argv(): array;
}
39 changes: 37 additions & 2 deletions src/Helper/Superglobals/SuperglobalsArrays.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@

namespace Scoutapm\Helper\Superglobals;

use Throwable;
use Webmozart\Assert\Assert;

use function array_combine;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_values;
use function is_object;
use function is_scalar;
use function method_exists;
Expand All @@ -25,6 +30,8 @@ final class SuperglobalsArrays implements Superglobals
private $env;
/** @var array<string, string> */
private $server;
/** @var list<string> */
private $argv;

/**
* @internal This is not covered by BC promise
Expand All @@ -33,13 +40,34 @@ final class SuperglobalsArrays implements Superglobals
* @param array<array-key, mixed> $request
* @param array<string, string> $env
* @param array<string, string> $server
* @param list<string> $argv
*/
public function __construct(array $session, array $request, array $env, array $server)
public function __construct(array $session, array $request, array $env, array $server, array $argv)
{
$this->session = $session;
$this->request = $request;
$this->env = $env;
$this->server = $server;
$this->argv = $argv;
}

/** @return list<string> */
private static function typeSafeArgvFromGlobals(): array
{
if (! array_key_exists('argv', $GLOBALS)) {
return [];
}

$argv = $GLOBALS['argv'];

try {
Assert::isArray($argv);
Assert::allString($argv);

return array_values($argv);
} catch (Throwable $anything) {
return [];
}
}

/**
Expand All @@ -51,7 +79,8 @@ public static function fromGlobalState(): self
$_SESSION ?? [],
$_REQUEST,
self::convertKeysAndValuesToStrings($_ENV),
self::convertKeysAndValuesToStrings($_SERVER)
self::convertKeysAndValuesToStrings($_SERVER),
self::typeSafeArgvFromGlobals()
);
}

Expand Down Expand Up @@ -109,4 +138,10 @@ public function server(): array
{
return $this->server;
}

/** @return list<string> */
public function argv(): array
{
return $this->argv;
}
}
61 changes: 61 additions & 0 deletions src/Laravel/Console/ConsoleListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Scoutapm\Laravel\Console;

use Exception;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Console\Events\CommandStarting;
use Scoutapm\Events\Span\SpanReference;
use Scoutapm\Events\Tag\Tag;
use Scoutapm\ScoutApmAgent;

use function implode;
use function sprintf;

final class ConsoleListener
{
/** @var ScoutApmAgent */
private $agent;
/** @var list<string> */
private $argv;

/** @param list<string> $argv */
public function __construct(ScoutApmAgent $agent, array $argv)
{
$this->agent = $agent;
$this->argv = $argv;
}

/** @throws Exception */
public function startSpanForCommand(CommandStarting $commandStartingEvent): void
{
if ($commandStartingEvent->command === null) {
return;
}

$this->agent->startNewRequest();

$this->agent->addContext(Tag::TAG_ARGUMENTS, implode(' ', $this->argv));

/** @noinspection UnusedFunctionResultInspection */
$this->agent->startSpan(sprintf(
'%s/artisan/%s',
SpanReference::INSTRUMENT_JOB,
$commandStartingEvent->command
));
}

/** @throws Exception */
public function stopSpanForCommand(CommandFinished $commandFinishedEvent): void
{
if ($commandFinishedEvent->command === null) {
return;
}

$this->agent->stopSpan();
$this->agent->connect();
$this->agent->send();
}
}
27 changes: 25 additions & 2 deletions src/Laravel/Providers/ScoutApmServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Dingo\Api\Routing\Router as DingoRouter;
use Illuminate\Cache\CacheManager;
use Illuminate\Console\Events\CommandFinished;
use Illuminate\Console\Events\CommandStarting;
use Illuminate\Contracts\Config\Repository as ConfigRepository;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Container\Container;
Expand All @@ -30,7 +32,9 @@
use Scoutapm\CoreAgent\Launcher;
use Scoutapm\CoreAgent\Verifier;
use Scoutapm\Helper\ComposerPackagesCheck;
use Scoutapm\Helper\Superglobals\SuperglobalsArrays;
use Scoutapm\Laravel\Console\Commands;
use Scoutapm\Laravel\Console\ConsoleListener;
use Scoutapm\Laravel\Database\QueryListener;
use Scoutapm\Laravel\Middleware\ActionInstrument;
use Scoutapm\Laravel\Middleware\IgnoredEndpoints;
Expand All @@ -51,6 +55,7 @@
use function array_filter;
use function array_map;
use function array_merge;
use function array_splice;
use function config_path;
use function count;
use function sprintf;
Expand All @@ -62,7 +67,8 @@ final class ScoutApmServiceProvider extends ServiceProvider

private const VIEW_ENGINES_TO_WRAP = ['file', 'php', 'blade'];

public const INSTRUMENT_LARAVEL_QUEUES = 'laravel_queues';
public const INSTRUMENT_LARAVEL_QUEUES = 'laravel_queues';
public const INSTRUMENT_LARAVEL_CONSOLE = 'laravel_console';

/** @var bool */
private $resolveViewEngineResolverOnBoot = false;
Expand Down Expand Up @@ -260,6 +266,10 @@ public function boot(
$this->instrumentQueues($agent, $application->make('events'), $runningInConsole);
}

if ($runningInConsole && $agent->shouldInstrument(self::INSTRUMENT_LARAVEL_CONSOLE)) {
$this->instrumentConsole($agent, $application->make('events'));
}

if ($this->resolveViewEngineResolverOnBoot) {
$engineResolver = $this->app->make('view.engine.resolver');
$this->registerWrappedEngines($engineResolver);
Expand Down Expand Up @@ -299,6 +309,19 @@ private function instrumentDatabaseQueries(ScoutApmAgent $agent, Connection $con
});
}

private function instrumentConsole(ScoutApmAgent $agent, Dispatcher $eventDispatcher): void
{
$argv = SuperglobalsArrays::fromGlobalState()->argv();
$listener = new ConsoleListener($agent, array_splice($argv, 2));

$eventDispatcher->listen(CommandStarting::class, static function (CommandStarting $event) use ($listener): void {
$listener->startSpanForCommand($event);
});
$eventDispatcher->listen(CommandFinished::class, static function (CommandFinished $event) use ($listener): void {
$listener->stopSpanForCommand($event);
});
}

private function instrumentQueues(ScoutApmAgent $agent, Dispatcher $eventDispatcher, bool $runningInConsole): void
{
$listener = new JobQueueListener($agent);
Expand All @@ -311,7 +334,7 @@ private function instrumentQueues(ScoutApmAgent $agent, Dispatcher $eventDispatc
$listener->startSpanForJob($event);
});

$eventDispatcher->listen(JobProcessed::class, static function (JobProcessed $event) use ($listener, $runningInConsole): void {
$eventDispatcher->listen(JobProcessed::class, static function () use ($listener, $runningInConsole): void {
$listener->stopSpanForJob();

if (! $runningInConsole) {
Expand Down
5 changes: 3 additions & 2 deletions tests/Unit/Errors/ErrorEventTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function testToJsonableArray(): void
$exception = new RuntimeException($exceptionMessage);

$request = Request::fromConfigAndOverrideTime(
new SuperglobalsArrays([], [], [], []),
new SuperglobalsArrays([], [], [], [], []),
$config,
$this->createMock(FindRequestHeaders::class)
);
Expand Down Expand Up @@ -58,7 +58,8 @@ public function testToJsonableArray(): void
[
'HTTPS' => 'on',
'HTTP_HOST' => 'the-great-website',
]
],
[]
),
$determineHostname,
$findRootPackageGitSha
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function setUp(): void
$this->config = Config::fromArray([]);
$this->logger = new TestLogger();
$this->findApplicationRoot = $this->createMock(FindApplicationRoot::class);
$this->superglobals = new SuperglobalsArrays([], [], [], []);
$this->superglobals = new SuperglobalsArrays([], [], [], [], []);
$this->determineHostname = $this->createMock(DetermineHostname::class);
$this->findRootPackageGitSha = $this->createMock(FindRootPackageGitSha::class);

Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Events/MetadataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ public function testHerokuSlugCommitOverridesTheGitSha(): void
[],
[],
['HEROKU_SLUG_COMMIT' => $testHerokuSlugCommit],
[],
[]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function testConfigurationOverridesApplicationRoot(): void
$findApplicationRoot = new FindApplicationRootWithConfigOverride(
$this->locateFileOrFolder,
Config::fromArray([Config\ConfigKey::APPLICATION_ROOT => '/my/configured/app/root']),
new SuperglobalsArrays([], [], [], ['DOCUMENT_ROOT' => '/my/document/root/path'])
new SuperglobalsArrays([], [], [], ['DOCUMENT_ROOT' => '/my/document/root/path'], [])
);

self::assertSame('/my/configured/app/root', ($findApplicationRoot)());
Expand All @@ -40,7 +40,7 @@ public function testComposerJsonLocationCanBeUsedAsApplicationRoot(): void
$findApplicationRoot = new FindApplicationRootWithConfigOverride(
$this->locateFileOrFolder,
Config::fromArray([]),
new SuperglobalsArrays([], [], [], ['DOCUMENT_ROOT' => '/my/document/root/path'])
new SuperglobalsArrays([], [], [], ['DOCUMENT_ROOT' => '/my/document/root/path'], [])
);

$this->locateFileOrFolder
Expand All @@ -57,7 +57,7 @@ public function testMissingDocumentRootInServerWillReturnEmptyString(): void
$findApplicationRoot = new FindApplicationRootWithConfigOverride(
$this->locateFileOrFolder,
Config::fromArray([]),
new SuperglobalsArrays([], [], [], [])
new SuperglobalsArrays([], [], [], [], [])
);
self::assertSame('', ($findApplicationRoot)());
}
Expand All @@ -67,7 +67,7 @@ public function testDocumentRootIsReturned(): void
$findApplicationRoot = new FindApplicationRootWithConfigOverride(
$this->locateFileOrFolder,
Config::fromArray([]),
new SuperglobalsArrays([], [], [], ['DOCUMENT_ROOT' => '/my/document/root/path'])
new SuperglobalsArrays([], [], [], ['DOCUMENT_ROOT' => '/my/document/root/path'], [])
);
self::assertSame('/my/document/root/path', ($findApplicationRoot)());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ public function testFromServerGlobal(): void
'HTTP_ACCEPT' => '*/*',
'HTTP_X_SOMETHING_EMPTY' => '',
'HTTP_X_SOMETHING_CUSTOM' => 'Something custom',
]
],
[]
)))->__invoke()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function testFindingRootPackageGitShaFromConfigOverride(): void
'abcdef',
(new FindRootPackageGitShaWithHerokuAndConfigOverride(
Config::fromArray([Config\ConfigKey::REVISION_SHA => 'abcdef']),
new SuperglobalsArrays([], [], [], [])
new SuperglobalsArrays([], [], [], [], [])
))()
);
}
Expand All @@ -30,7 +30,7 @@ public function testFindingRootPackageGitShaFromHerokuSlugCommit(): void
'bcdef1',
(new FindRootPackageGitShaWithHerokuAndConfigOverride(
Config::fromArray([]),
new SuperglobalsArrays([], [], ['HEROKU_SLUG_COMMIT' => 'bcdef1'], [])
new SuperglobalsArrays([], [], ['HEROKU_SLUG_COMMIT' => 'bcdef1'], [], [])
))()
);
}
Expand All @@ -41,7 +41,7 @@ public function testFindingRootPackageGitShaFallbackUsingComposer(): void
InstalledVersions::getRootPackage()['reference'],
(new FindRootPackageGitShaWithHerokuAndConfigOverride(
Config::fromArray([]),
new SuperglobalsArrays([], [], [], [])
new SuperglobalsArrays([], [], [], [], [])
))()
);
}
Expand Down
Loading