Files
michaelschiemer/src/Framework/ErrorReporting/ErrorStatistics.php
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

305 lines
9.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Framework\ErrorReporting;
use DateTimeImmutable;
/**
* Error statistics and analytics data
*/
final readonly class ErrorStatistics
{
public function __construct(
public int $totalErrors,
public int $uniqueErrors,
public array $errorsByLevel,
public array $errorsByException,
public array $errorsByRoute,
public array $errorsByUser,
public array $errorsByHour,
public array $errorsByDay,
public array $topErrors,
public array $trendingErrors,
public float $errorRate,
public array $responseTimeImpact,
public array $environmentBreakdown,
public DateTimeImmutable $periodStart,
public DateTimeImmutable $periodEnd
) {
}
/**
* Create empty statistics
*/
public static function empty(): self
{
return new self(
totalErrors: 0,
uniqueErrors: 0,
errorsByLevel: [],
errorsByException: [],
errorsByRoute: [],
errorsByUser: [],
errorsByHour: [],
errorsByDay: [],
topErrors: [],
trendingErrors: [],
errorRate: 0.0,
responseTimeImpact: [],
environmentBreakdown: [],
periodStart: new DateTimeImmutable(),
periodEnd: new DateTimeImmutable()
);
}
/**
* Get total critical errors (emergency, alert, critical levels)
*/
public function getCriticalErrorCount(): int
{
return ($this->errorsByLevel['emergency'] ?? 0) +
($this->errorsByLevel['alert'] ?? 0) +
($this->errorsByLevel['critical'] ?? 0);
}
/**
* Get error rate as percentage
*/
public function getErrorRatePercentage(): float
{
return round($this->errorRate * 100, 2);
}
/**
* Get most problematic route
*/
public function getMostProblematicRoute(): ?array
{
if (empty($this->errorsByRoute)) {
return null;
}
$route = array_key_first($this->errorsByRoute);
$count = reset($this->errorsByRoute);
return [
'route' => $route,
'count' => $count,
'percentage' => $this->totalErrors > 0 ? round(($count / $this->totalErrors) * 100, 2) : 0,
];
}
/**
* Get most frequent exception
*/
public function getMostFrequentException(): ?array
{
if (empty($this->errorsByException)) {
return null;
}
$exception = array_key_first($this->errorsByException);
$count = reset($this->errorsByException);
return [
'exception' => $exception,
'count' => $count,
'percentage' => $this->totalErrors > 0 ? round(($count / $this->totalErrors) * 100, 2) : 0,
];
}
/**
* Get peak error hour
*/
public function getPeakErrorHour(): ?array
{
if (empty($this->errorsByHour)) {
return null;
}
$hour = array_key_first($this->errorsByHour);
$count = reset($this->errorsByHour);
return [
'hour' => $hour,
'count' => $count,
'time' => sprintf('%02d:00', $hour),
];
}
/**
* Get error trend direction
*/
public function getErrorTrend(): string
{
if (count($this->errorsByDay) < 2) {
return 'stable';
}
$days = array_values($this->errorsByDay);
$recent = array_slice($days, -3); // Last 3 days
$previous = array_slice($days, -6, 3); // Previous 3 days
if (empty($recent) || empty($previous)) {
return 'stable';
}
$recentAvg = array_sum($recent) / count($recent);
$previousAvg = array_sum($previous) / count($previous);
$change = $previousAvg > 0 ? (($recentAvg - $previousAvg) / $previousAvg) * 100 : 0;
if ($change > 20) {
return 'increasing';
} elseif ($change < -20) {
return 'decreasing';
} else {
return 'stable';
}
}
/**
* Get error distribution health score (0-100)
*/
public function getHealthScore(): int
{
$score = 100;
// Penalize high error rates
if ($this->errorRate > 0.05) { // > 5%
$score -= 30;
} elseif ($this->errorRate > 0.02) { // > 2%
$score -= 15;
} elseif ($this->errorRate > 0.01) { // > 1%
$score -= 5;
}
// Penalize critical errors
$criticalCount = $this->getCriticalErrorCount();
if ($criticalCount > 0) {
$score -= min(40, $criticalCount * 5);
}
// Penalize trending up
if ($this->getErrorTrend() === 'increasing') {
$score -= 15;
}
// Penalize concentrated errors (single route causing many errors)
$topRoute = $this->getMostProblematicRoute();
if ($topRoute && $topRoute['percentage'] > 50) {
$score -= 10;
}
return max(0, $score);
}
/**
* Get insights and recommendations
*/
public function getInsights(): array
{
$insights = [];
// Critical error insight
$criticalCount = $this->getCriticalErrorCount();
if ($criticalCount > 0) {
$insights[] = [
'type' => 'critical',
'message' => "Found {$criticalCount} critical errors that require immediate attention.",
'priority' => 'high',
];
}
// Error rate insight
if ($this->errorRate > 0.05) {
$insights[] = [
'type' => 'error_rate',
'message' => sprintf('Error rate is %.2f%%, which is above the recommended threshold of 2%%.', $this->getErrorRatePercentage()),
'priority' => 'high',
];
}
// Route concentration insight
$topRoute = $this->getMostProblematicRoute();
if ($topRoute && $topRoute['percentage'] > 40) {
$insights[] = [
'type' => 'route_concentration',
'message' => sprintf('Route "%s" accounts for %.1f%% of all errors. Consider reviewing this endpoint.', $topRoute['route'], $topRoute['percentage']),
'priority' => 'medium',
];
}
// Exception concentration insight
$topException = $this->getMostFrequentException();
if ($topException && $topException['percentage'] > 50) {
$insights[] = [
'type' => 'exception_concentration',
'message' => sprintf('Exception "%s" accounts for %.1f%% of all errors. This indicates a systemic issue.', basename($topException['exception']), $topException['percentage']),
'priority' => 'medium',
];
}
// Trend insight
$trend = $this->getErrorTrend();
if ($trend === 'increasing') {
$insights[] = [
'type' => 'trend',
'message' => 'Error trend is increasing over the last few days. Monitor closely.',
'priority' => 'medium',
];
} elseif ($trend === 'decreasing') {
$insights[] = [
'type' => 'trend',
'message' => 'Error trend is decreasing. Good progress!',
'priority' => 'low',
];
}
// No errors insight
if ($this->totalErrors === 0) {
$insights[] = [
'type' => 'no_errors',
'message' => 'No errors recorded in this period. System is operating smoothly.',
'priority' => 'low',
];
}
return $insights;
}
/**
* Convert to array
*/
public function toArray(): array
{
return [
'total_errors' => $this->totalErrors,
'unique_errors' => $this->uniqueErrors,
'errors_by_level' => $this->errorsByLevel,
'errors_by_exception' => $this->errorsByException,
'errors_by_route' => $this->errorsByRoute,
'errors_by_user' => $this->errorsByUser,
'errors_by_hour' => $this->errorsByHour,
'errors_by_day' => $this->errorsByDay,
'top_errors' => $this->topErrors,
'trending_errors' => $this->trendingErrors,
'error_rate' => $this->errorRate,
'error_rate_percentage' => $this->getErrorRatePercentage(),
'response_time_impact' => $this->responseTimeImpact,
'environment_breakdown' => $this->environmentBreakdown,
'period_start' => $this->periodStart->format('c'),
'period_end' => $this->periodEnd->format('c'),
'critical_error_count' => $this->getCriticalErrorCount(),
'most_problematic_route' => $this->getMostProblematicRoute(),
'most_frequent_exception' => $this->getMostFrequentException(),
'peak_error_hour' => $this->getPeakErrorHour(),
'error_trend' => $this->getErrorTrend(),
'health_score' => $this->getHealthScore(),
'insights' => $this->getInsights(),
];
}
}