Как проверить лог php ошибок

(PHP 4, PHP 5, PHP 7, PHP 8)

error_logОтправляет сообщение об ошибке заданному обработчику ошибок

Описание

error_log(
    string $message,
    int $message_type = 0,
    ?string $destination = null,
    ?string $additional_headers = null
): bool

Список параметров

message

Сообщение об ошибке, которое должно быть логировано.

message_type

Определяет куда отправлять ошибку.
Возможны следующие значения:

Типы журналов error_log()

0 Сообщение message отправляется в системный регистратор PHP, используя
механизм логирования операционной системы, или файл, в зависимости от значения директивы
error_log
в конфигурационном файле. Это значение по умолчанию.
1 Сообщение message отправляется электронной почтой на адрес, установленный в параметре
destination. Это единственный тип сообщения, где используется четвёртый параметр
additional_headers.
2 Больше не используется.
3 message применяется к указанному в
destination файлу. Перенос строки автоматически не добавляется в конец
message.
4 Сообщение message отправляется напрямую в обработчик
логера SAPI.
destination

Назначение. Устанавливается в зависимости от параметра
message_type.

additional_headers

Дополнительные заголовки. Используется, когда значение параметра message_type
1.
Данный тип сообщения использует ту же внутреннюю функцию, что и
mail().

Возвращаемые значения

Возвращает true в случае успешного выполнения или false в случае возникновения ошибки.
Если message_type равен нулю, функция всегда возвращает true,
независимо от того, может ли ошибка логироваться или нет.

Список изменений

Версия Описание
8.0.0 Параметр destination и
additional_headers теперь допускают значение null.

Примеры

Пример #1 Примеры использования error_log()


<?php
// Отправляет уведомление посредством серверного лога, если мы не можем
// подключиться к базе данных.
if (!Ora_Logon($username, $password)) {
error_log("База данных Oracle недоступна!", 0);
}
// Уведомить администратора по электронной почте, если невозможно выделить ресурсы для FOO
if (!($foo = allocate_new_foo())) {
error_log("Большая проблема, мы выпали из FOO!", 1,
"operator@example.com");
}
// другой способ вызвать error_log():
error_log("Вы ошиблись!", 3, "/var/tmp/my-errors.log");
?>

Примечания

Внимание

error_log() не является бинарно-безопасной функцией. message обрезается по null-символу.

Подсказка

message не должен содержать null-символ. Учтите, что message может передаваться в файл, по почте, в syslog и т.д. Используйте подходящую преобразующую или экранирующую функцию, base64_encode(), rawurlencode() или addslashes() перед вызовом error_log().

kevindougans at gmail dot com

13 years ago


Advice to novices: This function works great along with "tail" which is a unix command to watch a log file live. There are versions of Tail for Windows too, like Tail for Win32 or Kiwi Log Viewer.

Using both error_log() and tail to view the php_error.log you can debug code without having to worry so much about printing debug messages to the screen and who they might be seen by.

Further Note: This works even better when you have two monitors setup. One for your browser and IDE and the other for viewing the log files update live as you go.


Sion

4 years ago


DO NOT try to output TOO LARGE texts in the error_log();

if you try to output massive amounts of texts it will either cut of the text at about 8ooo characters (for reasonable massive strings, < 32 K characters) or (for insanely massive strings, about 1.6 million characters) totally crash without even throwing an error or anything (I even put it in a try/catch without getting any result from the catch).

I had this problem when I tried to debug a response from a wp_remote_get(); all of my error_log() worked as they should, except for ONE of them... (-_-)
After about a day of debugging I finally found out why & that's why I type this.

Apparently the response contained a body with over 1.6 million chars (or bytes? (whatever strlen() returns)).

If you have a string of unknown length, use this:
$start_index = 0;
$end_index = 8000;
error_log( substr( $output_text , $start_index , $end_index ) );


frank at booksku dot com

16 years ago


Beware!  If multiple scripts share the same log file, but run as different users, whichever script logs an error first owns the file, and calls to error_log() run as a different user will fail *silently*!

Nothing more frustrating than trying to figure out why all your error_log calls aren't actually writing, than to find it was due to a *silent* permission denied error!


i dot buttinoni at intandtel dot com

15 years ago


Be carefull. Unexpected PHP dies when 2GByte of file log reached (on systems having upper file size limit).
A work aorund is rotate logs :)

php at kennel17 dot NOSPAM dot co dot uk

17 years ago


It appears that the system log = stderr if you are running PHP from the command line, and that often stderr = stdout.  This means that if you are using a custom error to both display the error and log it to syslog, then a command-line user will see the same error reported twice.

Anonymous

20 years ago


when using error_log to send email, not all elements of an extra_headers string are handled the same way.  "From: " and "Reply-To: " header values will replace the default header values. "Subject: " header values won't: they are *added* to the mail header but don't replace the default, leading to mail messages with two Subject fields.

<?php

error_log

("sometext", 1, "zigzag@my.domain",
 
"Subject: FoonFrom: Rizzlas@my.domainn");?>

---------------%<-----------------------
To: zigzag@my.domain
Envelope-to: zigzag@my.domain
Date: Fri, 28 Mar 2003 13:29:02 -0500
From: Rizzlas@my.domain
Subject: PHP error_log message
Subject: Foo
Delivery-date: Fri, 28 Mar 2003 13:29:03 -0500

sometext
---------------%<---------------------

quoth the docs: "This message type uses the same internal function as mail() does." 

mail() will also fail to set a Subject field based on extra_header data - instead it takes a seperate argument to specify a "Subject: " string.

php v.4.2.3, SunOS 5.8


russ at russtanner dot com

3 years ago


You can easily filter messages sent to error_log() using "tail" and "grep" on *nix systems. This makes monitoring debug messages easy to see during development.

Be sure to "tag" your error message with a unique string so you can filter it using "grep":

In your code:

error_log("DevSys1 - FirstName: $FirstName - LastName: $Lastname");

On your command line:

tail -f /var/log/httpd/error_log | grep DevSys1

In this example, we pipe apache log output to grep (STDIN) which filters it for you only showing messages that contain "DevSys1".

The "-f" option means "follow" which streams all new log entries to your terminal or to any piped command that follows, in this case "grep".


Matthew Swift

3 years ago


Relative paths are accepted as the destination of message_type 3, but beware that the root directory is determined by the context of the call to error_log(), which can change, so that one instance of error_log () in your code can lead to the creation of multiple log files in different locations.

In a WordPress context, the root directory will be the site's root in many cases, but it will be /wp-admin/ for AJAX calls, and a plugin's directory in other cases. If you want all your output to go to one file, use an absolute path.


paul dot chubb at abs dot gov dot au

14 years ago


When logging to apache on windows, both error_log and also trigger_error result in an apache status of error on the front of the message. This is bad if all you want to do is log information. However you can simply log to stderr however you will have to do all message assembly:

LogToApache($Message) {
        $stderr = fopen('php://stderr', 'w');
        fwrite($stderr,$Message);
        fclose($stderr);
}


SJL

15 years ago


"It appears that the system log = stderr if you are running PHP from the command line"

Actually, it seems that PHP logs to stderr if it can't write to the log file. Command line PHP falls back to stderr because the log file is (usually) only writable by the webserver.


stepheneliotdewey at GmailDotCom

15 years ago


Note that since typical email is unencrypted, sending data about your errors over email using this function could be considered a security risk. How much of a risk it is depends on how much and what type of information you are sending, but the mere act of sending an email when something happens (even if it cannot be read) could itself imply to a sophisticated hacker observing your site over time that they have managed to cause an error.

Of course, security through obscurity is the weakest kind of security, as most open source supporters will agree. This is just something that you should keep in mind.

And of course, whatever you do, make sure that such emails don't contain sensitive user data.


p dot lhonorey at nospam-laposte dot net

16 years ago


Hi !

Another trick to post "HTML" mail body. Just add "Content-Type: text/html; charset=ISO-8859-1" into extra_header string. Of course you can set charset according to your country or Env or content.

EG: Error_log("<html><h2>stuff</h2></html>",1,"eat@joe.com","subject  :lunchnContent-Type: text/html; charset=ISO-8859-1");

Enjoy !


eguvenc at gmail dot com

14 years ago


<?php

//Multiline error log class

// ersin güvenç 2008 eguvenc@gmail.com

//For break use "n" instead 'n'
Class log {

 
//

 
const USER_ERROR_DIR = '/home/site/error_log/Site_User_errors.log';

  const
GENERAL_ERROR_DIR = '/home/site/error_log/Site_General_errors.log';
/*

   User Errors...

  */

   
public function user($msg,$username)

    {

   
$date = date('d.m.Y h:i:s');

   
$log = $msg."   |  Date:  ".$date."  |  User:  ".$username."n";

   
error_log($log, 3, self::USER_ERROR_DIR);

    }

   
/*

   General Errors...

  */

   
public function general($msg)

    {

   
$date = date('d.m.Y h:i:s');

   
$log = $msg."   |  Date:  ".$date."n";

   
error_log($msg."   |  Tarih:  ".$date, 3, self::GENERAL_ERROR_DIR);

    }

}

