Logging with Monolog: From Devtools to Slack
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.
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");
// ...
});
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);
// ...
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");
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");
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");
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.