Логирование в PHP приложениях: лучшие практики на практике
Логирование — это не просто запись ошибок в файл. Это стратегический инструмент мониторинга, отладки и аудита работы приложения. В этой статье мы разберём, как организовать логирование в PHP на практике, используя современные подходы и библиотеки. Вы узнаете, как избежать типичных ошибок и сделать логи действительно полезными.
1. Выбор библиотеки и стандарт PSR-3
В современном PHP-сообществе стандартом де-факто является интерфейс PSR-3 (Logger Interface). Он определяет единый набор методов для логирования, что позволяет легко менять библиотеку логирования без переписывания кода приложения.
Наиболее популярная реализация PSR-3 — библиотека Monolog. Она гибкая, производительная и имеет множество "обработчиков" (handlers) для отправки логов в файлы, базы данных, Slack, Telegram, Email и облачные сервисы.
Установка Monolog через Composer:
composer require monolog/monolog
Базовый пример использования:
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Создаём канал (channel) логов — обычно по имени приложения или модуля
$log = new Logger('my_app');
// Добавляем обработчик: пишем логи уровня WARNING и выше в файл
$log->pushHandler(new StreamHandler(__DIR__ . '/logs/app.log', Logger::WARNING));
// Теперь можно логировать
$log->warning('Пользователь {username} попытался войти с неверным паролем', ['username' => 'ivan']);
$log->error('Не удалось подключиться к базе данных: {error}', ['error' => $e->getMessage()]);
?>
Почему это важно? Использование PSR-3 делает ваш код независимым от конкретной библиотеки. Если завтра вы захотите перейти с Monolog на другую реализацию (например, Log4php), вам нужно будет изменить только инициализацию логгера, а не все места, где вызываются $log->error().
2. Уровни логирования: когда что использовать
PSR-3 определяет 8 уровней логирования (от самого низкого приоритета к высокому): DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY. Правильное использование уровней — основа читаемых и полезных логов.
- DEBUG — детальная отладочная информация, которая нужна только разработчику (значения переменных, трассировка вызовов). Включайте только на dev-окружении.
- INFO — важные события работы приложения: пользователь зарегистрировался, заказ создан, cron-задача выполнена.
- NOTICE — нормальные, но значимые события (например, использование устаревшего API).
- WARNING — потенциальные проблемы, которые не остановили выполнение скрипта (неудачная попытка входа, медленный запрос к БД).
- ERROR — ошибки, требующие вмешательства: исключения, сбои в работе внешних сервисов.
- CRITICAL — критические сбои: потеря соединения с БД, нехватка памяти.
- ALERT — требуется немедленное действие администратора (сайт недоступен).
- EMERGENCY — вся система неработоспособна.
Практический совет: Настройте разные обработчики для разных уровней. Например, WARNING и выше пишите в файл, а ERROR и выше — ещё и отправляйте в Telegram/Slack.
// Пример: два обработчика для одного логгера
$log->pushHandler(new StreamHandler(__DIR__ . '/logs/all.log', Logger::DEBUG));
$log->pushHandler(new StreamHandler(__DIR__ . '/logs/errors.log', Logger::ERROR));
// Для критических ошибок — отправка в Telegram
$log->pushHandler(new TelegramBotHandler('TOKEN', 'CHAT_ID', Logger::CRITICAL));
3. Структурированные логи и контекст
Одна из самых частых ошибок — логирование строки с подстановкой переменных через конкатенацию:
// ПЛОХО: строку сложно парсить
$log->error('Ошибка с ID ' . $userId . ': ' . $errorMessage);
Правильный подход — использовать структурированные логи с контекстом (второй аргумент методов логгера):
// ХОРОШО: контекст сохраняется отдельно
$log->error('Ошибка обработки пользователя', [
'user_id' => $userId,
'error' => $errorMessage,
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
'trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5)
]);
Преимущества структурированных логов:
- Легко фильтровать и искать по полям (например, все ошибки с
user_id = 42). - Можно передавать в системы агрегации (ELK, Graylog, Sentry) в формате JSON.
- Не нужно экранировать спецсимволы в сообщениях.
Для вывода в файл в читаемом виде используйте LineFormatter:
use Monolog\Formatter\LineFormatter;
$formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context%\n");
$handler = new StreamHandler(__DIR__ . '/logs/app.log', Logger::DEBUG);
$handler->setFormatter($formatter);
$log->pushHandler($handler);
4. Ротация логов и управление размером
Если не управлять размером логов, файл может вырасти до гигабайтов, что вызовет проблемы с производительностью и дисковым пространством. Monolog предоставляет встроенный обработчик RotatingFileHandler, который автоматически создаёт новый файл каждый день (или каждый час) и удаляет старые.
use Monolog\Handler\RotatingFileHandler;
// Хранить логи за 30 дней
$handler = new RotatingFileHandler(__DIR__ . '/logs/app.log', 30, Logger::DEBUG);
$log->pushHandler($handler);
Дополнительные рекомендации:
- Храните логи вне корня веб-сервера (не в
public/), чтобы их нельзя было скачать через браузер.