$log = new log();

$log->user($msg,$username); //use for user errors

//$log->general($msg); //use for general errors

?>

franz at fholzinger dot com

18 years ago


In the case of missing your entries in the error_log file:
When you use error_log in a script that does not produce any output, which means that you cannot see anything during the execution of the script, and when you wonder why there are no error_log entries produced in your error_log file, the reasons can be:
- you did not configure error_log output in php.ini
- the script has a syntax error and did therefore not execute

daniel dot fukuda at gmail dot com

13 years ago


If you have a problem with log file permission *silently*
it's best to leave error_log directive unset so errors will be written in your Apache log file for current VirtualHost.

Anonymous

2 years ago


Depending on the error, you may also want to add an error 500 header, and a message for the user:

$message =  'Description of the error.';
error_log($message);
header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error', true, 500);
exit($message);


Robert Chapin

4 years ago


When error_log() unexpectedly uses stdout, you should check if the php.ini value for error_log is empty in your CLI environment.  Something as simple as this might restore expected behavior:

<?php ini_set('error_log', 'error_log'); ?>


kazezb at nospam dot carleton dot edu

17 years ago


It appears that error_log() only logs the first line of multi-line log messages. To log a multi-line message, either log each line individually or write the message to another file.

Anonymous

13 years ago


After scouring the internet for getting event logging to
work in syslog on Windows 2003, I found the following
from this post and was able to successfully get Windows
Event Viewer to log PHP errors/notices:

http://forums.iis.net/p/1159662/1912015.aspx#1913338

   1. Copy the PHP 5 binaries to "C:php".
   2. Right-click My Computer and select Properties to bring
up the Computer Properties dialog. Switch to the Advanced
tab and click Environment Variables. Find the system
environment variable PATH, edit it and add ";C:php"
(without the quotes) to the end.
   3. Make sure that the configuration file "php.ini" resides
in the directory "C:php" and contains the correct path
settings.
   4. DELETE any old "php.ini" files from "C:WINDOWS"
and other directories.
   5. Open REGEDIT, navigate to the key
"HKLMSOFTWAREPHP" and DELETE the string value
"IniFilePath" from there. It is outdated and no longer
necessary!
   6. Modify NTFS security permissions of the directory
"C:php" to give Read and Execute permissions to (1) the
IIS Guest Account and (2) the group IIS_WPG.
   7. Modify NTFS security permissions of the directories
"C:phpsession" and "C:phpupload" to give additional
Modify permissions to (1) the IIS Guest Account and (2)
the group IIS_WPG.
   8. Navigate to the registry key
"HKLMSYSTEMCurrentControlSetServicesEventlog
Application" and edit the value "CustomSD" there. Find
the substring "(D;;0xf0007;;;BG)" which Denies access to
the application event log for Builtin Guest accounts (like
the IIS Web User account) and replace this substring with
"(A;;0x3;;;BG)" which allows read and write access. Please
pay attention to leave the rest of the security string intact.
Damaging this value can have dangerous effects!
   9. Create or update the registry key
"HKLMSYSTEMCurrentControlSetServicesEventlogApplication
PHP-5.2.0" (adapt the last to your version part
if necessary) with the following values:

          * "EventMessageFile" (REG_EXPAND_SZ) = "C:phpphp5ts.dll"

          * "TypesSupported" (REG_DWORD) = 7


PHP logs are not just about errors. You can use logs to track the performance of API calls and function calls, or to count the occurrence of significant events in your applications (e.g., logins, signups, and downloads). Whether you’re operating a microservices architecture or a monolith, implementing a comprehensive PHP logging strategy will allow you to track critical changes in your applications and optimize their performance.

PHP and its available logging libraries give you many options for where to send and store your logs. As you’ll see in this post, storing your PHP logs in a central file is simple and gives you the greatest flexibility for processing and analyzing your logs later on. When you use a specialized tool to tail your log file and forward your logs to a central log management solution, your application code isn’t burdened with the overhead of buffering logs and handling network errors.

In this post, you’ll learn how to:

  • configure the PHP system logger to automatically log errors
  • use native PHP functions to log custom errors
  • expand your logging capabilities with the Monolog logging library
  • capture PHP exceptions and arbitrary events

How PHP creates logs

The PHP system logger creates logs automatically when the execution of your code produces an error. Additionally, you can create logs by calling PHP’s logging functions as you need to log custom errors and arbitrary events in your application. In this section, we’ll look at how logs are created and routed by each of these mechanisms.

The PHP system logger

You can configure the PHP system logger by using the error_reporting directive in PHP’s configuration file, php.ini, to designate the types of errors PHP will automatically log. This directive uses a set of predefined constants and bitwise operators to express what types of events to include and exclude from logs. For example, you would use this directive to log all errors:

PHP’s display_errors configuration directive gives you the option of displaying log messages in the browser. In a production environment, you should always set display_errors to Off for security reasons. However, in a development environment, you might want to display warnings and errors directly in the browser so developers can easily see information about the application’s status.

PHP displays warning messages in the browser.

The PHP system logger routes logs in different ways depending on the value of the error_log configuration directive in php.ini:

  • If error_log names a file, PHP writes its logs to that file.
  • If error_log is set to syslog, PHP sends logs to the OS logger. This is usually syslog or the newer rsyslog (which implements the syslog protocol) on Linux, or Event Log on Windows.
  • If error_log is unset, PHP creates logs using the Server API (SAPI). The SAPI used depends on your platform. As an example, a LAMP setup uses apache as a SAPI, and logs are written to Apache’s error log.

To maximize the logging data available and to give yourself options for centralizing, processing, and analyzing your logs later, add the following configuration to your php.ini. (In PHP .ini files, a semicolon indicates the start of a comment.)

; Log all errors
error_reporting = E_ALL
; Don't display any errors in the browser
display_errors = Off
; Write all logs to this file:
error_log = my_file.log

Now your PHP logs are written to the my_file.log file we specified in the error_log directive above.

PHP’s logging functions

You can log any event you choose by explicitly calling PHP’s error_log() or syslog() function within your code. These functions create logs containing the message string you provide. The syslog() function will use the configuration in your rsyslog.conf file to write log messages. The error_log() function routes it to the file specified by the error_log configuration directive. The following example sends a message to the PHP system logger:

<?php
error_log("An error has occurred.");

The PHP system logger automatically adds a timestamp to each log, so each time this code runs, a line like the one below will be appended to our my_file.log file:

[15-Apr-2019 20:25:11 UTC] An error has occurred.

If no value is provided for the error_log configuration item in php.ini, logs are generated by the SAPI, and their format depends on the SAPI in use. For example, on a LAMP server with Apache’s default logging configuration, the example code shown above adds the following line to Apache’s error log (e.g., /var/log/apache2/error.log):

[Mon Apr 15 20:25:11.950260 2019] [php7:notice] [pid 26154] [client 123.123.123.123:57728] An error has occurred.

PHP’s error_log() and syslog() functions provide more options for configuring where your logs are sent. For example, when you call error_log(), you can provide a path to the file where the message should be logged that is different from the one defined by the error_log directive. For information about the advanced routing capabilities of PHP’s error_log() and syslog() functions, see the PHP documentation. In this article, we will focus on logging to a file, since this gives you the ability to forward and process your logs, as we described above.

Centralizing and storing your logs

So far, we’ve looked at PHP’s system logger and native logging functions. These mechanisms don’t provide much flexibility when you want to customize how your logs are formatted or routed, but they make it easy to get started writing logs to a local file. You can also process your logs with an external service. Consider a strategy that combines writing logs to a local file and forwarding them to an external service to aggregate, analyze, and monitor your logs. This way, you can offload log processing and long-term storage and aggregate logs from all your hosts in a single platform. You can troubleshoot an incident much more efficiently if you don’t have to manually log into each of your servers to view logs.

When you use a log management and analytics platform like Datadog, we recommend using JSON-formatted logs. This makes it easy to process, search, filter, and monitor your logs. To make it easy to create JSON logs and route them to a file, we recommend that you use the Monolog logging library. In the next section we will cover how to use the Monolog library to format your logs as JSON and automatically add metadata to all your logs.

filter-php-logs-by-channel.png

The Monolog logging library

Monolog is one of the most widely used PHP logging libraries. It provides all the functionality of PHP’s native logging functions, and makes it easy to create PHP logs in different formats. You can easily differentiate logs within a single application by categorizing them in channels, and you can send your logs to databases, message queues, and external collaboration tools.

Monolog is available in the Packagist repository, and the examples in this section assume you’ve installed Monolog using Composer. If you already have Composer installed, all you need to do is issue this command to add Monolog to your project:

composer require monolog/monolog

