Files
XC_VM/docs/ru/guides/error-handling.md
T
Divarion-D 76844fef11 docs: restructure, fix PSR-4 drift, and unify en/ru
Overhaul the Docsify documentation (English + Russian) so it matches the current
codebase and follows one consistent pattern.

Content accuracy (post-migration):
- Rewrite development/autoloader.md to PSR-4 / Composer (the old XC_Autoloader
  scanner, igbinary tmp/cache/autoload_map and registerDirectories are gone).
- PascalCase every source path (src/core -> src/Core, domain/Stream, cli/Commands,
  public/Controllers, Infrastructure/Redis, ...) across all docs.
- Replace the removed autoload.php references with vendor/autoload.php
  (build_system, bootstrap-contexts, error-handling, modules).
- ssl-generation: note that the installer now auto-generates a unique self-signed
  certificate before Nginx starts.

Common pattern (Clean & uniform):
- Strip emoji from headings; remove the in-page Navigation blocks (the Docsify
  sidebar already provides navigation).
- One H1 + intro per doc; uniform "Related files" / "Связанные файлы" section,
  added to the code-centric docs that lacked it.

Structure:
- Remove the empty stray docs/api/; move updates_checklist.md into builds/;
  link the previously-orphaned ucs-integration.md.
- Regroup the sidebars (split the oversized guides group into Developer Guides /
  Security & Access / Integrations; fold builds into Build & Release).

Augment:
- dev-workflow: Local Setup (make dev-tools) + Quality Checks (phpstan, cs, gates).
- build_system: Composer Dependencies section (committed prod-only vendor,
  committed lock, dev tools via composer install, no build-time vendor step).

en/ru parity:
- Apply the same structure, fixes and pattern to docs/ru/ (translated), including
  a new Russian ucs-integration.md. The en and ru file sets are now identical.
2026-06-26 15:56:15 +03:00

22 KiB
Raw Blame History

Модель обработки ошибок

Обработка ошибок в XC_VM имеет три уровня:

  • Коды ошибок — что пошло не так (централизованный реестр именованных строк ошибок)
  • Обработчики ошибок — как формируется HTTP-ответ клиенту (generateError(), generate404())
  • Подсистема Logger — runtime-перехват ошибок PHP, необработанных исключений и фатальных сбоев

Обзор потока

Код приложения
  |
  +-- generateError('CODE')        // намеренный ответ с ошибкой
  |     -> debug-режим: стилизованная HTML-страница с кодом + описанием
  |     -> production:  generate404() или явный HTTP-код
  |
  +-- PHP warning / notice / error  // runtime-ошибки
  |     -> Logger::handleError()
  |        -> сопоставляет errno с уровнем (ERROR, WARNING, NOTICE, INFO)
  |        -> пишет base64-encoded JSON в error_log.log
  |        -> опционально отображает на экране
  |
  +-- Необработанное исключение     // unhandled Throwable
  |     -> Logger::handleException()
  |        -> логирует как EXCEPTION с полной цепочкой trace
  |
  +-- Фатальная ошибка при shutdown // E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR
        -> Logger::handleFatal()
           -> логирует как FATAL (stack trace недоступен)

Реестр кодов ошибок

Все коды объявляются в src/Core/Error/ErrorCodes.php как глобальный массив $rErrorCodes.

Формат кода:

  • Ключ: строка в верхнем регистре (например: INVALID_CREDENTIALS)
  • Значение: человекочитаемое описание на английском

Используйте только централизованные определения кодов. Не хардкодьте тексты ошибок в обработчиках endpoint'ов.

Полный список кодов

