- Add initializer count logging in DiscoveryServiceBootstrapper - Add route structure analysis in RouterSetup - Add request parameter logging in HttpRouter - Update PHP production config for better OPcache handling - Fix various config and error handling improvements
121 lines
3.8 KiB
PHP
121 lines
3.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console\Analytics\Middleware;
|
|
|
|
use App\Framework\Config\AppConfig;
|
|
use App\Framework\Config\Environment;
|
|
use App\Framework\Console\Analytics\Repository\CommandUsageRepository;
|
|
use App\Framework\Console\Analytics\ValueObjects\CommandUsageMetric;
|
|
use App\Framework\Console\ExitCode;
|
|
use App\Framework\Console\Input\ConsoleInput;
|
|
use App\Framework\Console\Middleware\ConsoleMiddleware;
|
|
use App\Framework\Console\Output\ConsoleOutput;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use DateTimeImmutable;
|
|
|
|
final readonly class AnalyticsCollectionMiddleware implements ConsoleMiddleware
|
|
{
|
|
public function __construct(
|
|
private CommandUsageRepository $repository,
|
|
private AppConfig $appConfig,
|
|
private Environment $environment,
|
|
private bool $enabled = true
|
|
) {
|
|
}
|
|
|
|
public function handle(ConsoleInput $input, ConsoleOutput $output, callable $next): ExitCode
|
|
{
|
|
if (! $this->enabled) {
|
|
return $next($input, $output);
|
|
}
|
|
|
|
$commandName = $this->extractCommandName($input);
|
|
$startTime = hrtime(true);
|
|
$executedAt = new DateTimeImmutable();
|
|
|
|
try {
|
|
$exitCode = $next($input, $output);
|
|
} catch (\Throwable $e) {
|
|
// Record the failure and re-throw
|
|
$this->recordUsage(
|
|
$commandName,
|
|
$executedAt,
|
|
$startTime,
|
|
ExitCode::GENERAL_ERROR,
|
|
$input
|
|
);
|
|
|
|
throw $e;
|
|
}
|
|
|
|
$this->recordUsage($commandName, $executedAt, $startTime, $exitCode, $input);
|
|
|
|
return $exitCode;
|
|
}
|
|
|
|
private function recordUsage(
|
|
string $commandName,
|
|
DateTimeImmutable $executedAt,
|
|
int $startTime,
|
|
ExitCode $exitCode,
|
|
ConsoleInput $input
|
|
): void {
|
|
try {
|
|
$endTime = hrtime(true);
|
|
$executionTimeNs = $endTime - $startTime;
|
|
$executionTime = Duration::fromNanoseconds($executionTimeNs);
|
|
|
|
$metric = CommandUsageMetric::create(
|
|
commandName: $commandName,
|
|
executionTime: $executionTime,
|
|
exitCode: $exitCode,
|
|
argumentCount: count($input->getArguments()),
|
|
userId: $this->getCurrentUserId(),
|
|
metadata: $this->collectMetadata($input, $exitCode)
|
|
);
|
|
|
|
$this->repository->store($metric);
|
|
} catch (\Throwable $e) {
|
|
// Analytics collection should never break the command execution
|
|
// In production, this could be logged to a separate error log
|
|
error_log("Analytics collection failed: " . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
private function extractCommandName(ConsoleInput $input): string
|
|
{
|
|
$arguments = $input->getArguments();
|
|
|
|
// First argument after script name is usually the command
|
|
return $arguments[1] ?? 'unknown';
|
|
}
|
|
|
|
private function getCurrentUserId(): ?string
|
|
{
|
|
// In a real implementation, this would extract user ID from context
|
|
// Could be from environment variables, session data, etc.
|
|
return $this->environment->getString('CONSOLE_USER_ID');
|
|
}
|
|
|
|
private function collectMetadata(ConsoleInput $input, ExitCode $exitCode): array
|
|
{
|
|
$metadata = [
|
|
'options' => $input->getOptions(),
|
|
'has_stdin' => ! empty(stream_get_contents(STDIN, 1)),
|
|
'php_version' => PHP_VERSION,
|
|
'memory_peak' => memory_get_peak_usage(true),
|
|
'exit_code_name' => $exitCode->name,
|
|
'environment' => $this->appConfig->type->value,
|
|
];
|
|
|
|
// Add process info
|
|
if (function_exists('posix_getpid')) {
|
|
$metadata['process_id'] = posix_getpid();
|
|
}
|
|
|
|
return $metadata;
|
|
}
|
|
}
|