Лог ошибок PHP: от базовой настройки до продвинутых решений
Логирование ошибок PHP: настройка и использование
Наиболее эффективный способ организовать запись ошибок PHP в лог это настройка директив log_errors и error_log в файле php.ini. Это позволяет централизованно управлять логированием для всех скриптов на сервере.
Как настроить запись ошибок в файл?
; Включить запись ошибок
log_errors = On
; Указать путь к файлу лога
error_log = /var/log/php_errors.log
; Уровень ошибок (E_ALL & ~E_NOTICE & ~E_DEPRECATED)
error_reporting = E_ALLПосле изменения php.ini необходимо перезапустить веб-сервер (Apache, Nginx, PHP-FPM). Лог-файл должен быть доступен для записи пользователем веб-сервера (обычно www-data).
Типичные проблемы:
- Файл лога не создается или пуст. Следует проверить права доступа к директории и наличие директивы log_errors.
- Ошибки не записываются, если PHP работает в режиме CGI или FastCGI, может потребоваться отдельная настройка.
- При использовании пула PHP-FPM, логи могут перенаправляться в stderr пула.
Как включить логирование ошибок в конкретном скрипте без доступа к php.ini?
Использование функции ini_set() позволяет изменить директивы во время выполнения скрипта.
<?php
// Включить логирование ошибок только для текущего скрипта
ini_set('log_errors', 1);
ini_set('error_log', '/tmp/my_script_errors.log');
// Установить уровень ошибок
error_reporting(E_ALL & ~E_NOTICE);
// Пример ошибки
echo $undefined_var;
?>Это удобно для отладки отдельных страниц, но не заменяет глобальную настройку.
Возможные проблемы:
- ini_set() может быть отключена в некоторых окружениях (например, в disable_functions).
- Директива log_errors может быть уже отключена на уровне php.ini, и ini_set() не сможет её включить, если allow_url_include отключено (на самом деле не связано, но бывает путаница).
Как записывать ошибки с дополнительной информацией (время, стек вызовов)?
Создание собственного обработчика ошибок даёт полный контроль над форматом записи.
<?php
function customErrorHandler($errno, $errstr, $errfile, $errline) {
$logMessage = sprintf(
'[%s] Error level: %d, Message: %s, File: %s, Line: %d' . "\n",
date('Y-m-d H:i:s'),
$errno,
$errstr,
$errfile,
$errline
);
error_log($logMessage, 3, '/var/log/custom_php_errors.log');
return true; // предотвращаем стандартную обработку
}
set_error_handler('customErrorHandler');
// Пример ошибки
trigger_error('Тестовая ошибка', E_USER_WARNING);
?>Функция error_log() с типом 3 записывает сообщение в указанный файл.
Подводные камни:
- Не все типы ошибок могут быть перехвачены set_error_handler (например, E_ERROR, E_PARSE). Для них нужен register_shutdown_function или обработчик исключений.
- Важно возвращать true, чтобы избежать двойной записи.
Как отправлять ошибки в системный лог (syslog)?
Директива error_log = syslog направляет все ошибки в системный журнал (rsyslog, syslog-ng).
; В php.ini
error_log = syslogИли в коде:
<?php
ini_set('error_log', 'syslog');
openlog('MyApp', LOG_PID | LOG_PERROR, LOG_LOCAL0);
syslog(LOG_WARNING, 'Предупреждение: что-то пошло не так');
closelog();
?>Полезно для интеграции с централизованными системами мониторинга.
Проблемы:
- Сложность чтения логов неспециалистами.
- Размер логов может быстро расти, требуется настройка ротации.
Как хранить ошибки в базе данных для последующего анализа?
С помощью того же пользовательского обработчика можно вставлять записи в таблицу БД.
<?php
function dbErrorHandler($errno, $errstr, $errfile, $errline) {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'pass');
$stmt = $pdo->prepare('INSERT INTO error_log (timestamp, errno, errstr, errfile, errline) VALUES (NOW(), ?, ?, ?, ?)');
$stmt->execute([$errno, $errstr, $errfile, $errline]);
}
set_error_handler('dbErrorHandler');
?>Этот подход удобен для веб-приложений с админ-панелью.
Риски:
- Ошибки подключения к БД могут вызывать рекурсивные ошибки.
- Снижение производительности при большом количестве ошибок.
Как настроить продвинутое логирование с помощью библиотеки Monolog?
Monolog предоставляет гибкие обработчики (файлы, syslog, email, Slack и т.д.).
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\BrowserConsoleHandler;
$log = new Logger('my_app');
$log->pushHandler(new StreamHandler('/var/log/app.log', Logger::WARNING));
$log->pushHandler(new BrowserConsoleHandler(Logger::DEBUG));
// Пример
$log->error('Ошибка базы данных', ['code' => 500]);
?>Дополнительно можно настроить формат сообщений через Formatter.
Сложности:
- Требуется установка через Composer.
- Избыточно для простых проектов.
Расширенные примеры логирования ошибок PHP
Пример 1: Обработка фатальных ошибок через register_shutdown_function
<?php
function shutdownHandler() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$log = sprintf(
'[%s] FATAL: %s in %s on line %d' . "\n",
date('Y-m-d H:i:s'),
$error['message'],
$error['file'],
$error['line']
);
error_log($log, 3, '/tmp/fatal_errors.log');
}
}
register_shutdown_function('shutdownHandler');
// Вызов фатальной ошибки
// undefined_function();
?>Результат: в файл /tmp/fatal_errors.log будет записана информация о фатальной ошибке.
Пример 2: Логирование исключений
<?php
function exceptionHandler($exception) {
$log = sprintf(
'[%s] Exception: %s in %s on line %d' . "\n" . 'Trace:' . "\n" . '%s' . "\n",
date('Y-m-d H:i:s'),
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
);
error_log($log, 3, '/tmp/exceptions.log');
}
set_exception_handler('exceptionHandler');
throw new Exception('Тестовое исключение');
?>Результат: в файл /tmp/exceptions.log появится запись с трассировкой стека.
Пример 3: Запись логов в формате JSON с Monolog
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Formatter\JsonFormatter;
$log = new Logger('api');
$handler = new StreamHandler('/var/log/api.log', Logger::DEBUG);
$handler->setFormatter(new JsonFormatter());
$log->pushHandler($handler);
$log->info('Пользователь авторизован', ['user_id' => 42, 'ip' => '192.168.1.1']);
$log->error('Ошибка валидации', ['field' => 'email', 'value' => 'invalid']);
?>Результат: в файле api.log строки вида {\"message\":\"Пользователь авторизован\",\"context\":{\"user_id\":42,\"ip\":\"192.168.1.1\"},\"level\":200,\"level_name\":\"INFO\",\"channel\":\"api\",\"datetime\":\"2025-03-28T10:00:00+00:00\",\"extra\":[]}Пример 4: Ротация логов с помощью logrotate
/var/log/php_errors.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 www-data adm
postrotate
/usr/bin/killall -USR1 rsyslogd 2>/dev/null || true
endscript
}Результат: логи будут архивироваться каждый день, храниться 7 дней.
Пример 5: Использование error_log с типом 1 (отправка по email)
<?php
error_log('Критическая ошибка: потеря соединения с БД', 1, 'admin@example.com');
?>Результат: письмо с сообщением будет отправлено на указанный адрес.
Пример 6: Комбинирование нескольких обработчиков в Monolog (файл + Slack)
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SlackWebhookHandler;
$log = new Logger('my_app');
$log->pushHandler(new StreamHandler('/var/log/app.log', Logger::WARNING));
$log->pushHandler(new SlackWebhookHandler(
'https://hooks.slack.com/services/...',
'#errors',
'PHP Error Bot',
true,
null,
Logger::CRITICAL
));
?>Результат: WARNING и выше в файл, CRITICAL и выше в Slack.