Files
michaelschiemer/src/Framework/ExceptionHandling/Metrics/ExceptionMetricsCollector.php
Michael Schiemer c93d3f07a2
All checks were successful
Test Runner / test-php (push) Successful in 31s
Deploy Application / deploy (push) Successful in 1m42s
Test Runner / test-basic (push) Successful in 7s
fix(Console): add void as valid return type for command methods
The MethodSignatureAnalyzer was rejecting command methods with void return
type, causing the schedule:run command to fail validation.
2025-11-26 06:16:09 +01:00

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