Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Журнал

Журнал (лог) — файл, в который приложение записывает, что происходило: кто вошёл, какая ошибка случилась и когда. На рабочем сервере подробности ошибок посетителю не показывают, и журнал — единственный способ узнать, что пошло не так.

Собственный пакет laminas-log больше не развивается, поэтому в новых проектах на Laminas используют библиотеку Monolog. Она реализует общий стандарт PSR-3: ваши классы зависят от интерфейса Psr\Log\LoggerInterface, а не от конкретной библиотеки.

Установка

composer require monolog/monolog

Создайте папку data/logs — в неё будет писаться файл журнала.

Фабрика журнала

Monolog — не модуль Laminas, поэтому в список модулей ничего добавлять не нужно. Журнал создаёт ваша фабрика:

// module/Blog/src/Log/LoggerFactory.php
namespace Blog\Log;
 
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use Psr\Container\ContainerInterface;
 
class LoggerFactory
{
    public function __invoke(ContainerInterface $container): Logger
    {
        $logger = new Logger('blog');
        $logger->pushHandler(new StreamHandler('data/logs/app.log', Level::Info));
 
        return $logger;
    }
}

StreamHandler пишет сообщения в файл. Второй аргумент — наименьший уровень: сообщения ниже Info в файл не попадут.

Зарегистрируйте журнал под именем интерфейса:

// module/Blog/config/module.config.php
// в начале файла: use Psr\Log\LoggerInterface;
'service_manager' => [
    'factories' => [
        LoggerInterface::class => Log\LoggerFactory::class,
    ],
],

Запись в журнал

Добавьте LoggerInterface в конструктор контроллера — ReflectionBasedAbstractFactory передаст его сама:

// module/Blog/src/Controller/PostController.php
use Psr\Log\LoggerInterface;
 
public function __construct(
    private PostTable $posts,
    private LoggerInterface $logger,
) {}
 
public function deleteAction()
{
    $id = (int) $this->params()->fromRoute('id');
    $this->posts->delete($id);
 
    $this->logger->info('Статья удалена', ['id' => $id]);
 
    return $this->redirect()->toRoute('blog');
}

Откройте /blog/delete/2 и загляните в data/logs/app.log: в файле появится строка с датой, уровнем INFO, текстом и значением id. Второй аргумент — массив с подробностями; их не вклеивают в текст сообщения.

Уровни сообщений

МетодКогда использовать
debug()Подробности для отладки
info()Обычные события: вход, сохранение
warning()Что-то необычное, но работа продолжается
error()Ошибка, из-за которой действие не выполнено
critical()Сбой, требующий срочного внимания

Запись исключений

Необработанные исключения удобно записывать в одном месте — слушателем события ошибки в module/Blog/src/Module.php:

// module/Blog/src/Module.php, внутри onBootstrap()
$events->attach(MvcEvent::EVENT_DISPATCH_ERROR, function (MvcEvent $e) {
    $exception = $e->getParam('exception');
 
    if ($exception === null) {
        return;
    }
 
    $logger = $e->getApplication()->getServiceManager()->get(LoggerInterface::class);
    $logger->error($exception->getMessage(), ['exception' => $exception]);
});

Теперь любое исключение в контроллере попадёт в журнал вместе с местом, где оно возникло.

Частая ошибка

В журнал нельзя писать пароли, номера карт и содержимое форм целиком: файл журнала читают многие, и он часто хранится дольше самих данных. Записывайте идентификаторы — ['user_id' => 5] — а не сами личные данные.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.