Psr/Log для PHP: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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]);

        //                    

Рекомендаций