Журнал
Журнал (лог) — файл, в который приложение записывает, что происходило: кто вошёл, какая ошибка случилась и когда. На рабочем сервере подробности ошибок посетителю не показывают, и журнал — единственный способ узнать, что пошло не так.
Собственный пакет 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] — а не сами личные данные.