Файл: system/logger.php
Строк: 135
<?php
/* Обновление игры под PHP 8.4 / PDO и усиление безопасности: by Метриум (Стэлп) */
declare(strict_types=1);
/**
* Central production error logger for PHP 8.4.
* Error details are written outside the public UI to /logs.
*/
function app_log_dir(): string {
return dirname(__DIR__) . '/logs';
}
function app_log_prune(string $dir): void {
foreach (['app-error-*.log','php-error-*.log'] as $pattern) {
$files=(array)@glob($dir.'/'.$pattern);
usort($files, static fn($a,$b)=>(@filemtime($b)?:0)<=> (@filemtime($a)?:0));
foreach(array_slice($files,2) as $old) @unlink($old);
}
}
function app_log_storage_available(string $dir): bool {
if(!is_dir($dir)) return false;
$free=@disk_free_space($dir);
if($free===false) return true;
if($free >= 2*1024*1024) return true;
app_log_prune($dir);
$free=@disk_free_space($dir);
return $free===false || $free >= 512*1024;
}
function app_log_prepare(): void {
$dir = app_log_dir();
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
if (is_dir($dir)) {
app_log_prune($dir);
$phpLog = $dir . '/php-error.log';
if (is_file($phpLog) && @filesize($phpLog) > 2 * 1024 * 1024) {
@rename($phpLog, $dir . '/php-error-' . date('Ymd-His') . '.log');
app_log_prune($dir);
}
@ini_set('display_errors', '0');
@ini_set('display_startup_errors', '0');
if(app_log_storage_available($dir)) {
@ini_set('log_errors', '1');
@ini_set('error_log', $phpLog);
} else {
// При полностью заполненной квоте не заставляем PHP без конца пытаться писать журнал.
@ini_set('log_errors', '0');
}
}
error_reporting(E_ALL);
}
function app_log_context(): string {
$method = $_SERVER['REQUEST_METHOD'] ?? 'CLI';
$uri = $_SERVER['REQUEST_URI'] ?? '';
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$uid = 0;
if (function_exists('security_auth_user_id')) {
try { $uid = (int)security_auth_user_id(); } catch (Throwable) { $uid = 0; }
}
return 'method=' . $method . ' uri=' . $uri . ' ip=' . $ip . ' user=' . $uid;
}
function app_log_write(string $level, string $message, array $extra = []): void {
$dir = app_log_dir();
if (!is_dir($dir)) @mkdir($dir, 0755, true);
$file = $dir . '/app-error.log';
// Small rotation to prevent an old game from filling hosting storage.
if (is_file($file) && @filesize($file) > 2 * 1024 * 1024) {
@rename($file, $dir . '/app-error-' . date('Ymd-His') . '.log');
app_log_prune($dir);
}
if(!app_log_storage_available($dir)) return;
$safeExtra = [];
foreach ($extra as $k => $v) {
if (is_scalar($v) || $v === null) $safeExtra[$k] = $v;
}
$line = '[' . date('Y-m-d H:i:s') . '] [' . $level . '] ' . $message
. ' | ' . app_log_context();
if ($safeExtra) $line .= ' | ' . json_encode($safeExtra, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$line .= PHP_EOL;
@file_put_contents($file, $line, FILE_APPEND | LOCK_EX);
}
function app_error_id(): string {
try { return strtoupper(bin2hex(random_bytes(4))); }
catch (Throwable) { return strtoupper(substr(md5((string)microtime(true)), 0, 8)); }
}
function app_render_public_error(string $id): void {
if (PHP_SAPI === 'cli') return;
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: text/html; charset=UTF-8');
}
echo '<div style="max-width:520px;margin:30px auto;padding:16px;background:#211;border:1px solid #633;color:#eee;font:14px Arial,sans-serif;text-align:center">'
. '<b>Внутренняя ошибка игры</b><br><br>'
. 'Код ошибки: <b>' . htmlspecialchars($id, ENT_QUOTES, 'UTF-8') . '</b><br>'
. '<span style="color:#aaa">Подробности записаны в папку logs.</span>'
. '</div>';
}
function app_register_error_handlers(): void {
app_log_prepare();
set_error_handler(static function (int $severity, string $message, string $file, int $line): bool {
if (!(error_reporting() & $severity)) return false;
$names = [
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_RECOVERABLE_ERROR=>'E_RECOVERABLE_ERROR',
E_DEPRECATED=>'E_DEPRECATED', E_USER_DEPRECATED=>'E_USER_DEPRECATED'
];
app_log_write($names[$severity] ?? ('E_' . $severity), $message, ['file'=>$file, 'line'=>$line]);
return true; // production: log it, do not print it into game HTML
});
set_exception_handler(static function (Throwable $e): void {
$id = app_error_id();
app_log_write('UNCAUGHT', $e::class . ': ' . $e->getMessage(), [
'id'=>$id,
'file'=>$e->getFile(),
'line'=>$e->getLine(),
'trace'=>substr($e->getTraceAsString(), 0, 6000),
]);
app_render_public_error($id);
});
register_shutdown_function(static function (): void {
$e = error_get_last();
if (!$e) return;
if (!in_array($e['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR], true)) return;
$id = app_error_id();
app_log_write('FATAL', (string)$e['message'], [
'id'=>$id,
'file'=>$e['file'] ?? '',
'line'=>$e['line'] ?? 0,
]);
app_render_public_error($id);
});
}