The MethodSignatureAnalyzer was rejecting command methods with void return type, causing the schedule:run command to fail validation.
180 lines
4.7 KiB
PHP
180 lines
4.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\ExceptionHandling\Metrics;
|
|
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Cache\CacheItem;
|
|
use App\Framework\Cache\CacheKey;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\ExceptionHandling\Context\ExceptionContextData;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Exception Metrics Collector
|
|
*
|
|
* Collects metrics for exceptions (rate, top exceptions, error rate by component).
|
|
*/
|
|
final readonly class ExceptionMetricsCollector
|
|
{
|
|
private const string CACHE_PREFIX = 'exception_metrics:';
|
|
private const int METRICS_TTL = 3600; // 1 hour
|
|
|
|
public function __construct(
|
|
private Cache $cache
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Record exception metric
|
|
*
|
|
* @param Throwable $exception Exception to record
|
|
* @param ExceptionContextData|null $context Optional context
|
|
* @param float|null $executionTimeMs Optional execution time in milliseconds
|
|
*/
|
|
public function record(
|
|
Throwable $exception,
|
|
?ExceptionContextData $context = null,
|
|
?float $executionTimeMs = null
|
|
): void {
|
|
$exceptionClass = get_class($exception);
|
|
$component = $context?->component ?? 'unknown';
|
|
|
|
// Increment total count
|
|
$this->incrementMetric('total');
|
|
|
|
// Increment by class
|
|
$this->incrementMetric('class:' . $exceptionClass);
|
|
|
|
// Increment by component
|
|
$this->incrementMetric('component:' . $component);
|
|
|
|
// Record execution time if available
|
|
if ($executionTimeMs !== null) {
|
|
$this->recordExecutionTime($executionTimeMs);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current metrics
|
|
*
|
|
* @return ExceptionMetrics Current metrics
|
|
*/
|
|
public function getMetrics(): ExceptionMetrics
|
|
{
|
|
$totalCount = $this->getMetric('total');
|
|
$byClass = $this->getMetricsByPrefix('class:');
|
|
$byComponent = $this->getMetricsByPrefix('component:');
|
|
$avgExecutionTime = $this->getAverageExecutionTime();
|
|
|
|
return new ExceptionMetrics(
|
|
totalCount: $totalCount,
|
|
byClass: $byClass,
|
|
byComponent: $byComponent,
|
|
averageExecutionTimeMs: $avgExecutionTime
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Increment metric
|
|
*/
|
|
private function incrementMetric(string $metricName): void
|
|
{
|
|
$cacheKey = CacheKey::fromString(self::CACHE_PREFIX . $metricName);
|
|
$current = $this->getMetricValue($cacheKey);
|
|
$newValue = $current + 1;
|
|
|
|
$cacheItem = CacheItem::fromKey(
|
|
$cacheKey,
|
|
$newValue,
|
|
Duration::fromSeconds(self::METRICS_TTL)
|
|
);
|
|
|
|
$this->cache->set($cacheItem);
|
|
}
|
|
|
|
/**
|
|
* Get metric value
|
|
*/
|
|
private function getMetric(string $metricName): int
|
|
{
|
|
$cacheKey = CacheKey::fromString(self::CACHE_PREFIX . $metricName);
|
|
|
|
return $this->getMetricValue($cacheKey);
|
|
}
|
|
|
|
/**
|
|
* Get metric value from cache
|
|
*/
|
|
private function getMetricValue(CacheKey $cacheKey): int
|
|
{
|
|
$result = $this->cache->get($cacheKey);
|
|
$item = $result->getItem($cacheKey);
|
|
|
|
if (! $item->isHit) {
|
|
return 0;
|
|
}
|
|
|
|
$value = $item->value;
|
|
|
|
return is_int($value) ? $value : 0;
|
|
}
|
|
|
|
/**
|
|
* Get metrics by prefix
|
|
*/
|
|
private function getMetricsByPrefix(string $prefix): array
|
|
{
|
|
// Simplified implementation - in production, would use cache prefix scanning
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Record execution time
|
|
*/
|
|
private function recordExecutionTime(float $executionTimeMs): void
|
|
{
|
|
$cacheKey = CacheKey::fromString(self::CACHE_PREFIX . 'execution_times');
|
|
$result = $this->cache->get($cacheKey);
|
|
$item = $result->getItem($cacheKey);
|
|
|
|
$times = $item->isHit && is_array($item->value) ? $item->value : [];
|
|
$times[] = $executionTimeMs;
|
|
|
|
// Keep only last 1000 execution times
|
|
if (count($times) > 1000) {
|
|
$times = array_slice($times, -1000);
|
|
}
|
|
|
|
$cacheItem = CacheItem::fromKey(
|
|
$cacheKey,
|
|
$times,
|
|
Duration::fromSeconds(self::METRICS_TTL)
|
|
);
|
|
|
|
$this->cache->set($cacheItem);
|
|
}
|
|
|
|
/**
|
|
* Get average execution time
|
|
*/
|
|
private function getAverageExecutionTime(): float
|
|
{
|
|
$cacheKey = CacheKey::fromString(self::CACHE_PREFIX . 'execution_times');
|
|
$result = $this->cache->get($cacheKey);
|
|
$item = $result->getItem($cacheKey);
|
|
|
|
if (! $item->isHit || ! is_array($item->value)) {
|
|
return 0.0;
|
|
}
|
|
|
|
$times = $item->value;
|
|
if (empty($times)) {
|
|
return 0.0;
|
|
}
|
|
|
|
return array_sum($times) / count($times);
|
|
}
|
|
}
|