Logging with Monolog: From Devtools to Slack

Younes Rafie
Share

Logging is an important part of the app development/maintenance cycle. It’s not just about the data you log, but also about how you do it. In this article, we are going to explore the Monolog package and see how it can help us take advantage of our logs.

big lot of document pile waiting for clear

Installation

Monolog is available on Packagist, which means that you can install it via Composer.

composer require 'monolog/monolog:1.13.*'

However, there is a great chance that you’re using a framework for your app. Monolog provides a list of integrations for most popular frameworks. I’m using Silex for this demo, but I won’t be using the provided integration to show how you can configure Monolog for any of your apps.

The Logger

When you create a new logger, you should give it a channel name. This is how you distinguish your list of loggers. I will bind my logger to the app container.

// app/bootstrap/container.php

$logger = new \Monolog\Logger('general');
$app->container->logger = $logger;

Because Monolog is PSR-3, you can be sure that you are adhering to the PHP-FIG standards. This will allow you to switch to any other implementation if you don’t feel comfortable with Monolog (I don’t see any reason why you wouldn’t). You can now start logging using one of the following methods, depending on your log level. (log, debug, info, warning, error, critical, alert, emergency).

$app->container->logger->info("Just an INFO message.");

Handlers

When you log something, it goes through the list of registered handlers. A handler needs to specify a log level it will be handling and a bubble status deciding wether to propagate the message or not. You can see that we didn’t register any handlers for our logger so far. By default, Monolog will use the \Monolog\Handler\StreamHandler to log to the standard error output. You can see the list of available handlers in the documentation.

To illustrate the use of multiple handlers and bubbling, let’s assume that we want to log our info messages to the browser console and error message to the terminal output.

// app/bootstrap/container.php

$logger = new \Monolog\Logger('general');

$browserHanlder = new \Monolog\Handler\BrowserConsoleHandler(\Monolog\Logger::INFO);
$streamHandler = new \Monolog\Handler\StreamHandler('php://stderr', \Monolog\Logger::ERROR);

$logger->pushHandler($browserHanlder);
$logger->pushHandler($streamHandler);

$app->container->logger = $logger;
// app/routes.php

$app->get('/admin/users', function () use ($app) {
    $app->container->logger->info("Another INFO message");
    $app->container->logger->error("just another ERROR message");
    // ...
});

Browser log

Stream log

The error message was logged to the terminal console as expected, but why does it appear on the browser console?
This is the bubbling option that Monolog provides. The error message was logged to the terminal first, and as long as the bubbling option is set to true, it will continue its way to the top of the handlers queue. Now, lets set the bubbling option to false on the stream handler.

// app/bootstrap/container.php

// ...
$streamHandler = new \Monolog\Handler\StreamHandler('php://stderr', \Monolog\Logger::ERROR, false);
// ...

Browser log

Stream log

Now you can see that the error log didn’t show on the browser console, and that’s how you can separate your log levels.

You may also use external services such as HipChat, Slack, etc, as message recipients. Let’s assume that we have a developers channel on Slack, and we want to let everybody know that an error occurred with the necessary details.

Slack Handler

Before pushing the Slack handler to the queue, you need to get a token to give the handler the authorization to post to your Slack channel. Go to the authorization page and generate a new token.

// app/bootstrap/container.php

// ...
$slackHandler = new \Monolog\Handler\SlackHandler('xoxp-5156076911-5156636951-6084570483-7b4fb8', '#general', 'ChhiwatBot');
$logger->pushHandler($slackHandler);

// ...

You only need to specify your Slack token and the channel name, and the third optional parameter is the robot name. The log level for Slack is CRITICAL, but you can change it using the setLevel method.

$slackHandler->setLevel(\Monolog\Logger::ERROR);

After sending the log message, you can visit your channel to see the logged error message.

$app->container->logger->error("just an ERROR message");

Slack log

Formatters

Until now, all of our logs were pre-formatted and we didn’t get the chance to specify the logging format. This is what formatters are for. Every handler has a default formatter, and if you don’t specify one you’ll get the LineFormatter. For every formatter you can always switch to another one, like the HtmlFormatter. Some handlers have their own formatters like the ChromePHPHandler, LogglyHandler, etc.

// app/bootstrap/container.php

$browserHanlder = new \Monolog\Handler\BrowserConsoleHandler(\Monolog\Logger::INFO);
$browserHanlder->setFormatter(new \Monolog\Formatter\HtmlFormatter);

//...

Now when you log a new message it will be formatted in HTML and then logged to the browser console.

$app->container->logger->info("Another INFO message");

Browser log

You can also extend the LineFormatter to include more details on the message. The available variables are:

  • message: Log message.
  • context: list of data passed when creating the logger. (new Logger('channelName', ['user' => 'adam'])).
  • level: Error level code.
  • level_name: Error level name.
  • channel: Logger channel name.
  • datetime: Current date time.
  • extra: Data pushed by preprocessors.

Preprocessors

Preprocessors are useful for adding more details to your log. For example, the WebProcessor adds more details about the request like (url, ip, etc). When creating your logger instance you set the list of handlers and processors, but you can also use the pushProcessor method to add them later.

// app/bootstrap/container.php

$logger = new \Monolog\Logger('general');
$logger->pushProcessor(new \Monolog\Processor\WebProcessor);

$browserHanlder = new \Monolog\Handler\BrowserConsoleHandler(\Monolog\Logger::INFO);

$logger->pushHandler($browserHanlder);
//...

When logging anything, the log record will be passed through the list of registered processors, and you’ll get something like the following.

$app->container->logger->info("Another INFO message");

Browser log

Monolog provides a list of useful preprocessor for including details about memory peaks, user id, etc. You can check the list of available preprocessors in the documentation.

Wrap Up

Monolog is one of the best available logging libraries out there, and it’s also integrated into most of the popular frameworks like Symfony, Laravel, Silex, Slim, etc. If you have any comments or questions, you can post them below and I’ll do my best to answer them.