Clean-room copy of the reusable engines from comiida, with all instance data, secrets, dependencies, and build output excluded: - app/ Astro theme skeleton (no comiida blog posts; hero image -> placeholder) - api/ SeedProject PHP framework (no vendor/.env/config.php) - content-pipeline/ engine only (scripts/admin/prompts; empty runtime state) - astroagent.config.json + app/.astroagent/skills Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SYHWLHihq3v9nxNwoPCKSn
170 lines
7.7 KiB
PHP
170 lines
7.7 KiB
PHP
<?php
|
|
|
|
class ErrorHandler {
|
|
|
|
private static array $errorTypeMap = [
|
|
E_ERROR => 'E_ERROR',
|
|
E_WARNING => 'E_WARNING',
|
|
E_PARSE => 'E_PARSE',
|
|
E_NOTICE => 'E_NOTICE',
|
|
E_CORE_ERROR => 'E_CORE_ERROR',
|
|
E_CORE_WARNING => 'E_CORE_WARNING',
|
|
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
|
|
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
|
|
E_USER_ERROR => 'E_USER_ERROR',
|
|
E_USER_WARNING => 'E_USER_WARNING',
|
|
E_USER_NOTICE => 'E_USER_NOTICE',
|
|
E_STRICT => 'E_STRICT',
|
|
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
|
|
E_DEPRECATED => 'E_DEPRECATED',
|
|
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
|
|
];
|
|
|
|
private static array $errorTypeLabels = [
|
|
E_ERROR => 'Fatal Error',
|
|
E_WARNING => 'Warning',
|
|
E_PARSE => 'Parse Error',
|
|
E_NOTICE => 'Notice',
|
|
E_CORE_ERROR => 'Core Error',
|
|
E_CORE_WARNING => 'Core Warning',
|
|
E_COMPILE_ERROR => 'Compile Error',
|
|
E_COMPILE_WARNING => 'Compile Warning',
|
|
E_USER_ERROR => 'User Error',
|
|
E_USER_WARNING => 'User Warning',
|
|
E_USER_NOTICE => 'User Notice',
|
|
E_STRICT => 'Strict Standards',
|
|
E_RECOVERABLE_ERROR => 'Recoverable Error',
|
|
E_DEPRECATED => 'Deprecated',
|
|
E_USER_DEPRECATED => 'User Deprecated',
|
|
];
|
|
|
|
private static array $fatalTypes = [
|
|
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR,
|
|
E_USER_ERROR, E_RECOVERABLE_ERROR,
|
|
];
|
|
|
|
public static function handleError($errno, $errstr, $errfile, $errline) {
|
|
if (!(error_reporting() & $errno)) {
|
|
return;
|
|
}
|
|
|
|
// Only persist fatal/critical errors — skip warnings, notices, deprecated, strict
|
|
if (in_array($errno, self::$fatalTypes)) {
|
|
self::persistError($errno, $errstr, $errfile, $errline);
|
|
self::logError($errno, $errstr, $errfile, $errline);
|
|
}
|
|
|
|
self::displayError($errno, $errstr, $errfile, $errline);
|
|
|
|
return true;
|
|
}
|
|
|
|
public static function handleException($exception) {
|
|
$trace = $exception->getTraceAsString();
|
|
self::persistError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace, 'Exception');
|
|
self::logError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine());
|
|
self::displayError(E_ERROR, $exception->getMessage(), $exception->getFile(), $exception->getLine(), $trace);
|
|
}
|
|
|
|
public static function handleShutdown() {
|
|
$error = error_get_last();
|
|
if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE])) {
|
|
self::persistError($error['type'], $error['message'], $error['file'], $error['line']);
|
|
self::logError($error['type'], $error['message'], $error['file'], $error['line']);
|
|
self::displayError($error['type'], $error['message'], $error['file'], $error['line']);
|
|
}
|
|
}
|
|
|
|
// ─── JSON flat-file persistence ───────────────────────────────────────────
|
|
|
|
private static function persistError($errno, $errstr, $errfile, $errline, $trace = null, $forcedType = null) {
|
|
try {
|
|
$errorType = $forcedType ?? (self::$errorTypeMap[$errno] ?? 'UNKNOWN');
|
|
|
|
$userId = null;
|
|
$orgId = null;
|
|
if (isset($_SESSION['login'])) {
|
|
$userId = $_SESSION['login']['userid'] ?? null;
|
|
$orgId = $_SESSION['login']['org_id'] ?? null;
|
|
}
|
|
|
|
$url = ((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http')
|
|
. '://' . ($_SERVER['HTTP_HOST'] ?? '') . ($_SERVER['REQUEST_URI'] ?? '');
|
|
$method = $_SERVER['REQUEST_METHOD'] ?? null;
|
|
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
|
|
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? null;
|
|
$referer = $_SERVER['HTTP_REFERER'] ?? null;
|
|
|
|
$entry = [
|
|
'id' => uniqid('err_', true),
|
|
'userid' => $userId,
|
|
'org_id' => $orgId,
|
|
'errortype' => $errorType,
|
|
'errorcode' => (string)$errno,
|
|
'url' => substr($url, 0, 512),
|
|
'file' => substr($errfile, 0, 512),
|
|
'line' => (int)$errline,
|
|
'fullerror' => $errstr,
|
|
'trace' => $trace,
|
|
'method' => $method ? substr($method, 0, 10) : null,
|
|
'ip_address' => $ipAddress ? substr($ipAddress, 0, 45) : null,
|
|
'user_agent' => $userAgent ? substr($userAgent, 0, 512) : null,
|
|
'referer' => $referer ? substr($referer, 0, 512) : null,
|
|
'status' => 'unresolved',
|
|
'createdate' => date('Y-m-d H:i:s'),
|
|
];
|
|
|
|
$logPath = __DIR__ . '/errors.json';
|
|
$entries = [];
|
|
if (file_exists($logPath)) {
|
|
$raw = file_get_contents($logPath);
|
|
$decoded = json_decode($raw, true);
|
|
if (is_array($decoded)) {
|
|
$entries = $decoded;
|
|
}
|
|
}
|
|
|
|
array_unshift($entries, $entry);
|
|
if (count($entries) > 1000) {
|
|
$entries = array_slice($entries, 0, 1000);
|
|
}
|
|
|
|
file_put_contents($logPath, json_encode($entries, JSON_PRETTY_PRINT), LOCK_EX);
|
|
|
|
} catch (\Throwable $e) {
|
|
// Silently fail — never let error logging crash the app
|
|
error_log('ErrorHandler::persistError failed: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// ─── File logging ─────────────────────────────────────────────────────────
|
|
|
|
private static function logError($errno, $errstr, $errfile, $errline) {
|
|
$message = date('[Y-m-d H:i:s]') . " Error: [$errno] $errstr in $errfile on line $errline\n";
|
|
error_log($message, 3, __DIR__ . '/app_errors.log');
|
|
}
|
|
|
|
// ─── Display (debug mode only) ────────────────────────────────────────────
|
|
|
|
private static function displayError($errno, $errstr, $errfile, $errline, $trace = null) {
|
|
$errorType = self::$errorTypeLabels[$errno] ?? 'Unknown Error';
|
|
|
|
if (defined('DEBUG') && DEBUG) {
|
|
echo "<div style='background-color:#f8d7da;color:#721c24;padding:10px;margin:10px;border:1px solid #f5c6cb;border-radius:5px;'>";
|
|
echo "<h1 style='color:#721c24;'>" . htmlspecialchars($errorType) . " Occurred</h1>";
|
|
echo "<p><strong>Message:</strong> " . htmlspecialchars($errstr) . "</p>";
|
|
echo "<p><strong>File:</strong> " . htmlspecialchars($errfile) . "</p>";
|
|
echo "<p><strong>Line:</strong> " . htmlspecialchars((string)$errline) . "</p>";
|
|
if ($trace) {
|
|
echo "<h2>Stack Trace:</h2>";
|
|
echo "<pre>" . htmlspecialchars($trace) . "</pre>";
|
|
}
|
|
echo "<h2>Request Details:</h2><pre>";
|
|
echo "URL: " . htmlspecialchars($_SERVER['REQUEST_URI'] ?? '') . "\n";
|
|
echo "Method: " . htmlspecialchars($_SERVER['REQUEST_METHOD'] ?? '') . "\n";
|
|
echo "Time: " . date('Y-m-d H:i:s') . "\n";
|
|
echo "IP: " . htmlspecialchars($_SERVER['REMOTE_ADDR'] ?? '') . "\n";
|
|
echo "</pre></div>";
|
|
}
|
|
}
|
|
}
|