- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
305 lines
9.1 KiB
PHP
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(),
|
|
];
|
|
}
|
|
}
|