In this section, we’ll look at some of the Monolog features that you can use to enhance your PHP logging. We’ll show you how to:

  • create and organize logs using loggers and channels
  • route logs using Monolog handlers
  • use formatters to create JSON-formatted logs
  • use processors to log uniform data
  • assign appropriate log levels to events of different types

Loggers and channels

To start using Monolog, you need to create a logger—an instance of Monolog’s Logger class:

<?php
// Load dependencies required by Composer (including Monolog):
require_once "vendor/autoload.php";
// Use Monolog's `Logger` namespace:
use MonologLogger;
$logger = new Logger('transactions');

This code creates a logger object named $logger and gives it a channel name of transactions.

Monolog uses channels to differentiate logs that have been routed to the same destination but that contain data about different categories of events. Each time you create a logger, you need to provide a channel name. You can create multiple loggers within your application and use each one to log events related to a category of activity, such as purchases or user accounts. Because each logger’s channel value is associated with the logs it creates (as an object within a JSON-formatted log, for example), channels give you more latitude to use metadata to differentiate your logs.

Handlers

Monolog’s handlers determine how PHP will act on the log messages sent to each logger. The StreamHandler is Monolog’s basic means of writing logs to a file. Numerous other handlers are available so you can easily send logs to the service of your choice.

Once you’ve created a logger, you use it by defining one or more handlers and pushing them onto the Logger object. For each handler you create, you provide information about how it should route the log (e.g., a filename), and a minimum log level at which the handler should be triggered. By pushing multiple handlers onto a logger, you can use it to log different types of events to different destinations.

The following code illustrates pushing a handler on to the logger ($logger) we created above. It then calls Monolog’s info method to trigger the handler and log a message to the file /var/log/monolog/php.log:

<?php
require_once "vendor/autoload.php";
use MonologLogger;
use MonologHandlerStreamHandler;

$logger = new Logger('transactions');

// Declare a new handler and store it in the $logstream variable
// This handler will be triggered by events of log level INFO and above
$logstream = new StreamHandler('/var/log/monolog/php.log', Logger::INFO);

// Push the $logstream handler onto the Logger object
$logger->pushHandler($logstream);

$logger->info('A notable event has occurred.');

This logger creates logs in Monolog’s default format, but it’s easy to make Monolog structure your logs in a useful format. In the next sections of this post, we’ll look at the benefits you gain when you use the JsonFormatter to create your logs.

Formatters

Monolog allows you to define a custom log format, or you can choose an existing formatter to determine how your log messages appear. Monolog formatters are available to meet different logging requirements, and you can choose the one that best suits your needs.

Monolog’s JSONFormatter helps you structure your log data and lets you include any arbitrary data you require. This can make it easy to store multi-line errors in a single log line. You can also store information unique to each session by logging the PHP session array. JSON-formatted logs are easy for log management solutions to parse, so you can search, filter, and analyze your application’s data to track errors, usage, and performance trends.

The sample code below creates JSON logs with a channel value of transactions.

<?php
require_once "vendor/autoload.php";
use MonologLogger;
use MonologHandlerStreamHandler;
use MonologFormatterJsonFormatter;

$logger = new Logger('transactions');

$logstream = new StreamHandler('/var/log/monolog/php.log', Logger::INFO);

// Apply Monolog's built-in JsonFormatter
$logstream->setFormatter(new JsonFormatter());

$logger->pushHandler($logstream);

$logger->info('Transaction complete');

When PHP executes this code, a log is added to the specified file—/var/log/monolog/php.log—that looks like this:

{
	"message": "Transaction complete",
	"context": [],
	"level": 200,
	"level_name": "INFO",
	"channel": "transactions",
	"datetime": {
		"date": "2019-02-14 17:19:11.332526",
		"timezone_type": 3,
		"timezone": "UTC"
	},
	"extra": []

}

To isolate these logs from those created by other loggers in your application, you can use a log management solution to filter your data and view only logs from the transactions channel.

Notice that Monolog automatically adds two arrays to this log—context and extra. You can use these arrays to enrich your logs and provide more information about the activity you’re logging. In the next section, we’ll look at how to create and populate these arrays.

Processors

The context and extra arrays give you options for easily adding metadata to each log. You can use them to store any data that’s useful to you. We recommend using context to log the high-cardinality data that varies between sessions, and extra to log global metadata that’s common to all requests. In this section we’ll illustrate how you can use the two arrays to store different kinds of data.

You can use a Monolog processor to define metadata to be added to each log’s context and extra arrays. Processors make it easy to include the same information consistently across all the logs created by a single logger. The following example defines a Monolog processor to include context and extra data:

<?php
require_once "vendor/autoload.php";
use MonologLogger;
use MonologHandlerStreamHandler;
use MonologFormatterJsonFormatter;

$logger = new Logger('transactions');

$logstream = new StreamHandler('/var/log/monolog/php.log', Logger::INFO);
$logstream->setFormatter(new JsonFormatter());

$logger->pushHandler($logstream);

$logger->pushProcessor(function ($record) {
        $record['extra']['env'] = 'staging';
        $record['extra']['version'] = '1.1';
        $record['context'] = array('user' => $_SESSION["user"], 'customerID' => $_SESSION["customerID"], 'checkoutValue' => $_SESSION["checkoutValue"], 'sku_array' => $_SESSION["sku"]);
        return $record;
});

$logger->info('Transaction complete');

In the resulting log, both the context and extra arrays are populated.

{
	"message": "Transaction complete",
	"context": {
		"user": "user@example.com",
		"customerID": 12102,
		"checkoutValue": "17.39",
		"sku_array": [468, 116]
	},
	"level": 200,
	"level_name": "INFO",
	"channel": "transactions",
	"datetime": {
		"date": "2019-04-16 15:46:16.531986",
		"timezone_type": 3,
		"timezone": "UTC"
	},
	"extra": {
		"env": "staging",
		"version": "1.1"
	}
}

You can also pass context array data as an argument to the method you use to create the log. The example below illustrates passing the log message and context data in a single call:

$logger->info('Transaction complete', array('user' => $_SESSION["user"], 'customerID' => $_SESSION["customerID"], 'checkoutValue' => $_SESSION["checkoutValue"], 'sku_array' => $_SESSION["sku"]));

Log levels

PHP’s error_log() function assumes all messages describe errors within your application, but Monolog allows you to log other types of PHP events as well. Monolog supports eight different log levels—the same ones defined in the syslog protocol—so that each log carries metadata that conveys the severity of the event being logged.

When you call the Monolog function to create a log, you specify the log’s level. This way, you
can log the types of events (e.g., debug, error, or alert) you need to know about. For example, to log an event whose log level is error, you would call the logger’s error method as shown below:

$logger->error('Transaction failed');

Of course, you don’t want to have to revise your code during an outage to log debug messages. Instead you can configure your application to log events of all levels, and use a log management solution to filter logs downstream to isolate certain kinds of events.

Expanding your logging coverage

Because PHP logging is flexible, you have options in how much to log and how to handle your logs. In this section, we’ll look at how PHP exceptions work and how to capture them. We’ll also show you how to expand your logging to capture useful information about different types of events—not just errors.

Centralize and organize your PHP logging for easier analysis with Datadog.

Catch and log exceptions

Like many other languages, PHP uses exceptions to accommodate unintended behavior by your application. An exception is an object PHP creates (or throws) when the execution of your PHP script reaches an unintended state.

Exceptions should be caught when they occur—governed by code that addresses the exceptional case. The exception handler—the code that catches the exception—defines PHP’s behavior and output when faced with an exception. PHP does not automatically log exceptions when they are thrown, so you should create exception handlers that log useful information about the exception.

The code below shows an example of a basic exception handling strategy. The checkUsername function validates the length of the string passed to it, then throws exceptions under certain conditions. The function is called from within a try block, and a catch block handles any exceptions and logs the details.

<?php
require_once "vendor/autoload.php";
use MonologLogger;
use MonologHandlerStreamHandler;
use MonologFormatterJsonFormatter;

$logger = new Logger('signups');

$logstream = new StreamHandler('/var/log/monolog/php.log', Logger::INFO);
$logstream->setFormatter(new JsonFormatter());

$logger->pushHandler($logstream);
  
function checkUsername($username) {
    if (strlen($username) < 4) {
        throw new Exception("Username $username is not long enough.");
    } else if (strlen($username) > 12) {
        throw new Exception("Username $username is too long.");
    }
    // $username is OK
}

try {
    checkUsername('me');
} catch (exception $e) {
    $message_string = "{$e->getMessage()} (file: {$e->getFile()}, line: {$e->getLine()})";
    $logger->error($message_string);
}

When PHP throws an exception, it creates an exception object (named $e in the example above) that is available for the exception handler to use. The exception object contains properties, such as the file and lines of code that have caused the unintended state, that describe the state of the application. It also provides methods you can use to access those properties (such as getMessage() in the example above). You can use an exception handler to access the data contained in the exception object and log details of the exception.