Код Описание
API_IP_NOT_ALLOWED IP не разрешён для доступа к API.
ARCHIVE_DOESNT_EXIST Архивные файлы отсутствуют для этого stream ID.
ASN_BLOCKED ASN заблокирован.
BANNED Линия забанена.
BLOCKED_USER_AGENT User-agent заблокирован.
CACHE_INCOMPLETE Кэш генерируется...
DEVICE_NOT_ALLOWED Устройствам MAG и Enigma запрещён доступ.
DISABLED Линия отключена.
DOWNLOAD_LIMIT_REACHED Достигнут лимит одновременных загрузок.
E2_DEVICE_LOCK_FAILED Проверки device lock не пройдены.
E2_DISABLED Устройство отключено.
E2_NO_TOKEN Токен не указан.
E2_TOKEN_DOESNT_MATCH Токен не совпадает с записями.
E2_WATCHDOG_TIMEOUT Достигнут лимит времени.
EMPTY_USER_AGENT Пустые user-agent запрещены.
EPG_DISABLED EPG отключён.
EPG_FILE_MISSING Кэшированные файлы EPG отсутствуют.
EXPIRED Срок линии истёк.
FORCED_COUNTRY_INVALID Страна не совпадает с forced country.
GENERATE_PLAYLIST_FAILED Не удалось сгенерировать плейлист.
HLS_DISABLED HLS отключён.
HOSTING_DETECT Обнаружен hosting-сервер.
INVALID_API_PASSWORD Неверный пароль API.
INVALID_CREDENTIALS Неверное имя пользователя или пароль.
INVALID_HOST Имя домена не распознано.
INVALID_STREAM_ID Stream ID не существует.
INVALID_TYPE_TOKEN Токены нельзя использовать для этого типа потока.
IP_BLOCKED IP заблокирован.
IP_MISMATCH Текущий IP не совпадает с IP начального подключения.
ISP_BLOCKED ISP заблокирован.
LB_TOKEN_INVALID AES-токен не удаётся расшифровать.
LEGACY_EPG_DISABLED Доступ к legacy epg.php отключён.
LEGACY_GET_DISABLED Доступ к legacy get.php отключён.
LEGACY_PANEL_API_DISABLED Доступ к legacy panel_api.php отключён.
LINE_CREATE_FAIL Не удалось вставить линию в базу данных.
NO_CREDENTIALS Учётные данные не указаны.
NO_SERVERS_AVAILABLE Сейчас нет доступных серверов для этого потока.
NO_TIMESTAMP Не указана временная метка архива.
NO_TOKEN_SPECIFIED Не указан AES-зашифрованный токен.
NOT_ENIGMA_DEVICE Линия не является устройством enigma.
NOT_IN_ALLOWED_COUNTRY Не в списке разрешённых стран.
NOT_IN_ALLOWED_IPS Не в списке разрешённых IP.
NOT_IN_ALLOWED_UAS Не в списке разрешённых user-agent.
NOT_IN_BOUQUET У линии нет доступа к этому stream ID.
PLAYER_API_DISABLED Player API отключён.
PROXY_ACCESS_DENIED Невозможно обратиться к потоку напрямую, пока включён proxy.
PROXY_DETECT Обнаружен proxy.
PROXY_NO_API_ACCESS Нельзя обращаться к API через proxy.
RESTREAM_DETECT Обнаружен рестрим.
STALKER_CHANNEL_MISMATCH Stream ID не совпадает со stalker-токеном.
STALKER_DECRYPT_FAILED Не удалось расшифровать stalker-токен.
STALKER_INVALID_KEY Неверный stalker-ключ.
STALKER_IP_MISMATCH IP не совпадает со stalker-токеном.
STALKER_KEY_EXPIRED Срок stalker-токена истёк.
STREAM_OFFLINE Поток сейчас offline.
SUBTITLE_DOESNT_EXIST Файл субтитров не существует.
THUMBNAIL_DOESNT_EXIST Файл превью не существует.
THUMBNAILS_NOT_ENABLED Превью не включены для этого потока.
TOKEN_ERROR AES-токен содержит неполные данные.
TOKEN_EXPIRED Срок AES-токена истёк.
TS_DISABLED MPEG-TS отключён.
USER_ALREADY_CONNECTED Линия уже подключена с другого IP.
USER_DISALLOW_EXT Расширение не в списке разрешённых.
VOD_DOESNT_EXIST VOD-файл не существует.
WAIT_TIME_EXPIRED Истёк таймаут запуска потока, не удалось запустить.

Специфичные для стриминга коды (CACHE_INCOMPLETE, SUBTITLE_DOESNT_EXIST, NO_SERVERS_AVAILABLE, PROXY_ACCESS_DENIED) были перенесены из stream/init.php в централизованный реестр.


Обработчики ошибок

Определены в src/Core/Error/ErrorHandler.php. Это обычные функции (а не методы класса), подключаемые рано в bootstrap.

generateError(string $rError, bool $rKill = true, ?int $rCode = null)

Формирует HTTP-ответ с ошибкой. Поведение зависит от настройки debug_show_errors:

если debug_show_errors === true
    рендерит стилизованную HTML-страницу с ключом ошибки + описанием
    если $rKill -> exit()
иначе (production)
    если $rKill
        если задан $rCode -> http_response_code($rCode) + exit()
        иначе             -> generate404()
    // если !$rKill, в production-режиме ничего не делает

Параметры:

Параметр Тип По умолчанию Смысл
$rError string -- Ключ из $rErrorCodes
$rKill bool true Завершить скрипт после вывода
$rCode int|null null Явный HTTP-код ответа (обходит 404 в production)

Примеры:

generateError('INVALID_CREDENTIALS');              // production: 404 + exit
generateError('API_IP_NOT_ALLOWED', true, 403);    // production: 403 + exit
generateError('STREAM_OFFLINE', false);             // production: без вывода, без exit

generate404(bool $rKill = true)

