By Matej Elias
Last Validated on May 10 2021 · Originally Published on May 10, 2021 · Viewed 1.1k times

Introduction

In this tutorial, you will learn why logging is important and how to use Monolog for logging PHP applications. This will help you troubleshoot your application much more easily and quickly.

Monolog is an open-source PHP library that aims to make logging easier and more efficient. To see the library source code, visit Monolog on GitHub.

Finding bugs in the code may be very frustrating and time-consuming, especially, when the application grows in scale. With Monolog, you can debug your application efficiently and save a lot of time.


Prerequisites

  • Webserver with PHP installed.
  • Composer installed.

Step 1 — Installing Monolog

The best way to install Monolog is using composer. First, open the terminal or command line and navigate yourself to your project directory. Then install Monolog by typing:

composer require monolog/monolog

This will install Monolog in your project directory.

Getting To Know Monolog

With Monolog, you create a Logger instance that has a channel-name and stack of Handlers. Handlers are responsible for saving or sending the message to the desired location which can be a file, terminal, database, or even an email.

The sent message travels through the stack of Handlers. That means that lastly added handler reacts to the message first. If the bubble variable is set to true, the message will "bubble" through the stack of Handlers. If the variable is set to false, only the first Handler (the one on the top of the stack - lastly added) will react.

Monolog also has Processors that can be used to process the message and Formatters that are used to transform the log message to the desired format.

Monolog Log Levels

Monolog recognizes the following levels of intensity:

  • DEBUG (100): detailed debug information
  • INFO (200) : interesting events. Examples: User logs in, SQL logs
  • NOTICE (250): normal but significant events
  • WARNING (300): exceptional occurrences that are not errors. Examples: Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong
  • ERROR (400): runtime errors that do not require immediate action but should typically be logged and monitored
  • CRITICAL (500): critical conditions. Example: Application component unavailable, unexpected exception
  • ALERT (550): action must be taken immediately. Example: Entire website down, database unavailable, etc. This should trigger phone call or SMS alerts and wake you up
  • EMERGENCY (600): emergency: system is unusable

Handlers will only take logs that have the same severity or higher. For example, the WARNING log will not pass through the ERROR handler.


Step 2 — Logging To A File

Let's start with the logging to a file. For that, we will create a Logger instance. The constructor requires a string channel-name. That will be the name of our Logger. With the pushHandler() method, we will push the new StreamHandler to the top of the stack.

StreamHandler is used for logging into a file. The first argument of the constructor is the location of the log file and the second argument is the severity level.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Handler\\StreamHandler;
use Monolog\\Logger;

$logger = new Logger('my_logger');
$logger->pushHandler(new StreamHandler(__DIR__ . '/app.log', Logger::DEBUG));

$logger->info('Message that will be logged');

Lastly, we use info() method to send message to the top of the handler stack. In PHP, the __DIR__ constant represents a path to the current project. This saves space in the code as you don't need to write the full path to the file.

This will create a new log in the app.log file that will look like this:

Output:
[2021-04-22T11:04:36.606731+02:00] my_logger.INFO: Message that will be logged [] []

Step 3 — Logging In Terminal

We can also send logs directly to the terminal using the same handler. We will switch the log file with the php://stderr stream. This will print the log to the terminal and create a new log in the webserver's default log file.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Logger;
use Monolog\\Handler\\StreamHandler;

$logger = new Logger('my_logger');
$logger->pushHandler(new StreamHandler('php://stderr', Logger::DEBUG));

$logger->info('Message in the terminal');

This is the output in the terminal and webserver's log file:

Output:
[2021-04-22T11:19:20.746254+02:00] my_logger.INFO: Message in the terminal [] []

Step 4 — Sending Logs To Email

In conjunction with the SwiftMailer, we can send logs to email. This is especially useful for critical errors that caused the application to shut down or for errors that prevent the application from working properly.

To be able to send email using SwiftMailer, we need to install SwiftMailer using composer:

composer require swiftmailer/swiftmailer

First, we need to create a transporter where we specify the required information. We don't recommend using Gmail for this as it may cause unexpected behaviour. We will be using a custom mail server provided by the webhosting company. Then we create SwiftMailer message and finally send a critical error message to the email.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Logger;
use Monolog\\Handler\\SwiftMailerHandler;

//Create transporter
$transporter = new Swift_SmtpTransport('host', 123 /* PORT */, 'tls');
$transporter->setUsername('username');
$transporter->setPassword('password');

//Create mailer and message
$mailer = new Swift_Mailer($transporter);
$message = new Swift_Message('A critical error has occurred');
$message->setFrom(['no-reply@myapp.com' => 'My Application']);
$message->setTo(['admin@myapp.com' => 'Web Admin']);