The code above will append a line like this one to the file /var/log/monolog/php.log:

{
	"message": "Username me is not long enough. (file: /var/www/html/checkUsername.php, line: 17)",
	"context": [],
	"level": 400,
	"level_name": "ERROR",
	"channel": "signups",
	"datetime": {
		"date": "2019-04-11 20:33:45.500634",
		"timezone_type": 3,
		"timezone": "UTC"
	},
	"extra": []
}

Better than logging only the message returned by the exception’s getMessage() method, you should log the exception object itself. The PHP logging standard that Monolog implements, PSR-3, states that a logged exception must be in the exception element of the context array. To log the whole exception object, change the $logger->error() call in the previous example to look like this instead:

$logger->error("checkUsername failed", array('exception' => $e));

When you log the exception object, all the information it contains is recorded in the log as JSON, as shown in this example log:

{
	"message": "checkUsername failed",
	"context": {
		"exception": {
			"class": "Exception",
			"message": "Username me is not long enough.",
			"code": 0,
			"file": "/var/www/html/checkUsername.php:16"
		}
	},
	"level": 400,
	"level_name": "ERROR",
	"channel": "signups",
	"datetime": {
		"date": "2019-04-24 15:01:17.656613",
		"timezone_type": 3,
		"timezone": "UTC"
	},
	"extra": []
}

If you use a log management service, you can use the exception object’s data to view, filter, and analyze your logs.

Datadog's Log Explorer shows a parsed PHP exception.

Catch unhandled exceptions

If your code doesn’t include a handler for a particular exception, PHP will generate a fatal error and halt execution. To prevent this, you can use PHP’s set_exception_handler() function to define your own default exception handler. This way you can avoid the fatal error caused by an unhandled exception, and you can capture the exception in your logs. The example below uses set_exception_handler() to catch and log any unhandled exceptions.

<?php
require_once "vendor/autoload.php";
use MonologLogger;
use MonologHandlerStreamHandler;
use MonologFormatterJsonFormatter;

$logger = new Logger('signups');

$logstream = new StreamHandler('/var/log/monolog/php.log', Logger::INFO);
$logstream->setFormatter(new JsonFormatter());

$logger->pushHandler($logstream);  
// Define default behavior if an exception isn't caught:
set_exception_handler( function($e) {        
    $uncaught_log = new Logger('uncaught');
    $uncaught_logstream = new StreamHandler('/var/log/monolog/php.log', Logger::ERROR);
    $uncaught_logstream->setFormatter(new JsonFormatter());
    $uncaught_log->pushHandler($uncaught_logstream);
    $uncaught_log->error("Uncaught exception", array('exception' => $e));
});

// Declare an empty class
class myClass {
	// empty
}

// Try to call a non-existent function
try {
    myClass::myFunction();
} catch (Exception $e) {
    $logger->error("Call to myFunction failed", array('exception' => $e));
}

In this code, set_exception_handler() processes the exception thrown when the nonexistent myFunction() is called. It serves as the default exception handler, and will process any uncaught exceptions throughout the script (any of which would otherwise have caused a PHP fatal error).

When this code is executed, it logs an exception like the one below:

{
	"message": "Uncaught exception",
	"context": {
		"exception": {
			"class": "Error",
			"message": "Call to undefined method myClass::myFunction()",
			"Code": 0,
			"file": "/var/www/html/test_exception_handler.php:30"
		}
	},
	"level": 400,
	"level_name": "ERROR",
	"channel": "uncaught",
	"datetime": {
		"date": "2019-04-11 19:20:20.241717",
		"timezone_type": 3,
		"timezone": "UTC"
	},
	"extra": []
}

Note that it does not log the Call to myFunction failed error from the catch block. The code in the catch block would execute if myFunction() threw an exception, but in this case PHP throws an exception when we try to call the nonexistent myFunction(). Since that exception is uncaught, it gets processed by the function defined in set_exception_handler().

Log events (not just errors)

In addition to the errors the PHP system logger records automatically, you can log custom events such as API calls to and from your application. Logging these events allows you to monitor your application’s performance and usage trends. In an application made up of microservices, pretty much everything will be an API call, and you can add custom logging code around any calls worthy of attention. The example below calculates the response time of an API call, then uses Monolog to log the result.

<?php
require_once "vendor/autoload.php";
use MonologLogger;
use MonologHandlerStreamHandler;
use MonologFormatterJsonFormatter;

$logger = new Logger('APIperformance');
$logstream = new StreamHandler('/var/log/monolog/php.log', Logger::INFO);
$logstream->setFormatter(new JsonFormatter());
$logger->pushHandler($logstream);

function myAPIcall() {
    $curl = curl_init();
    $url = 'http://dummy.restapiexample.com/api/v1/employees';
    curl_setopt($curl, CURLOPT_URL, $url); 
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($curl);
    curl_close($curl);
    return $result;
}

$logger->pushProcessor(function ($record) {
    $record['extra']['env'] = 'staging';
    $record['extra']['version'] = '1.1';
    return $record;
});

$start = microtime(TRUE); // A timestamp before the call
$result = myAPIcall();
$end = microtime(TRUE); // Another timestamp after the call

// Log the call duration as a readable string
// and include the context array
$logger->info("myAPIcall took " . ($end - $start) . " seconds.", array('duration' => ($end - $start)), array('user' => $_SESSION["user"], 'customerID' => $_SESSION["customerID"], 'checkoutValue' => $_SESSION["checkoutValue"], 'sku_array' => $_SESSION["sku"]));

Each time this function runs, your logs collect data on the performance of the API call, which you can visualize in a service like Datadog:

Datadog's Log Explorer graphs duration of API calls from a PHP application.

In addition to logging API calls, you can expand your logging coverage to capture logins and logouts, as well as other user activity such as signups and transactions.

With all your logs aggregated in one place, Datadog’s Log Analytics makes it easy for you to visualize log data. For example, you can see your aggregated log volume, grouped by channel to understand the amount of activity across the different areas of your application.

A Datadog Log Analytics graph shows the volume of logs created in the signups, transactions, and APIperformance channels.

From this view, you can export the graph to a dashboard or click to see individual logs. You can even create a monitor to alert on your log data, so you can automatically be notified of any unusual activity captured in your application logs.

For further information about using Monolog and Datadog, see our documentation.

Do more with your PHP logs

PHP logging offers a lot of flexibility that enables you to capture the right information and make it available for troubleshooting and monitoring. Once you’ve configured your applications to log all the information that might be useful to you, you can send your logs to a monitoring platform for in-depth analysis and collaborative troubleshooting. If you’re not already using Datadog to collect and analyze your logs, you can start with a free, full-featured 14-day trial.

 on
May 06, 2021

The Essential Guide to PHP Error Logging

PHP has been one of the top (if not best) server-side scripting languages in the world for decades. However, let’s be honest – error logging in PHP is not the most straightforward or intuitive. It involves tweaking a few configuration options plus some playing around to get used to. Once you have everything set up and figured out (like you will after reading this post), things seem much easier, and you realize how helpful error logging can turn out to be for your application – from debugging and troubleshooting to monitoring and maintenance. 

And this is why we are covering error logging in PHP in this post. We will start by revisiting the importance of logging errors in your application. We will then explore errors in PHP – their different types, and how they can be output. Next, we will look at all the error logging configurations in PHP, and understand how we can tweak these to our liking, before we see some error logging examples, and explore functions in PHP that allow us to write errors to log files. This post is a complete guide to error logging in PHP.

Here’s an outline of what we’ll be covering so you can easily navigate or skip ahead in the guide: 

  • Importance of Logging Errors 
  • PHP Error Types
  • Where Can PHP Errors be Output
  • Enabling and Configuring Error Reporting in PHP 
  • Logging Errors in PHP 
  • PHP’s Error Logging Functions 
    • error_log()
    • trigger_error()
    • syslog()
    • set_error_handler()
  • Popular PHP Logging Libraries 

Importance of Logging Errors 

Errors in software systems have this terrible reputation of being associated with failing things and breaking functionality. As a result, many of us often fail to recognize the importance of these loud red strings that bring our attention to faults, inconsistencies, and inaccuracies in our code – mistakes that can cost us dearly if allowed to fall through the cracks. Therefore, it is worthwhile for some of us to change our outlook towards error messages – to track, log, and organize them – and embrace their importance. 

There’s a reason developers and organizations build and leverage dedicated logging systems that keep track of errors that arise throughout an application’s lifecycle. These logs provide useful information about what went wrong, when, where, and how it can be fixed. 

For small-scale personal projects, it is common for developers not to feel the need to spend time setting up an effective logging system. This seems plausible because your code and end-user interactions are much more manageable for smaller projects. However, the requirement for effectively logging and maintaining errors and other information grows exponentially as your application scales. For larger applications, catering to thousands of users, it becomes unwieldy to track errors and updates across hundreds of components in real-time. Putting in place a system that can record the status of the very many events that an application’s operation entails allows organizations to maintain a clear record of their performance. This allows for more transparency and therefore ensures that no issues go unnoticed. As a result, this makes your application more reliable, easy to maintain, monitor, and debug.

