Files
Atomcms-edit/app/Exceptions/Handler.php
T

170 lines
4.1 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace App\Exceptions;
use App\Services\AlertService;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Throwable;
class Handler extends ExceptionHandler
{
#[\Override]
protected $levels = [
//
];
#[\Override]
protected $dontReport = [
//
];
#[\Override]
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
private const string CACHE_KEY_ERROR_COUNT = 'error_count_';
private const int ERROR_COUNT_DURATION = 300;
#[\Override]
public function register(): void
{
$this->reportable(function (Throwable $e) {
$this->handleExceptionAlert($e);
});
$this->renderable(function (Throwable $e) {
$this->handleExceptionAlert($e);
});
}
private function handleExceptionAlert(Throwable $e): void
{
if (! $this->shouldAlertException($e)) {
return;
}
try {
$alertService = app(AlertService::class);
$errorMessage = $e->getMessage() ?: $e::class;
$alertService->sendCriticalError($errorMessage, $e);
$this->trackErrorRate();
Log::channel('emergency')->error('Critical exception reported via alert', [
'exception' => $e::class,
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
]);
} catch (\Exception $alertException) {
Log::error('Failed to send exception alert: ' . $alertException->getMessage());
}
}
private function shouldAlertException(Throwable $e): bool
{
if (! app()->isBooted()) {
return false;
}
if (! (bool) setting('alert_errors_enabled', true)) {
return false;
}
$criticalExceptions = [
QueryException::class,
RconConnectionException::class,
];
foreach ($criticalExceptions as $criticalException) {
if ($e instanceof $criticalException) {
return true;
}
}
if ($this->isHighErrorRate()) {
return true;
}
$minSeverity = setting('alert_min_severity', 'error');
return $this->isSeverityHighEnough($e, $minSeverity);
}
private function trackErrorRate(): void
{
if (! app()->isBooted()) {
return;
}
$key = self::CACHE_KEY_ERROR_COUNT . now()->format('YmdHi');
$count = Cache::get($key, 0);
Cache::put($key, $count + 1, self::ERROR_COUNT_DURATION);
}
private function isHighErrorRate(): bool
{
if (! app()->isBooted()) {
return false;
}
$threshold = (int) setting('alert_error_threshold', 10);
$key = self::CACHE_KEY_ERROR_COUNT . now()->format('YmdHi');
$count = Cache::get($key, 0);
return $count >= $threshold;
}
private function isSeverityHighEnough(Throwable $e, string $minSeverity): bool
{
$severityLevel = [
'info' => 0,
'warning' => 1,
'error' => 2,
'critical' => 3,
];
$exceptionSeverity = $this->determineExceptionSeverity($e);
$minLevel = $severityLevel[$minSeverity] ?? 2;
return $exceptionSeverity >= $minLevel;
}
private function determineExceptionSeverity(Throwable $e): int
{
if ($e instanceof \Error) {
return 3;
}
if ($e instanceof ValidationException) {
return 0;
}
if ($e instanceof AuthenticationException) {
return 1;
}
if ($e instanceof AuthorizationException) {
return 1;
}
return 2;
}
}