Logging
A log is a file where the application records what happened: who logged in, which error occurred and when. On a production server error details are not shown to the visitor, and the log is the only way to find out what went wrong.
Laminas's own laminas-log package is no longer developed, so new Laminas projects use the Monolog library. It implements the common PSR-3 standard: your classes depend on the Psr\Log\LoggerInterface interface, not on a particular library.
Installation
composer require monolog/monologCreate the folder data/logs — the log file will be written there.
The logger factory
Monolog is not a Laminas module, so nothing is added to the module list. The logger is created by a factory of your own:
// 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 writes messages to a file. The second argument is the lowest level: messages below Info do not reach the file.
Register the logger under the interface name:
// module/Blog/config/module.config.php
// at the top of the file: use Psr\Log\LoggerInterface;
'service_manager' => [
'factories' => [
LoggerInterface::class => Log\LoggerFactory::class,
],
],Writing to the log
Add LoggerInterface to the controller's constructor — ReflectionBasedAbstractFactory passes it in by itself:
// 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('Post deleted', ['id' => $id]);
return $this->redirect()->toRoute('blog');
}Open /blog/delete/2 and look into data/logs/app.log: the file has a line with the date, the INFO level, the text and the id value. The second argument is an array of details; they are not pasted into the message text.
Message levels
| Method | When to use |
|---|---|
debug() | Details for debugging |
info() | Ordinary events: a login, a save |
warning() | Something unusual, but work continues |
error() | An error that stopped an action |
critical() | A failure that needs urgent attention |
Logging exceptions
Unhandled exceptions are best logged in one place — with an error event listener in module/Blog/src/Module.php:
// module/Blog/src/Module.php, inside 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]);
});Now any exception in a controller reaches the log together with the place where it was thrown.
Common mistake
Never write passwords, card numbers or whole form contents to the log: many people read the log file, and it is often kept longer than the data itself. Record identifiers — ['user_id' => 5] — rather than the personal data.