Now that we are hopefully convinced that error logging is a worthwhile expedition, let us look at the different types of errors in PHP.

PHP Error Types

Broadly, there are five types of errors in PHP:

1. Fatal run-time Errors (E_ERROR) 

These errors typically happen when an operation in your code cannot be performed. This leads to your code exiting. An example of a fatal error would be when you call a function that hasn’t been defined in your code, shown below:

<?php
function foo() {
  echo "Function foo called.";
}
boo(); // undefined function 'boo'
?>

Error output –>

Fatal error: Uncaught Error: Call to undefined function boo() in code/my-php/index.php:5 Stack trace: #0 {main} thrown in code/my-php/index.php on line 5.

2. Warning Errors (E_WARNING)

A warning error is more gentle and less obtrusive in that it does not halt the execution. It presents a friendly reminder of something amiss in your code – a mistake that might not fail things immediately or fail anything at all but suggests a more accurate way of doing things that make your code more foolproof. These warnings can also save developers from issues that might pose a much bigger threat in the future. An example of a warning error would be when you try to include a file in PHP using an incorrect file path, as shown below:

<?php
include('filename.txt'); // arbitrary file that is not present
echo "Hello world";
?>

Error output ->

Warning: include(filename.txt): failed to open stream: No such file or directory in code/my-php/index.php on line 2

3. Parse Errors (E_PARSE)

Parse errors are also known as syntax errors as they arise from syntactical mistakes in your code. These errors are raised during the compilation of your code, making it exit before it runs. A common example of a parse error is missing a semicolon at the end of a code statement, shown below:

<?php
echo Hello world // no quotes or semicolon used
?>

Error output ->

Parse error: syntax error, unexpected 'world' (T_STRING), expecting ',' or ';' in code/my-php/index.php on line 2.

4. Notice Errors (E_NOTICE)

Notice errors are minor errors that are encountered during run-time, and just like warning errors, do not halt the execution. They usually occur when the script is attempting to access an undefined variable, for example, as shown below:

<?php
$a = 1;
$c = $a + $b; // undefined variable $b
?>

Error output ->

Notice: Undefined variable: b in code/my-php/index.php on line 3

5. User Errors (E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE) 

User errors are user-defined, i.e., present a custom user-generated message raised explicitly from the code to capture a specific condition. These errors are manually raised by developers using the trigger_error function instead of the PHP engine. They are further classified as fatal, warning, and notice errors, but we’ll group them all as user errors for simplicity.

Where can PHP Errors be Output?

There are two primary places where we can have our errors presented in PHP – through inline errors and dedicated error log files.

Inline Errors 

Inline errors are those that show up on your webpage in the browser or your terminal via STDOUT in a command-line environment. These errors prove to be quite useful during development – for developers to debug their code, fix issues, and get information about the overall execution. Below is an example of what these errors usually look like in the browser:

undefined

Though this proves to be super helpful for developers, you should be very careful in ensuring that these errors are not output when your application goes into production – for two reasons – end-user experience and security. You can toggle the displaying of these errors using the display_error directive in your system’s configuration. We’ll dive deeper into this in the next section.

Error Log Files 

Inline errors are not persistent in memory, i.e., they are not saved anywhere and are only viewed as long as the browser or terminal session is alive. Additionally, you’ll only want to have them in a development environment. Conversely, as the theme of this post suggests, logging your errors is the more intelligent and more systematic approach towards maintaining large-scale applications. These are persistent in memory and provide information about the operation of your application across multiple components in one place, making it easier for monitoring and troubleshooting.

PHP, therefore, allows you to direct all your errors to specific log files; these files store timestamps, error stack traces, custom messages, and other helpful information about the source of the error and how to fix it. You can specify the path of your custom log file using the error_log directive of your system configuration. Here’s an example of an error log file:

undefined

You can also choose to have your errors logged to the system’s log file, usually located in – /var/log/syslog. We’ll cover this in a later section in the post.

Now let’s look at how we can configure where and how we want our errors logged.

Enabling and Configuring Error Reporting in PHP 

As we discussed previously, logging in PHP is slightly less straightforward than other languages and frameworks. You might have to tweak a few options in configuration files to customize logging patterns. For example, when you install PHP on your machine, the initial configuration comes with some aspects of error logging disabled. This differs from system to system, and therefore, you should manually check these settings before getting started.

Logging Configuration Options in php.ini File

The configuration options are in the php.ini file. This file is read when PHP starts up and allows developers to play around with PHP’s functionality. Usually, this file can be found somewhere in the /etc/php directory on most Linux systems. 

There are a bunch of directives (options) pertaining to the logging of errors in PHP that we can configure in this php.ini file:

  • display_errors (default: 1)

Display errors are the inline errors we previously looked at. This directive can be used to good effect during development to output PHP error messages to the browser or terminal. However, for applications in production, you should likely turn this off to save your users from a poor website experience due to obscure error messages. Not only that, but this also protects you from exposing valuable information about the internals of your application as a security measure.

  • display_startup_errors (default: 0)

As the name suggests, this is to output any errors that take place when PHP starts up. These usually do not provide any valuable information about your application specifically, and therefore need not be turned on.

  • log_errors (default: 0)

This directive allows you to toggle the logging of errors to the specified path (in the next directive). Because this is turned off by default, it would be advisable to toggle this to 1 for recording your application’s error messages in a log file.

  • error_log (default: 0)

This directive allows you to specify the path of your log file. You can also set this to “syslog” for directing all your error log messages to the system log. 

  • error_reporting (default: null)

The error_reporting directive allows you to customize which error levels you want reported, and which you are okay with going unreported. For example, you can use the directive as shown below to have all errors reported: error_reporting = E_ALL

  • track_errors (default: 0)

This directive allows you to access the last raised error message in the $php_errormsg global variable in your code and can keep track of errors across your whole project.

After making changes to your php.ini file, you will need to restart the server for the changes to take effect.

The ini_set() Function

However, if you are unable to locate the php.ini file, or prefer overriding your project’s global configuration options, there is also an option to update these directives using the ini_set() function in your PHP code. For example, the below code can be used for customizing error reporting in your project:

<?php

// enabling error logging
ini_set('log_errors', 1);  

// Customize reporting of errors
ini_set('error_reporting', E_WARNING | E_ERROR | E_PARSE | E_NOTICE);

// specify error log file path
ini_set('error_log', '/tmp/my-logs.log');

?>

The error_reporting() Function

One can also modify the error_reporting configuration option using the error_reporting() function from inside your code during run-time. As in the ini_set function, you can use bitwise operators like OR (|), AND (&), NOT (~), etc., when specifying the error levels to be reported. Below are a few examples of how this function can be used.

// Report only selected kinds of errors
error_reporting(E_ERROR | E_PARSE | E_NOTICE);

or

// Report all errors except E_WARNING
error_reporting(E_ALL & ~E_WARNING);

Now that we have got the system configurations and overall setup out of the way, let’s look at an example of how errors in your project code can be logged to files on your system.

Logging Errors in PHP

First, we will override the logging configuration parameters using the ini_set() function to enable error logging and specify the log file’s path. Then we’ll write some erroneous code to have PHP raise an error that we would like to have logged.

<?php
  ini_set('log_errors', 1); // enabling error logging
  ini_set('error_log', '/path/my-error-file.log'); // specifying log file path

  echo $b; // undefined variable should raise error
?>

After opening the web page on our browser, let’s open the ‘my-error-file.log’ file to see if the error message was logged. Here is the log file output:

[28-Feb-2021 13:34:36 UTC] PHP Notice:  Undefined variable: b in code/my-php/index.php on line 5

As you can see, our notice error was logged with a timestamp. As our code encounters more and more errors, this file will keep getting populated with corresponding timestamps. Note that we haven’t explicitly turned off display_errors, so these error messages are likely to be logged to the browser web page – something you might want to avoid during production. 

This was an example of capturing errors raised by PHP in log files. Now let’s look at how we can raise and log custom error messages for our application.

PHP’s Error Logging Functions

So far, we looked at errors raised by PHP – errors about your code execution. However, oftentimes you would want to also capture custom errors, with custom error messages specific to the functioning of your application. These so-called errors might not necessarily fail your code or halt its execution, but can indicate conditions characterized as erroneous and noteworthy for your application. These can act as indications to the organization about anomalous behavior that the team might want to look into and fix.

To facilitate this, PHP provides a set of functions that we can use to actively log errors in our code.

error_log()

The most common method for actively logging errors is the error_log() function. This sends a string argument for the error message to the log file.

error_log (string $message, int $message_type=0, string $destination=?, string $extra_headers=?) : bool

It also takes many other parameters to send error messages over email or specific log files. However, for the sake of simplicity, we won’t be covering that here. 