Возвращает страницу 404 Not Found в стиле nginx и устанавливает HTTP 404. HTML включает padding-комментарии для подавления «дружелюбных» страниц ошибок в MSIE и Chrome.

generate404();       // 404 + exit
generate404(false);  // 404, выполнение продолжается

Подсистема Logger

Определена в src/Core/Logging/Logger.php. Класс final, регистрирующий три глобальных PHP-обработчика для перехвата всех runtime-ошибок и записи их в файл.

Инициализация

Logger::init(bool $showErrors, string $logFile): void

Регистрирует:

  1. set_error_handler([Logger::class, 'handleError']) — PHP warning, notice, error
  2. set_exception_handler([Logger::class, 'handleException']) — необработанные Throwable
  3. register_shutdown_function([Logger::class, 'handleFatal']) — фатальные ошибки при shutdown

Также настраивает error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED) и устанавливает display_errors / display_startup_errors на основе $showErrors.

Где вызывается Logger::init()

Logger инициализируется в двух местах в зависимости от пути запроса:

Путь входа Файл Как
Bootstrap (все контексты) src/bootstrap.php XC_Bootstrap::loadConstants() вызывает Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')
Стриминговые endpoint'ы src/Core/Http/RequestGuard.php Загружает настройки из файлового кэша, определяет PHP_ERRORS, затем вызывает Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')

В обоих случаях PHP_ERRORS отражает настройку debug_show_errors (по умолчанию false, когда настройки недоступны).

Сопоставление уровней ошибок

Logger::handleError() сопоставляет константы PHP-ошибок строкам уровней через mapErrorLevel():

Константа(ы) PHP Уровень лога
E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR ERROR
E_WARNING, E_USER_WARNING WARNING
E_NOTICE, E_USER_NOTICE NOTICE
Все другие значения errno INFO

Обработчик shutdown (handleFatal()) проверяет error_get_last() на эти фатальные типы и логирует их как FATAL:

Константа(ы) PHP при shutdown Уровень лога
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR FATAL

Необработанные исключения, логируемые handleException(), всегда используют уровень EXCEPTION.

Ошибки, подавленные оператором @, игнорируются (обработчик проверяет error_reporting() & $errno).

Формат лога

Каждая запись лога пишется одной строкой: base64_encode(json_encode($data)) с переводом строки. Это предотвращает повреждение строк из многострочных сообщений.

Декодированная JSON-структура:

{
    "type":        "WARNING",
    "log_message": "Undefined variable $foo",
    "file":        "/home/xc_vm/Domain/Stream/StreamService.php",
    "line":        142,
    "log_extra":   "#0 /home/xc_vm/...(line): function()\n#1 ...",
    "time":        1716220800,
    "env":         "fpm-fcgi"
}
Поле Содержимое
type Уровень лога: ERROR, WARNING, NOTICE, INFO, EXCEPTION или FATAL
log_message Текст сообщения об ошибке/исключении
file Абсолютный путь к исходному файлу
line Номер строки, где произошла ошибка
log_extra Stack trace (форматированная строка). Пустой для фатальных ошибок.
time Unix timestamp
env Имя PHP SAPI (cli, fpm-fcgi и т.д.)

Расположение файла лога

Путь по умолчанию: LOGS_TMP_PATH . 'error_log.log'

Если директория лога не существует, Logger создаёт её с правами 0775. При запуске под root (часто в контейнерах) файл chown'ится в xc_vm:xc_vm с режимом 0664.

Вывод на экран

Когда $showErrors = true, Logger также рендерит ошибки напрямую:

  • CLI: цветной вывод в терминал (красный для FATAL/ERROR, жёлтый для WARNING, синий для NOTICE)
  • Web: inline <div> с моноширинным шрифтом, красной рамкой и stack trace в блоке <pre>

Конвейер логирования: файл → база данных

Logger пишет в error_log.log на диск. Отдельная подсистема читает этот файл и сохраняет записи в таблицу panel_logs:

  1. Logger пишет base64-encoded JSON-строки в error_log.log
  2. FileLogger (src/Core/Logging/FileLogger.php) предоставляет вторичный логирующий интерфейс для кода приложения (ошибки PDO, ошибки EPG и т.д.), который пишет в тот же файл в том же формате
  3. Записи импортируются в таблицу panel_logs
  4. DiagnosticsService (src/Core/Diagnostics/DiagnosticsService.php) читает из panel_logs для:
    • downloadPanelLogs() — получает до 1000 свежих не-EPG ошибок, затем truncate'ит таблицу
    • submitPanelLogs() — отправляет логи на центральный API-сервер для анализа
  5. Админ-панель показывает эти логи в Management > Logs > Panel Errors

Шумовая фильтрация FileLogger

