Files
michaelschiemer/src/Framework/Console/Analytics/Middleware/AnalyticsCollectionMiddleware.php
Michael Schiemer 70e45fb56e fix(Discovery): Add comprehensive debug logging for router initialization
- 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
2025-10-27 22:23:18 +01:00

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;
}
}