The interesting thing about this function is it logs your error message to the file specified in the configuration (or to the system log), regardless of the value of the log_errors directive. Let’s take a very simple example of logging an error when a specific condition in our code is met.

<?php

ini_set('error_log', '/path/my-error-file.log');
$a = 5;
$b = 10;

$c = $a + $b;

if ($c < 20) {
error_log("Sum is less than 20."); // logging custom error message
}

?>

Here is the output of the log file:

[28-Feb-2021 13:31:50 UTC] Sum is less than 20

Similarly, you can also log the values of variables in your code to provide additional context about your errors. Let’s see an example for that:

<?php
  ini_set('error_log', '/path/my-error-file.log');

  $languagesArray = array("PHP", "Python", "Node.js");
  error_log("Lorem ipsum. Array data -> ".print_r($languagesArray, true));
?>

Here’s the output of the log file ->

[28-Feb-2021 13:49:28 UTC] Lorem ipsum. Array data -> Array

(

   [0] => PHP

   [1] => Python

   [2] => Node.js

)

trigger_error()

The trigger_error() function can be used to raise a user-defined error/warning/notice. You can also specify the error type based on the condition. This allows you to customize its reporting and other behavior – for example, using an error type of E_USER_ERROR. We can cause the code to exit immediately compared to an E_USER_WARNING error.

trigger_error (string $error_msg, int $error_type=E_USER_NOTICE) : bool

The difference between trigger_error and error_log is that the former only generates a user error and depends on your system’s logging configurations to handle this error message (whether displayed or logged). error_log, on the other hand, will log your message regardless of the system’s configuration.

Here is the code for the same example we saw previously:

<?php
ini_set('log_errors', 1); // enabling error logging
ini_set('error_log', '/path/my-error-file.log'); // specifying log file path

$a = 5;
$b = 10;
$c = $a + $b;

if ($c < 20) {
trigger_error("Sum is less than 20.", E_USER_ERROR);
      echo "This will not be printed!";
}

?>

This adds a similar log entry to what we saw previously, but with an error level, plus the conventional error source information (log file output below):

[01-Mar-2021 01:16:56 UTC] PHP Fatal error:  Sum is less than 20. in code/my-php/index.php on line 10

syslog()

You can also choose to directly send an error message to the system’s log using the syslog() function.

syslog (int $priority, string $message) : bool

The first argument is the error’s priority level – LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_ALERT, LOG_EMERG, etc. (more about it here).  The second argument is the actual message’s text. This is how the function can be used:

<?php
// opening logger connection
openlog('myApp', LOG_CONS | LOG_NDELAY | LOG_PID, LOG_USER | LOG_PERROR
); // more information about params in documentation

syslog(LOG_WARNING, "My error message!");

closelog();
?>

This should reflect in your system’s logger (usually in /var/log/syslog) as:

Mar 1 13:27:15 zsh php: My error message! 

set_error_handler()

To customize the handling of all the user-defined errors throughout your code, PHP allows you to specify a custom error handler function to override the default handling of errors. This makes it easy for organizations to modify how they want their errors logged, the corresponding error messages send method, and much more. The set_error_handler() function helps with this.

set_error_handler (callable $error_handler, int $error_types=E_ALL | E_STRICT) : mixed

It takes as an argument our custom error handler function, which will define the handling of our errors, and look something like this:

handler (int $errno, string $errstr, string $errfile=?, int $errline=?, array $errcontext=?) : bool

This takes in many parameters like the error number, error string, corresponding file, etc. Let’s understand this better using the same previous example:

<?php
// custom error handler function ->
function myErrorHandler($errno, $errstr, $errfile, $errline, $errcontext){
    $message = date("Y-m-d H:i:s - "); // timestamp in error message
    $message .= "My Error: [" . $errno ."], " . "$errstr in $errfile on line $errline, n"; // custom error message
    $message .= "Variables:" . print_r($errcontext, true) . "rn";
   
    error_log($message, 3, "/path/my-error-file.log");
    die("There was a problem, please try again."); // exit code
}

set_error_handler("myErrorHandler");

$a = 5;
$b = 10;
$c = $a + $b;

if ($c < 20) {
trigger_error("Sum is less than 20.", E_USER_WARNING);
}

echo "This will not be printed!";
?>

Here, we define a custom error handler function where we modify the error message a bit, log it, and exit the code. Then, when we use the trigger_error() function, its logging is handled by the above function, which takes care of the rest. This is what the output in the log file looks like:

2021-03-01 06:58:07 - My Error: [512], Sum is less than 20. in code/my-php/index.php on line 22,

Variables:Array

(

   [a] => 5

   [b] => 10

   [c] => 15

)

As you can see, this can be used to fully customize error logging in applications, allowing organizations to prioritize aspects of errors and contexts that are more important for their application.

Popular PHP Logging Libraries 

Thanks to the huge PHP community support on the internet, there have been very many logging libraries that aim to provide more functionality and ease the overall process for developers and organizations. Each of the renowned PHP frameworks that you must have heard of come equipped with logging libraries built-in. There are also now logging standards established, like the PSR-3 (PHP Standards Recommendation) logger interface, that defines a standardized interface to follow for logging libraries. 

Below is a list of some of the most popular logging libraries in PHP:

  • Monolog
  • Analog
  • KLogger
  • Log4PHP

Feel free to check these out to see what default error logging in PHP is missing out on.

Wrapping Up 

In this post, we covered everything about errors and logging in PHP. We discussed the importance of logging mechanisms in your application, looked at the different types of errors in PHP, and explored the various configuration options and PHP functions that we can use to log errors effectively.

Now that you have a decent understanding of everything, go ahead and start implementing error logging for your application! It doesn’t matter if you are working on a small project where things might seem under control even without log files. Logging your errors is considered one of the top “best practices” in software development that becomes exponentially more important as your applications grow and scale.

To learn more about logging in PHP in general, feel free to check out the Tutorial: Log to Console in PHP on our blog!

To up your application monitoring game by identifying bottlenecks and gaining effective application performance insights, check out ScoutAPM to get started with a 14-day free trial!

Happy coding!

Software development is a complex endeavor. There are many things that must work just right for the whole system to remain stable and useful. Most of the time, there is only so much a developer can anticipate regarding all the possible scenarios that may arise during the lifetime of an application. The bottom line is that good software is the result of a long line of iterations, each building upon the feedback obtained from the use of previous ones.

This feedback comes in many forms, with the most obvious being user comments and complaints. However, a message such as «This isn’t working!» doesn’t provide quite enough information for us to fix things. What we, as developers, need is detailed information on what was happening when things went south.

This information can typically be found in log files; at least, that’s what we’d expect when doing this kind of forensic analysis. However, for this information to exist, we have to be very proactive in having our programs leave clues behind as they progress towards their goals.

There are two questions that need to be answered when thinking of logs:

  1. What should you log?
  2. Where should you store your logs?

The first question is probably too specific to provide a general answer, but the best rule-of-thumb is «log anything you think you might need to figure out what went wrong». It’s ultimately a delicate equilibrium between having enough information and flooding the server disks. It’s not an easy choice.

The second question is a little easier to tackle, as there are only so many places where information can be stored in a computing environment.

In this article, I’ll analyze three ways to approach this common task. The first two are built-in functions, and the last one is a popular third-party library:

  • trigger_error
  • error_log
  • Monolog

Let’s dig in, shall we?

Error Logging via trigger_error

The first tool a PHP developer encounters when looking for logging mechanisms is the trigger_error function.

This function is very simple to use; it takes a message explaining what was wrong and an integer number signaling the error type. This number can be supplied using pre-defined constants.

An example of its usage is as follows:

trigger_error('Something went wrong', E_USER_ERROR);

This will produce an output similar to the following:

PHP Fatal error:  Something went wrong in php shell code on line 1

That is true, of course, if you run the code from the command line. If you were to put it in a PHP file to be accessed via the browser, for example:

<?php
trigger_error('Something went wrong', E_USER_ERROR);

And then brought the built-in webserver up by issuing:

And finally made a request to http://localhost:8000, you’d see, right there in the console, something similar to the following:

