Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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/monolog

Create 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

MethodWhen 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.

Thanks for reading!

Donate to the site — we work hard for you.