Psr/Log для PHP: полное руководство с примерами
Логирование — один из ключевых аспектов разработки любого серьёзного приложения. Без него невозможно отлаживать ошибки в production-среде, отслеживать поведение пользователей или анализировать производительность системы. Однако, если в проекте используется несколько библиотек, каждая из которых пишет логи по-своему, разработчик сталкивается с хаосом: разные уровни, разные форматы, разные способы вывода. Именно для решения этой проблемы был создан стандарт PSR-3 (Logger Interface).
Psr/Log — это не библиотека для логирования как таковая, а стандартный интерфейс (набор контрактов), который определяет, как должен выглядеть универсальный логгер в PHP. Он предоставляет восемь методов для записи сообщений разного уровня важности (emergency, alert, critical, error, warning, notice, info, debug), а также универсальный метод log(). Любая библиотека, реализующая PSR-3, может быть легко интегрирована в любой проект, который ожидает этот интерфейс. Это позволяет разработчику менять реализацию логгера (например, с Monolog на Analog) без изменения кода приложения.
В этой статье мы подробно разберём библиотеку psr/log, начиная от установки и заканчивая сложными практическими примерами. Вы узнаете, как правильно использовать каждый метод, как форматировать сообщения с плейсхолдерами и как избежать типичных ошибок. Материал будет полезен как новичкам, только знакомящимся с PHP, так и опытным разработчикам, желающим структурировать свои знания.
Установка
Библиотека psr/log устанавливается через Composer — стандартный менеджер зависимостей для PHP. Выполните в терминале следующую команду:
composer require psr/log
После установки в вашем проекте появится директория vendor/psr/log. Если вы используете фреймворк (Laravel, Symfony), то PSR-3, скорее всего, уже включён в его состав. Для подключения автозагрузчика в вашем PHP-скрипте добавьте строку:
require __DIR__ . '/vendor/autoload.php';
Обратите внимание: psr/log — это только интерфейс. Для реальной записи логов вам понадобится реализация, например, monolog/monolog. Установка Monolog:
composer require monolog/monolog
Быстрый старт
Давайте создадим простейший пример логирования с использованием Monolog (самой популярной реализации PSR-3).
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// Создаём логгер с именем 'my_logger'
$log = new Logger('my_logger');
// Добавляем обработчик: пишем все сообщения уровня DEBUG и выше в файл app.log
$log->pushHandler(new StreamHandler(__DIR__ . '/app.log', Logger::DEBUG));
// Записываем несколько сообщений
$log->info('Приложение запущено');
$log->warning('Не удалось загрузить конфигурационный файл, используем значения по умолчанию');
$log->error('Критическая ошибка: база данных недоступна');
echo "Логи записаны. Проверьте файл app.log";
После выполнения этого скрипта в файле app.log вы увидите примерно следующее:
[2025-04-10T15:30:45.123456+00:00] my_logger.INFO: Приложение запущено [] []
[2025-04-10T15:30:45.123789+00:00] my_logger.WARNING: Не удалось загрузить конфигурационный файл, используем значения по умолчанию [] []
[2025-04-10T15:30:45.124012+00:00] my_logger.ERROR: Критическая ошибка: база данных недоступна [] []
Это минимальный рабочий пример. Теперь разберём каждый метод интерфейса подробно.
Основные методы интерфейса Psr\Log\LoggerInterface
Интерфейс Psr\Log\LoggerInterface определяет 9 методов. Первые 8 — это методы для конкретных уровней логирования, а девятый — универсальный метод log(), который принимает уровень в качестве параметра.
1. emergency()
Сигнатура: public function emergency(string $message, array $context = []): void
Что делает: Записывает сообщение самого высокого уровня критичности. Система полностью неработоспособна. Пример: потеря соединения с базой данных, отказ основного сервиса.
Пример:
$log->emergency('Система не может продолжить работу: {reason}', ['reason' => 'отказ диска RAID']);
2. alert()
Сигнатура: public function alert(string $message, array $context = []): void
Что делает: Сообщение уровня "Тревога". Требуется немедленное вмешательство. Пример: закончилось место на диске, взлом.
Пример:
$log->alert('Закончилось место на диске {disk}, свободно только {free} MB', ['disk' => '/dev/sda1', 'free' => 50]);
3. critical()
Сигнатура: public function critical(string $message, array $context = []): void
Что делает: Критическое состояние. Основной компонент приложения не работает, но система в целом может функционировать. Пример: не удалось обработать платёж, ошибка в основном потоке.
Пример:
$log->critical('Не удалось обработать заказ #{orderId}: {error}', ['orderId' => 12345, 'error' => 'Недостаточно средств']);
4. error()
Сигнатура: public function error(string $message, array $context = []): void
Что делает: Ошибка, которая не требует немедленного вмешательства, но должна быть залогирована и исследована. Пример: исключение, пойманное в блоке try-catch.
Пример:
try {
// какой-то код
} catch (\Exception $e) {
$log->error('Ошибка при обработке файла {file}: {error}', [
'file' => $filename,
'error' => $e->getMessage()
]);
}
5. warning()
Сигнатура: public function warning(string $message, array $context = []): void
Что делает: Предупреждение. Что-то пошло не так, но приложение продолжает работу. Пример: использование устаревшего API, неверный формат входных данных.
Пример:
$log->warning('Используется устаревший метод {method}. Замените на {newMethod}', [
'method' => 'mysql_connect',
'newMethod' => 'mysqli_connect'
]);
6. notice()
Сигнатура: public function notice(string $message, array $context = []): void
Что делает: Уведомление о нормальном, но значимом событии. Пример: пользователь сменил пароль, задача выполнена успешно.
Пример:
$log->notice('Пользователь {username} успешно зарегистрирован', ['username' => 'john_doe']);
7. info()
Сигнатура: public function info(string $message, array $context = []): void
Что делает: Информационное сообщение. Ход выполнения приложения. Пример: запрос обработан, страница загружена.
Пример:
$log->info('Запрос к API {url} выполнен за {time} мс', ['url' => '/api/users', 'time' => 120]);
8. debug()
Сигнатура: public function debug(string $message, array $context = []): void
Что делает: Отладочная информация. Подробные данные, которые обычно не нужны в production. Пример: значения переменных, результаты промежуточных вычислений.
Пример:
$log->debug('SQL запрос: {query} с параметрами {params}', ['query' => $sql, 'params' => json_encode($bindings)]);
9. log()
Сигнатура: public function log($level, string $message, array $context = []): void
Что делает: Универсальный метод для записи сообщения любого уровня. Уровень передаётся первым параметром. Позволяет динамически выбирать уровень логирования.
Пример:
$levels = [Logger::INFO, Logger::WARNING, Logger::ERROR];
$currentLevel = $levels[array_rand($levels)];
$log->log($currentLevel, 'Динамическое логирование: {level}', ['level' => $currentLevel]);
Реальный практический пример (150+ строк кода)
Рассмотрим полноценный пример: веб-приложение для обработки заказов. Мы создадим логгер, который пишет логи в файл и, в случае критических ошибок, отправляет уведомление администратору по email (имитация).
<?php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\NativeMailerHandler;
use Monolog\Processor\IntrospectionProcessor;
use Monolog\Processor\MemoryUsageProcessor;
use Monolog\Formatter\LineFormatter;
/**
* Класс для обработки заказов
*/
class OrderProcessor
{
private Logger $logger;
private array $orders = [];
public function __construct()
{
// Создаём логгер канала 'order'
$this->logger = new Logger('order');
// Формат для логов: [дата] канал.УРОВЕНЬ: сообщение {контекст}
$formatter = new LineFormatter(
"[%datetime%] %channel%.%level_name%: %message% %context%\n",
'Y-m-d H:i:s'
);
// Обработчик для записи ВСЕХ логов в файл
$fileHandler = new StreamHandler(__DIR__ . '/logs/orders.log', Logger::DEBUG);
$fileHandler->setFormatter($formatter);
$this->logger->pushHandler($fileHandler);
// Обработчик для отправки email при CRITICAL и выше
$emailHandler = new NativeMailerHandler(
'admin@example.com', // Кому
'Критическая ошибка в заказе', // Тема
'from@example.com', // От кого
Logger::CRITICAL // Уровень
);
$emailHandler->setFormatter($formatter);
$this->logger->pushHandler($emailHandler);
// Добавляем процессоры для обогащения логов
$this->logger->pushProcessor(new IntrospectionProcessor()); // Файл и строка вызова
$this->logger->pushProcessor(new MemoryUsageProcessor()); // Использование памяти
}
/**
* Добавить заказ в систему
*/
public function addOrder(int $orderId, array $items): bool
{
try {
// Валидация
if (empty($items)) {
throw new \InvalidArgumentException('Заказ должен содержать хотя бы один товар');
}
$total = 0;
foreach ($items as $item) {
if (!isset($item['price']) || !isset($item['quantity'])) {
throw new \InvalidArgumentException("Неверный формат товара: " . json_encode($item));
}
$total += $item['price'] * $item['quantity'];
}
$this->orders[$orderId] = [
'items' => $items,
'total' => $total,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s')
];
$this->logger->info('Заказ #{orderId} добавлен. Сумма: {total}', [
'orderId' => $orderId,
'total' => $total,
'items_count' => count($items)
]);
return true;
} catch (\InvalidArgumentException $e) {
$this->logger->warning('Ошибка валидации заказа #{orderId}: {error}', [
'orderId' => $orderId,
'error' => $e->getMessage()
]);
return false;
} catch (\Exception $e) {
$this->logger->critical('Неожиданная ошибка при добавлении заказа #{orderId}: {error}', [
'orderId' => $orderId,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return false;
}
}
/**
* Обработать заказ (изменить статус)
*/
public function processOrder(int $orderId): bool
{
if (!isset($this->orders[$orderId])) {
$this->logger->error('Попытка обработать несуществующий заказ #{orderId}', [
'orderId' => $orderId
]);
return false;
}
// Имитация длительной обработки
$this->logger->debug('Начинаем обработку заказа #{orderId}', ['orderId' => $orderId]);
//