[Wed Apr 20 15:15:21 2022] PHP 7.4.3 Development Server (http://localhost:8000) started
[Wed Apr 20 15:15:25 2022] 127.0.0.1:34614 Accepted
[Wed Apr 20 15:15:25 2022] 127.0.0.1:34616 Accepted
[Wed Apr 20 15:15:25 2022] PHP Fatal error:  Something went wrong in /mnt/c/Users/mchoj/PhpstormProjects/Honeybadger/index.php on line 2
[Wed Apr 20 15:15:25 2022] 127.0.0.1:34614 [500]: GET / - Something went wrong in /mnt/c/Users/mchoj/PhpstormProjects/Honeybadger/index.php on line 2
[Wed Apr 20 15:15:25 2022] 127.0.0.1:34614 Closing
[Wed Apr 20 15:15:26 2022] 127.0.0.1:34620 Accepted
[Wed Apr 20 15:15:29 2022] 127.0.0.1:34620 Closed without sending a request; it was probably just an unused speculative preconnection

In the browser, you’d see the following:

Non-working page

One particular feature of this function is that it’s heavily influenced by the interpreter’s configuration. This means that depending on the settings, such as error_reporting, display_errors, and a few others, the actual behavior of the function can change significantly.

For instance, if you were to re-start the server with the following command:

php -S localhost:8000 -d display_errors=1

You’d see the following:

Non-working page

What this means in practice is that it’s difficult to ensure a consistent result among different running environments. It’s not really something you want for your projects.

Error Logging via error_log

Another built-in tool that PHP provides is the error_log function.

This function is more predictable than trigger_error, as it only depends on the error_log setting; however, its actual behavior can change dramatically depending on its parameters (particularly the message_type).

For instance, if you change the code to:

<?php
error_log('Something went wrong');

And refresh the page, you’ll be staring at a blank screen; however, when looking at the console output, you’ll find the following:

[Wed Apr 20 15:39:41 2022] PHP 7.4.3 Development Server (http://localhost:8000) started
[Wed Apr 20 15:39:47 2022] 127.0.0.1:34648 Accepted
[Wed Apr 20 15:39:47 2022] 127.0.0.1:34646 Accepted
[Wed Apr 20 15:39:47 2022] Something went wrong
[Wed Apr 20 15:39:47 2022] 127.0.0.1:34646 [200]: GET /
[Wed Apr 20 15:39:47 2022] 127.0.0.1:34646 Closing
[Wed Apr 20 15:39:47 2022] 127.0.0.1:34650 Accepted
[Wed Apr 20 15:39:57 2022] 127.0.0.1:34648 Closed without sending a request; it was probably just an unused speculative preconnection
[Wed Apr 20 15:39:57 2022] 127.0.0.1:34648 Closing
[Wed Apr 20 15:39:57 2022] 127.0.0.1:34650 Closed without sending a request; it was probably just an unused speculative preconnection
[Wed Apr 20 15:39:57 2022] 127.0.0.1:34650 Closing

Even if you use the same php -S localhost:8000 -d display_errors=1 to bring the server up.

In fact, this function stores the error messages together with those from the webserver by default.

You can change this behavior via the message_type parameter. If you fill it with a number 3 when calling the function, you’ll be asking the interpreter to output your message to a specific file, which is determined by the value of the next parameter: destination. Like this:

<?php
error_log('Something went wrong', 3, __DIR__.'/log');

If you refresh the page. you’ll end up with the same blank screen, but the server logs will be different:

[Wed Apr 20 15:46:11 2022] PHP 7.4.3 Development Server (http://localhost:8000) started
[Wed Apr 20 15:46:15 2022] 127.0.0.1:34652 Accepted
[Wed Apr 20 15:46:15 2022] 127.0.0.1:34654 Accepted
[Wed Apr 20 15:46:15 2022] 127.0.0.1:34652 [200]: GET /
[Wed Apr 20 15:46:15 2022] 127.0.0.1:34652 Closing
[Wed Apr 20 15:46:16 2022] 127.0.0.1:34658 Accepted
[Wed Apr 20 15:46:18 2022] 127.0.0.1:34658 Closed without sending a request; it was probably just an unused speculative preconnection
[Wed Apr 20 15:46:18 2022] 127.0.0.1:34658 Closing
[Wed Apr 20 15:46:18 2022] 127.0.0.1:34654 Closed without sending a request; it was probably just an unused speculative preconnection
[Wed Apr 20 15:46:18 2022] 127.0.0.1:34654 Closing

However, as expected, if you look at the files present in the project’s directory, you’ll find a newly created log one, and if you look at its contents, you’ll find the following:

One of the main differences between error_log and trigger_error is that a call to the latter will most likely result in an execution interruption, while the former will simply write the message to the appropriate stream and yield back control.

At first glance, you could get the impression that this function adds little to no value over other functions aimed at writing strings to files.

However, if you take a closer look, you’ll notice a very particular, and certainly useful, feature of error_log; it handles locking internally, meaning you won’t have to deal with nasty race conditions in high concurrency environments, such as a web server.

Although either trigger_error or error_log will technically work, it’s pretty clear that the developer experience they provide is sub-optimal at best.

Think for a minute what it would take for you to store log information both locally and remotely just using these tools, not to mention choosing the log destination by severity.

Of course, the PHP community has better options at your disposal. Read on to discover an professional tool.

Error Logging via Monolog

So far, I’ve been discussing what PHP itself has to offer, which, quite frankly, is not very appealing. Luckily, though, there are great developers involved in pushing beyond those boundaries, making the appearance of great tools a reality. That is the case for Monolog.

Monolog is an object-oriented library that can be brought into any PHP project thanks to composer, which provides a lot of really cool features, such as the ability to send the same message through different channels (a file, a database, and an email) and apply different filtering and formatting logic before storing, allowing you to build really complex logging mechanisms without too much hassle.

Let’s start with a simple example: you want to keep a log of every IP address that visits your website.

Your code would look something like the following:

<?php

$currentIp = $_SERVER['REMOTE_ADDR'];

error_log('Got a visitor from '.$currentIp, 3, __DIR__.'/ip.log');

The next step is to bring Monolog in by using the following command:

composer require monolog/monolog

Which will produce an output similar to the following:

Loading composer repositories with package information
Updating dependencies
Lock file operations: 2 installs, 0 updates, 0 removals
  - Locking monolog/monolog (2.5.0)
  - Locking psr/log (3.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing psr/log (3.0.0): Extracting archive
  - Installing monolog/monolog (2.5.0): Extracting archive
10 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!
PSR-4 autoloading configured. Use "namespace MauroLog;" in src/
Include the Composer autoloader with: require 'vendor/autoload.php';

After the installation is finished, you need to make use of the library in your code. As usual, it all starts with including the autoloading definition:

<?php

require_once 'vendor/autoload.php';

$currentIp = $_SERVER['REMOTE_ADDR'];

error_log('Got a visitor from '.$currentIp, 3, __DIR__.'/ip.log');

Now, before we move forward with this refactoring, there are a few concepts you need to know about. The thing is, while Monolog takes logging to a whole new level, that kind of power comes with a price tag… no, I’m not talking about money, but about a learning curve.

The first one is the Logger itself. This class constitutes the backbone of the whole library. In fact, it’s the one you’ll use most of the time.

You can have as many loggers as you need, each one with its own configuration.

Every Logger is identified by its channel name. This can be useful if, for instance, you’re sending logs to the same file and need to differentiate them later.

The idea is for the logs generated via Monolog to be easily parsed using simple tools like grep.

The Logger relies on three other classes to fulfill its purpose:

  • Handlers
  • Processors
  • Formatters

Monolog Handlers

Handlers are the lowest level component. A handler’s responsibility is to answer the question «Where should this log be stored?». There are many handler classes defined within the Monolog library. Here are a few of them:

  • ErrorLogHandler: the simplest one, just a little wrapper around error_log
  • NativeMailerHandler: send logs to an email address using php’s mail function
  • SQSHandler: send logs to an AWS SQS queue.

Monolog Processors

Processors are used to transform raw log data before it is passed to the handlers for storage.

Usually, this means adding standard context information, such as the date and time, user, and IP.

The library comes bundled with quite a few very useful processors, such as:

  • WebProcessor which adds information about the client making the request that spawned this message
  • MemoryProcessor which adds information about the memory usage
  • GitProcessor which adds information about the Git commits involved

And, of course, you can build your own processors by creating new classes which implement the ProcessorInterface.

In summary, processors are meant to answer the question «What else should be logged?»

Monolog Formatters

Finally, formatters are meant to answer the question «How should messages be stored?»

Formatters work in conjunction with handlers to add formatting information around the messages, so they can be better rendered in a particular environment.

For instance, if you’re sending logs via email, a nicely crafted HTML will make the logs more easily readable within an email client.

Monolog in Action

Ok, it’s been a lot of theory right? Let’s see some code to make sense of all we’ve been discussing.

Going back to our little example, the first thing we’ll need is a Logger:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;

$logger = new Logger('my-app');
$currentIp = $_SERVER['REMOTE_ADDR'];

But … where are the logs going to be stored? We need a Handler for that!

Let’s add one:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandlerStreamHandler;

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$currentIp = $_SERVER['REMOTE_ADDR'];

Now we have a handler that will save every log record to the file ip.log in the same directory as our application.

What we need to do now is attach this Handler to the Logger, like this:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandlerStreamHandler;

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$logger->pushHandler($handler);
$currentIp = $_SERVER['REMOTE_ADDR'];

And now, let’s write our first log message using Monolog:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandlerStreamHandler;

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$logger->pushHandler($handler);
$currentIp = $_SERVER['REMOTE_ADDR'];
$logger->info('Got a visitor from '.$currentIp);

The info method is just one of many that can be called to signal the severity of the message.

If you start a local webserver (php -S localhost:8000) and visit http://localhost:8000, you’ll find something like this in your ip.log file:

[2022-04-29T11:41:39.121355+02:00] my-app.INFO: Got a visitor from 127.0.0.1 [] []

Now let’s say you want to not only save this information to a local file but also send it to an admin via email. You could do it using a NativeMailerHandler, like this:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandler{StreamHandler, NativeMailerHandler};

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$emailHandler = new NativeMailerHandler('you@yourdomain.com', 'Message from Honeybadger's App', 'app@localhost');

$logger->pushHandler($handler);
$logger->pushHandler($emailHandler);
$currentIp = $_SERVER['REMOTE_ADDR'];

$logger->info('Got a visitor from '.$currentIp);

If you refresh your page, you’ll see a new entry in your file but no email in your inbox. Why did this happen?

NativeMailerHandler is designed to only react to error messages by default, so you have two options here; you can either change the severity of your message:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandler{StreamHandler, NativeMailerHandler};

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$emailHandler = new NativeMailerHandler('you@yourdomain.com', 'Message from HoneBadger's App', 'app@localhost');

$logger->pushHandler($handler);
$logger->pushHandler($emailHandler);
$currentIp = $_SERVER['REMOTE_ADDR'];

$logger->error('Got a visitor from '.$currentIp);

Or you can have your $emailHandler also take info messages into account:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandler{StreamHandler, NativeMailerHandler};

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$emailHandler = new NativeMailerHandler('you@yourdomain.com', 'Message from Honeybadger's App', 'app@localhost', Logger::INFO);

$logger->pushHandler($handler);
$logger->pushHandler($emailHandler);
$currentIp = $_SERVER['REMOTE_ADDR'];

$logger->info('Got a visitor from '.$currentIp);

In this case, your inbox should contain a message similar to the following:

A green info message

As you can see, an HTML formatter was used to produce this nice email without you doing anything. Are you sold on the idea of using Monolog yet? :)

You could keep adding handlers, formatters and processors as you see fit; the possibilities are endless.

Integration with Honeybadger

Now, if you’re familiar with Honeybadger, you already know all the benefits it can bring to your development team.

Wouldn’t it be great to integrate it into your Monolog-powered logs? Well, guess what? It is really simple.

All it takes is an instance of HoneybadgerLogHandler as a Monolog Handler.

Start by adding the required library:

composer require Honeybadger-io/Honeybadger-php

Then update your code to look like this:

<?php

require_once 'vendor/autoload.php';

use MonologLogger;
use MonologHandler{StreamHandler, NativeMailerHandler};
use Honeybadger{Honeybager, LogHandler};

$logger = new Logger('my-app');
$handler = new StreamHandler(__DIR__.'/ip.log');

$emailHandler = new NativeMailerHandler('you@yourdomain.com', 'Message from Honeybadger's App', 'app@localhost', Logger::INFO);
$honeybadger = HoneybadgerHoneybadger::new([
  'api_key' => 'YOUR_API_KEY'
]);

$honeyBadgerHandler = new LogHandler($honeybadger);

$logger->pushHandler($handler);
$logger->pushHandler($emailHandler);
$logger->pushHandler($honeyBadgerHandler);

$currentIp = $_SERVER['REMOTE_ADDR'];

$logger->info('Got a visitor from '.$currentIp);

And, after refreshing the page again, you’ll find a message like this on your Honeybadger dashboard:

Log message from php at Honeybadger's Dashboard.

Is your project based on Laravel? You’re also covered. Read here for a complete integration guide.

Conclusion

As we’re accustomed when it comes to PHP, there are quick-and-dirty solutions you can implement in a heartbeat, and there are professional ones that take a little more effort, but the difference in the long run makes them well worth the trouble.

Which one will you choose for your projects?

How to Debug PHP Errors

Debugging PHP errors in a production environment can be one of the single most frustrating experiences as a developer. More often than not, the error reports are vague, and identifying the underlying causes can be difficult at best. That said, there are a few common steps that can be followed towards identifying and resolving errors that crop up in production.

Debugging PHP Errors

Step 1: Increase the log level

More information is always better. Using the methods described in Where are PHP Errors Logged?, the first step towards diagnosing any issue is to increase the log level. This allows you to see everything that is happening before and after a problem occurs. There is a good chance that the problems you are experiencing have warnings or messages associated with them that don’t necessarily make it into the log files by default.

Step 2: Retain logs

Once you’ve increased the log level, the next step is to start retaining logs. This can be done through any number of log aggregation platforms, and allows you to start establishing a timeline of events without worrying about the log files being rolled over.

Step 3: Attempt to replicate circumstances

Once you’ve determined the log lines that relate to the problem at hand, the next step is to attempt to replicate the circumstances of the error in a development environment. Before we can do this, we first need to establish some testing guidelines. This involves doing things like mimicking the production database, the user accounts involved, and even the operating system. Everything is fair game here.

Step 4: Test assumptions

Once you’ve established the circumstances that you think might throw the exception or error you are hunting down, it’s time to test them. Never test exceptions in production. Development and staging environments are designed to be breakable without any impact on the end-users, so always always always try to break your code in a safe environment.

Step 5: Adjust test parameters and try again

If you were unable to replicate the problem in Step 4, then it’s back to the drawing board. Not every error is easy to reproduce, and may have time-based constraints or something else making it difficult to replicate in a non-production environment. Jump back to Step 3, adjust your test parameters, and try it all over again.

What is a stack trace?

Whenever exceptions are thrown, a stack trace is usually included. But, what is a stack trace? In essence, it is a rundown of every file and function that is called leading up to the error. To be clear, a stack trace doesn’t include the files and functions that are touched before the error occurred, only the chain of methods that are called as the error happened. This allows you to «trace» the «stack» of operations that are performed when an error happened in order to identify exactly what went wrong, and where.

As an example, let’s take a look at the stack trace that is returned from the following (incredibly simplistic) code:

do_the_thing();

function do_the_thing() {
  throw new Exception("a thing happened!");
}

When do_the_thing() is executed, an exception is immediately thrown. This results in the following stack trace:

Fatal error: Uncaught Exception: a thing happened! in test.php:6
Stack trace:
#0 test.php(3): do_the_thing()
#1 {main}
  thrown in test.php on line 6

As you can see, rather than simply returning the exception message, reading the stack trace in reverse order shows that the exception was thrown on line 6, but was triggered by a call to do_the_thing() on line 3. For more complicated stack traces, this can be invaluable as it gives us a lot of post-mortem information.

A brief introduction to Xdebug

While debugging in development can be difficult to do, Xdebug is a popular tool to help identify exactly what is happening as a piece of code executes. Containing a single-step debugger that allows you to interact directly with running PHP scripts, Xdebug is an excellent way to deal with highly complex and nuanced issues without having to resort to dangerous var_dump() and exit calls.

Example var_dump/exit call

Example var_dump/exit call

IDE integrations

While Xdebug is an incredibly powerful tool that can be used for more than just stepping through code, one of the more impressive aspects of it is the numerous IDE integrations that exist. These integrations give you the ability to diagnose problems with your code from directly within your editor, allowing you to test and fix problems in the environment you are most comfortable with.

Visual Studio Code XDebug Integration

Visual Studio Code XDebug Integration

Identifying patterns using Rollbar

Too often, debugging errors in a PHP application comes down to the amount of information you can eke out of your logs. Rollbar offers a better way because it empowers you to not only identify what is happening, but when, where, to whom, and how often. Rather than having to sort through pages of raw text logs, Rollbar aggregates data from individual exceptions and errors and creates a dashboard with as much information as possible for analyzing these issues.

Rollbar dashboard

When properly configured, these exceptions can be tied directly to user accounts, and tracked over time across an easy-to-read graph—with deployment markers to boot. While this doesn’t necessarily tell you exactly what an issue is, it comes as close as possible to doing so by providing you with more information than you could possibly ask for.

Occurrences

Occurrences

Whenever an exception is thrown during the course of an HTTP request, the request details are tracked alongside any additional information—such as the browser and operating system. This gives you the ability to identify any potential issues that could be related to specific systems, or even track down offending request parameters.

Suspected Deploy

Suspected deploy

If you are tracking deployments within Rollbar, then identifying which deployment might have been responsible for a new error or exception is as straightforward as possible—so straightforward, in fact, that Rollbar does the work for you.

Source Control Integration

Source control integration

When a Rollbar project is linked to a repository in GitHub, Bitbucket, or GitLab, any file references in a stack trace are automatically linked directly out to the offending file in the linked repository.

Возможно, вам также будет интересно:

  • Как проверить линию на ошибки
  • Как проверить лексические ошибки в тексте
  • Как проверить лексические ошибки в предложении
  • Как проверить лаунчер на ошибки
  • Как проверить кэш процессора на ошибки

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии