Technical Resources
Educational Resources
APM Integrated Experience
Connect with Us
PHP logging is usually handled through an applications framework interface. This guide includes examples from a few popular frameworks and explores how each handles logging. Frameworks included are Laravel®, Drupal®, Zend Framework®, Symfony®, and Magento®. Some of the examples included pertain to specific versions and others to the current version.
Laravel is a popular full-stack framework. True to its philosophy of reusing the best components available, Laravel chose Monolog as its logging functionality.
Logging uses a concept of channels to send log messages to different outputs. Laravel creates a default channel named for the current environment (development, production, etc.). You can configure logging via the file config/logging.php
. Each channel can have one or more drivers, which are the destinations logs are sent to. For a complete list of channels, please see the documentation. Some of the available channel drivers are:
Your application can log error details depending on the value of the APP_DEBUG
environment level in laravel/.env
. On your development environment, you’ll want this value set to true, so errors are displayed along with output. However, for your production environment, you can set it to false, which will log errors depending on your configuration settings. For example, if you’re using single or daily and your code encounters an error or an exception and APP_DEBUG=false, your laravel.log would have a line that looks like this:
[2015-09-25 02:25:40] local.ERROR: exception 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'syntax error, unexpected 'Log' (T_STRING), expecting ',' or ';'' in /var/www/laravel/app/Http/routes.php:20
You can log custom actions in Laravel very easily with the Log facade. It has different methods corresponding to severity levels. These correspond to PSR-3 Log Levels for emergency, alert, critical, error, warning, notice, info, and debug, which come from RFC 5424. (Read more about PSR-3.) In your route or controller, the following will log an informational item:
use Log;
// ...
public function login() {
// ...
Log::info('User login attempt', ['user' => $username]);
}
When a user tries to log in, the following example JSON output will be written to your log file:
[2015-09-25 02:38:39] local.INFO: User logged in. {"user":"oscar"}
Similarly, you can log other severity levels:
// email validation failed
Log::info('Invalid email supplied for registration', ['email' => $email]);
// could not write a file
Log::error('Can not save file');
Logging in Drupal 7 is totally different than Drupal 8, so jump to the Drupal 8 logging section below unless you’re running Drupal 7. Drupal 7 logging is done using the hook_watchdog function. It passes you an array containing the necessary logging details you need for your module. We can take the DBLog module as an example, which writes logs to the Drupal database.
/**
* Implements hook_watchdog().
*/
function dblog_watchdog(array $log_entry) {
Database::getConnection(
'Default',
'default')->insert('watchdog')->fields(array(
'uid' => $log_entry['uid'],
'type' => substr($log_entry['type'], 0, 64),
'message' => $log_entry['message'],
'variables' => serialize($log_entry['variables']),
'severity' => $log_entry['severity'],
'link' => substr($log_entry['link'], 0, 255),
'location' => $log_entry['request_uri'],
'referer' => $log_entry['referer'],
'hostname' => substr($log_entry['ip'], 0, 128),
'timestamp' => $log_entry['timestamp'],
)
)->execute();
}
See the Drupal documentation for more details on available fields.
Logging modules like SysLog, FileLog, and MailLog provide a configuration page to personalize things like errors display, limits, logs formatting, and more. For the example below, you may give the user the ability to select the logging severity and custom system variables (such as user or timestamp).
We’re going to build a MailLog
module for our example. It will only email log errors and above (ERROR, CRITICAL, ALERT, EMERGENCY).
Inside our mailog.module
file, we’re going to register our hook function. You can read more about building a Drupal module in the official documentation.
/**
* Implements hook_watchdog().
*/
function maillog_watchdog(array $log_entry) {
if( (int) $log_entry['severity'] <= WATCHDOG_ERROR ) {
$to = "developer@example.com";
$from = "admin@example.com";
$subject = "MySite logs";
$headers = "From: {$from}";
if (!isset($log_entry['variables'])) {
$message = $log_entry['message'];
} else {
$message = strtr($log_entry['message'], $log_entry['variables']);
}
mail($to, $subject, $message, $headers);
}
}
The severity attribute inside the log_entry array contains an integer from 0 to 7 describing error severity. So, the if condition will only log errors and above. If the log contains any context variables, we substitute them using the strtr PHP function.
Drupal 8 loggers are PSR-3 compliant. You can resolve the Drupal logger class from the container like the following:
Drupal::logger($channel)->info($message);
You may also pass some context variables as a second parameter.
$channel = "general";
$message = "User %username has been deleted.";
$context = [
'%username' => 'younes'
];
Drupal::logger($channel)->info($message, $context);
Because the DBLog module is activated by default, messages are logged to the watchdog table in your database.
// Output
mysql> select wid, type, message, variables from watchdog;
wid type message variables
81 general Just an info message a:0:{}
82 general User %username has been deleted. a:1:{s:9:"%username";s:6:"younes";}
2 rows in set (0.00 sec)
You can install other loggers from the modules page on the dashboard to log to different destinations. As an example, let’s use the Syslog module. You can go ahead and install it on the modules dashboard.
If you run the same code samples from above, you can check the log message on the /var/log/syslog
file.
sudo cat /var/log/syslog
// Output
Sep 27 23:16:04 vagrant-ubuntu-trusty-64 drupal: https://vaprobash.dev|1443395764|general|192.168.22.1|https://vaprobash.dev/admin/modules|https://vaprobash.dev/admin/modules|1||Just an info message
Sep 27 23:16:21 vagrant-ubuntu-trusty-64 drupal: https://vaprobash.dev|1443395781|general|192.168.22.1|https://vaprobash.dev/admin/modules|https://vaprobash.dev/admin/modules|1||User younes has been deleted.
You can configure logging through the page found at Configuration > Development > Logging and Errors. The interface lets you specify errors display level, database log records limit, Syslog format (when Syslog module is installed), and other settings.
Note: Don’t forget to turn off the errors display when moving to production.
You can read all about defining a logger for Drupal 8 in the Logging API documentation. Our logger class should implement the Psr\Log\LoggerInterface
interface and define the nine parent methods (emergency, alert, critical, error, warning, notice, info, debug, and log). You can check the PSR-3 Logging Standard for more details on how to use the Psr\Log package.
For example, we can build a MailLog
class that will log to the website admin. We won’t cover how to create Drupal modules and settings pages. Here’s a stripped-down example:
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;
class MailLog extends LoggerInterface {
use LoggerTrait;
/**
* The parser will replace context variables inside the log message.
*/
Protected $parser;
public function __construct(LogMessageParserInterface $parser) {
$this->parser = $parser;
}
public function log($level, $message, array $context = array()) {
$to = "developer@example.com";
$from = "admin@example.com";
$subject = "MySite logs";
$headers = "From: {$from}";
$message = $this->parser->parseMessagePlaceholders($message, $context);
mail($to, $subject, $message, $headers);
}
}
Symfony is another well-regarded, full-stack framework. While framework components can be used separately, this section assumes you are using the standard edition for your application, as indicated in the installation docs. The standard edition also uses the Monolog package to handle logging.
Depending on your environment, Monolog is configured as a service in app/config/config_dev.yml
or app/config/config_prod.yml
. In the development environment, you’ll see a section with the following content (note that the level is set to debug, which can be quite verbose).
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
If you look at config_prod.yml
, you'll see a different handler and action_level are used.
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
The FingersCrossedHandler
class from Monolog stores all the messages for a request in a buffer and only logs them if a message reaches the action_level
specified. For example, if a request generates six INFO-level messages, none will be logged to the secondary handler (such as syslog or a file). However, if one of those messages is ERROR-level (or higher), then all six messages will be logged to the secondary handler. This helps keep your production logs from becoming cluttered with log messages you aren’t interested in, while still allowing you to capture data for troubleshooting issues.
Monolog service configuration includes specifying a logging format as a console service in app/config/services.yml
. Below is an example of errors logged to the console. The formatting includes placeholders for passed arguments.
monolog:
my_formatter:
class: Symfony\Bridge\Monolog\Formatter\ConsoleFormatter
Arguments:
- "[%%datetime%%] %%start_tag%%%%message%%%%end_tag%% (%%level_name%%) %%context%% %%extra%%\n"
For a production environment, a typical Symfony app will use Monolog to save messages to app/logs/prod.log. Similarly, if you’re in a dev environment, the log file is app/logs/dev.log.
To log a message, use dependency injection in your controller method.
Use Psr\Log\LoggerInterface;
public function indexAction(LoggerInterface $logger) {
$logger->info('User logged in', ['user' => $username]);
}
In your log file, you’ll find a line with:
[2015-09-24 23:05:42] app.INFO: User logged in {"user":"janeDoe"}
You can also use different severity levels.
// email validation failed
$logger->info('Invalid email supplied for registration', ['email' => $email]);
// could not write a file
$logger->error('Can not save file');
Last updated: December 2022