Logging with Monolog: From Devtools to Slack

Younes Rafie
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.

Frequently Asked Questions (FAQs) about Logging with Monolog from DevTools to Slack

How do I set up Monolog to send logs to Slack?

Setting up Monolog to send logs to Slack involves a few steps. First, you need to install Monolog via composer by running the command composer require monolog/monolog. Next, create a new logger and push handler instance into it. You can use the SlackHandler class to send logs to Slack. You will need to provide your Slack Webhook URL and the channel name where you want to send the logs. Here is a sample code:

use Monolog\Logger;
use Monolog\Handler\SlackHandler;

$logger = new Logger('my_logger');
$logger->pushHandler(new SlackHandler('Your-Slack-Webhook-URL', '#Your-Slack-Channel', 'Monolog', true, null, Logger::ERROR));

How can I filter logs sent to Slack based on their level?

Monolog allows you to filter logs based on their level. When you create a new SlackHandler instance, you can specify the minimum level of logs that should be sent to Slack. For example, if you want to send only error logs and above, you can do so by setting the level to Logger::ERROR. Here is how you can do it:

$logger->pushHandler(new SlackHandler('Your-Slack-Webhook-URL', '#Your-Slack-Channel', 'Monolog', true, null, Logger::ERROR));

Can I send logs to multiple channels in Slack?

Yes, you can send logs to multiple channels in Slack. You just need to create a new SlackHandler instance for each channel and push them into the logger. Here is an example:

$logger->pushHandler(new SlackHandler('Your-Slack-Webhook-URL', '#Channel1', 'Monolog', true, null, Logger::ERROR));
$logger->pushHandler(new SlackHandler('Your-Slack-Webhook-URL', '#Channel2', 'Monolog', true, null, Logger::ERROR));

How can I customize the format of logs sent to Slack?

Monolog allows you to customize the format of logs by using formatters. You can create a new instance of any formatter class and set it to the handler. For example, you can use the LineFormatter class to customize the format of logs. Here is how you can do it:

use Monolog\Formatter\LineFormatter;

$formatter = new LineFormatter("%datetime% > %level_name% > %message% %context% %extra%\n");
$handler = new SlackHandler('Your-Slack-Webhook-URL', '#Your-Slack-Channel', 'Monolog', true, null, Logger::ERROR);
$handler->setFormatter($formatter);
$logger->pushHandler($handler);

How can I handle exceptions with Monolog?

Monolog provides a NormalizerFormatter class that can handle exceptions. It converts an exception to a loggable format. You can set this formatter to the handler to handle exceptions. Here is an example:

use Monolog\Formatter\NormalizerFormatter;

$formatter = new NormalizerFormatter();
$handler = new SlackHandler('Your-Slack-Webhook-URL', '#Your-Slack-Channel', 'Monolog', true, null, Logger::ERROR);
$handler->setFormatter($formatter);
$logger->pushHandler($handler);

Can I use Monolog to log messages in my local development environment?

Yes, you can use Monolog to log messages in your local development environment. You can use the StreamHandler class to log messages to a file. Here is an example:

use Monolog\Handler\StreamHandler;

$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::DEBUG));

How can I log messages with different levels?

Monolog provides different methods to log messages with different levels. For example, you can use the info(), error(), warning(), debug(), etc. methods to log messages with different levels. Here is an example:

$logger->info('This is an info message');
$logger->error('This is an error message');

Can I send logs to other services besides Slack?

Yes, Monolog supports many different handlers that allow you to send logs to various services. For example, you can use the SyslogHandler to send logs to a syslog daemon, or the MailHandler to send logs via email. You just need to create a new instance of the handler and push it into the logger.

How can I test if my Monolog setup is working correctly?

You can test your Monolog setup by logging a test message and checking if it appears in your Slack channel or other logging destinations. Here is an example:

$logger->info('This is a test message');

Can I use Monolog in my Symfony application?

Yes, Monolog is integrated with Symfony and you can use it to log messages in your Symfony application. You just need to configure it in your config.yml file.