//Send log
$logger = new Logger('my_logger');
$logger->pushHandler(new SwiftMailerHandler($mailer, $message, Logger::CRITICAL));
$logger->critical('Critical error message');

This will send a log to the email address specified in the sendTo() method. Don't forget to replace the example values with the actual SMTP login credentials.


Step 5 — Log Additional Data Using Context Array

With the message, we can also pass the context array to add additional pieces of information to the log file by providing the second argument of the info() method.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Logger;
use Monolog\\Handler\\StreamHandler;

$logger = new Logger('my_logger');
$logger->pushHandler(new StreamHandler( __DIR__ . '/app.log', Logger::DEBUG));

$logger->info('Log message', ['data' => 'value']);

This will add the following line in the app.log file:

Output:
[2021-04-22T11:32:27.798282+02:00] my_logger.INFO: Log message {"data":"value"} []

As you can see, the context array was stored for further use.


Step 6 — Multiple Handlers

As you already know, we can have multiple handlers in the stack. In the following example, we have a DEBUG handler on top of the WARNING handler.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Handler\\StreamHandler;
use Monolog\\Logger;

$logger = new Logger('my_logger');
//Second Handler (from the top of the stack)
$logger->pushHandler(new StreamHandler(__DIR__ . '/app.log', Logger::WARNING));
//First handler (from the top of the stack)
$logger->pushHandler(new StreamHandler(__DIR__ . '/app.log', 
										 Logger::DEBUG, $bubble = true));

$logger->info('This is DEBUG severity message');
$logger->warning('This is WARNING severity message');

This will send DEBUG severity message that will only pass through the DEBUG handler. Then, it will send a WARNING severity message that will pass through both handlers. That means it will create three new log entries in the app.log file.

Output:
[2021-04-22T12:16:22.544159+02:00] my_logger.INFO: This is DEBUG severity message [] []
[2021-04-22T12:16:22.545472+02:00] my_logger.WARNING: This is WARNING severity message [] []
[2021-04-22T12:16:22.545472+02:00] my_logger.WARNING: This is WARNING severity message [] []

Step 7 — Custom Processor

As you have seen, there are two arrays at the end of the log. The first is the context array which we already covered, the second one is the array returned by the custom processor. A custom processor is a custom function that is used to further process the log data contained in the $record variable.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Handler\\StreamHandler;
use Monolog\\Logger;

$logger = new Logger('my_logger');
$logger->pushHandler(new StreamHandler(__DIR__ . '/app.log', Logger::DEBUG));

//Creating new processor
$logger->pushProcessor(function ($record) {
    $record['extra']['data'] = 'value';
    return $record;
});

$logger->info('Message with extra data');

Processors are added by the pushProcessor() method called on the Logger object.

This will create following entry in the app.log file:

Output:
[2021-04-22T12:28:34.597590+02:00] my_logger.INFO: Message with extra data [] {"data":"value"}

But there are also many built-in processors that can fit your needs such as WebProcessor that adds the current request URI, request method, and client IP to a log record, or ProcessIdProcessor that adds the process id to a log record. A list of all processors can be found on the official GitHub documentation.


Step 8 — Monolog Formatters (Optional)

Monolog has multiple built-in formatters that are used to transform your log message to the desired format. A full list of the built-in formatters can be found on the official GitHub documentation.

JSON Formatter

In this example, we will use a JSON formatter that will transform the message to the JSON format. JSON is the acronym for JavaScript Object Notation and it's widely used among web application developers.

require __DIR__ . '/vendor/autoload.php';

use Monolog\\Formatter\\JsonFormatter; 
use Monolog\\Handler\\StreamHandler;
use Monolog\\Logger;

$logger = new Logger('my_logger');

//Create the formatter
$formatter = new JsonFormatter();

//Set the formatter
$handler = new StreamHandler(__DIR__ . '/app.log', Logger::DEBUG);
$handler->setFormatter($formatter);

//Set the handler
$logger->pushHandler($handler);
$logger->info('Information message', ['data' => 'value']);

Formatters are added to specific handlers as every handler can have a different formatter. Formatter is added by the setFormatter() method called on the Handler object.

This will add the following entry to the log file:

Output:
{"message":"Information message","context":{"data":"value"},"level":200,"level_name":"INFO","channel":"my_logger","datetime":"2021-04-22T12:48:41.111543+02:00","extra":{}}

Conclusion

In this tutorial, you learned how to install and use Monolog in your PHP projects. At this point, you can log to a file or terminal, and even send logs to your email. You can create custom processors to further process the log data, and change the log format to fit your needs. Now, you have a great tool to create a powerful logging system in your PHP application.