FileLogger::log() пропускает записи, соответствующие:

  • Сообщения, содержащие panel_logs в дополнительном поле (предотвращает рекурсивное логирование)
  • Сообщения, совпадающие с timeout exceeded, lock wait timeout или duplicate entry (шумные MySQL-ошибки)

Другие логгеры

Директория src/Core/Logging/ содержит дополнительные специализированные логгеры:

Класс Файл Назначение
Logger Logger.php Глобальный обработчик ошибок/исключений/fatal PHP (описан выше)
FileLogger FileLogger.php Логирование уровня приложения (ошибки PDO, EPG и т.д.) в error_log.log
DatabaseLogger DatabaseLogger.php События клиентских стриминговых запросов в client_request.log (импортируются в таблицу client_logs)
UpdateLogger UpdateLogger.php Операции обновления системы в MAIN_HOME/update.log (простой текст, не base64)

Все логгеры, кроме UpdateLogger, реализуют LoggerInterface и пишут base64-encoded JSON.


Типы исключений в кодовой базе

Кодовая база определяет небольшое число пользовательских классов исключений. Все необработанные исключения перехватываются Logger::handleException(), который логирует полную цепочку исключений (включая getPrevious()).

Класс исключения Базовый класс Расположение
DropboxException Exception src/Core/Storage/DropboxClient.php
M3uParser\Exception \Exception src/Core/Parsing/M3uParser/src/Exception.php
DataBuildingException \RuntimeException src/Core/Parsing/PhpM3u8/src/Parser/DataBuildingException.php
DefinitionException \RuntimeException src/Core/Parsing/PhpM3u8/src/Definition/DefinitionException.php
DumpingException \RuntimeException src/Core/Parsing/PhpM3u8/src/Dumper/DumpingException.php

Большая часть кода приложения использует выбросы обобщённого Exception или полагается на встроенную систему ошибок PHP. Exception-обработчик Logger принимает любой Throwable.


Debug vs Production

Production (по умолчанию: debug_show_errors = false)

  • generateError() возвращает универсальную страницу 404 (или явный HTTP-код), скрывая внутреннюю причину сбоя
  • Logger по-прежнему пишет все ошибки в error_log.log на диск
  • display_errors и display_startup_errors устанавливаются в '0'
  • Ошибки видны только через админ-панель (страница Panel Errors) или файлы логов

Debug (debug_show_errors = true)

  • generateError() показывает стилизованную страницу с ключом ошибки и сопоставленным описанием
  • Logger дополнительно рендерит ошибки на экран (цветной CLI-вывод или inline HTML)
  • display_errors и display_startup_errors устанавливаются в '1'

Не включайте debug-отображение на production-нодах.


Регистрация обработчика ошибок в bootstrap

Инфраструктура обработки ошибок загружается рано в последовательности загрузки:

  1. bootstrap.php определяет MAIN_HOME и регистрирует Composer-автозагрузчик
  2. XC_Bootstrap::loadConstants() загружает (в порядке):
    • Core/Error/ErrorCodes.php — заполняет $rErrorCodes
    • Core/Error/ErrorHandler.php — определяет generateError() и generate404()
    • Файлы путей и конфигурации
    • Core/Logging/Logger.php — определение класса
  3. Вызывается Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log'), регистрирующий три глобальных обработчика
  4. С этого момента все PHP-ошибки, необработанные исключения и фатальные сбои перехватываются

Для стриминговых endpoint'ов, обходящих полный bootstrap, RequestGuard.php выполняет шаги 2-3 независимо: загружает настройки из файлового кэша, определяет PHP_ERRORS и вызывает Logger::init().


Добавление нового кода ошибки

  1. Добавьте новый ключ в src/Core/Error/ErrorCodes.php:
'MY_NEW_ERROR' => 'Human-readable description.',
  1. Используйте его в коде:
generateError('MY_NEW_ERROR');

Описания должны оставаться на английском для согласованности с существующим реестром.


Связанные файлы

Файл Назначение
src/Core/Error/ErrorCodes.php Централизованная карта кодов ошибок ($rErrorCodes)
src/Core/Error/ErrorHandler.php Функции generateError() и generate404()
src/Core/Logging/Logger.php Глобальные обработчики PHP-ошибок, исключений и фатальных
src/Core/Logging/LoggerInterface.php Интерфейс контракта логирования
src/Core/Logging/FileLogger.php Файловое логирование уровня приложения (PDO, EPG и т.д.)
src/Core/Logging/DatabaseLogger.php Логирование событий клиентских стриминговых запросов
src/Core/Logging/UpdateLogger.php Логирование операций обновления системы
src/Core/Http/RequestGuard.php Стриминговый путь: защита от флуда, проверка хоста, инициализация Logger
src/Core/Diagnostics/DiagnosticsService.php Читает таблицу panel_logs для отображения в админке и отправки в API
src/bootstrap.php Подключает слой ошибок и Logger во всех контекстах bootstrap