Регистрация и вывод ошибок в PHP

Регистрация ошибок в PHP не самая простая и интуитивно понятная. Это включает в себя настройку нескольких параметров конфигурации, а также некоторые эксперименты, чтобы привыкнуть. Как только вы все настроите и разберетесь (как вы это сделаете после прочтения этого поста), все станет намного проще, и вы поймете, насколько полезным может оказаться ведение журнала ошибок для вашего приложения — от отладки и устранения неполадок до мониторинга и обслуживания.

Важность регистрации ошибок

Ошибки в программных системах имеют ужасную репутацию, связанную со сбоями и нарушением функциональности. В результате многие из нас часто не осознают важность этих кричащих красных строк, которые привлекают наше внимание к ошибкам, несоответствиям и неточностям в нашем коде — ошибкам, которые могут дорого нам обойтись, если их не заметить. Поэтому некоторым из нас стоит изменить свое отношение к сообщениям об ошибках — отслеживать, регистрировать и систематизировать их — и осознавать их важность. 

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

Теперь, когда мы, надеюсь, убедились, что логирование ошибок — стоящая экспедиция, давайте рассмотрим различные типы ошибок в PHP.

Типы ошибок PHP

В целом, в PHP существует пять типов ошибок:

1. Фатальные ошибки во время выполнения (E_ERROR) 

Эти ошибки обычно возникают, когда операция в вашем коде не может быть выполнена. Это приводит к выходу вашего кода. Примером фатальной ошибки может быть вызов функции, которая не была определена в вашем коде, как показано ниже:

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

Вывод ошибки:

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. Предупреждающие ошибки (E_WARNING)

Предупреждающая ошибка является более мягкой и менее навязчивой, поскольку она не останавливает выполнение. Он представляет собой дружеское напоминание о том, что в вашем коде что-то не так — ошибка, которая может не привести к немедленному сбою или сбою вообще, но предлагает более точный способ сделать что-то, что сделает ваш код более надежным. Эти предупреждения также могут уберечь разработчиков от проблем, которые могут представлять гораздо большую угрозу в будущем. Примером предупреждения об ошибке может быть попытка включения файла в PHP с использованием неправильного пути к файлу, как показано ниже:

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

Вывод ошибки:

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

3. Ошибки синтаксического анализа (E_PARSE)

Ошибки синтаксического анализа также известны как синтаксические ошибки, поскольку они возникают из-за синтаксических ошибок в вашем коде. Эти ошибки возникают во время компиляции вашего кода, заставляя его выйти до запуска. Распространенный пример ошибки синтаксического анализа — отсутствие точки с запятой в конце оператора кода, как показано ниже:

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

Вывод ошибки:

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

4. Уведомления об ошибках (E_NOTICE)

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

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

Вывод ошибки:

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

5. Пользовательские ошибки (E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE) 

Ошибки пользователя определяются пользователем, т. е. представляют собой пользовательское сообщение, созданное явно из кода для захвата определенного состояния. Эти ошибки вызываются разработчиками вручную с помощью функции trigger_error вместо механизма PHP. Далее они классифицируются как фатальные ошибки, ошибки предупреждений и уведомления, но для простоты мы сгруппируем их все как ошибки пользователя.

Где могут быть выведены ошибки PHP?

Есть два основных места, где мы можем представить наши ошибки в PHP — через встроенные ошибки и специальные файлы журнала ошибок.

Встроенные ошибки 

Встроенные ошибки — это те, которые отображаются на вашей веб-странице в браузере или на вашем терминале через STDOUT в среде командной строки. Эти ошибки оказываются весьма полезными во время разработки — разработчики могут отлаживать свой код, исправлять проблемы и получать информацию об общем выполнении. Ниже приведен пример того, как эти ошибки обычно выглядят в браузере:

неопределенный

Хотя это оказывается очень полезным для разработчиков, вы должны быть очень осторожны, гарантируя, что эти ошибки не будут выводиться, когда ваше приложение будет запущено в работу — по двум причинам — удобство для конечного пользователя и безопасность. Вы можете переключить отображение этих ошибок, используя директиву display_error в конфигурации вашей системы. Мы углубимся в это в следующем разделе.

Файлы журнала  ошибок

Встроенные ошибки не сохраняются в памяти, т. е. они нигде не сохраняются и просматриваются только до тех пор, пока активен браузер или сеанс терминала. Кроме того, вы захотите иметь их только в среде разработки. И наоборот, как предполагает тема этого поста, регистрация ошибок — более разумный и систематический подход к поддержке крупномасштабных приложений. Они сохраняются в памяти и предоставляют информацию о работе вашего приложения в нескольких компонентах в одном месте, что упрощает мониторинг и устранение неполадок.

Таким образом, PHP позволяет вам направлять все ваши ошибки в определенные файлы журналов; в этих файлах хранятся метки времени, трассировки стека ошибок, настраиваемые сообщения и другая полезная информация об источнике ошибки и способах ее устранения. Вы можете указать путь к вашему пользовательскому файлу журнала, используя директиву error_log конфигурации вашей системы. Вот пример файла журнала ошибок:

неопределенный

Вы также можете записывать свои ошибки в файл системного журнала, который обычно находится в — /var/log/syslog . Мы расскажем об этом в следующем разделе поста.

Теперь давайте посмотрим, как мы можем настроить, где и как мы хотим регистрировать наши ошибки.

Включение и настройка отчетов об ошибках в PHP 

Как мы обсуждали ранее, регистрация ошибок в PHP немного менее проста, чем в других языках и фреймворках. Возможно, вам придется настроить несколько параметров в файлах конфигурации, чтобы настроить шаблоны ведения журнала. Например, когда вы устанавливаете PHP на свой компьютер, первоначальная конфигурация поставляется с отключенными некоторыми аспектами регистрации ошибок. Это отличается от системы к системе, и поэтому вам следует вручную проверить эти настройки перед началом работы.

Регистрация параметров конфигурации в файле php.ini

Параметры конфигурации находятся в файле php.ini . Этот файл считывается при запуске PHP и позволяет разработчикам экспериментировать с функциональностью PHP. Обычно этот файл можно найти где-нибудь в каталоге /etc/php в большинстве систем Linux. 

Существует множество директив (опций), относящихся к регистрации ошибок в PHP, которые мы можем настроить в этом файле php.ini :

  • display_errors (по умолчанию: 1)

Ошибки отображения — это встроенные ошибки, которые мы рассматривали ранее. Эту директиву можно эффективно использовать во время разработки для вывода сообщений об ошибках PHP в браузер или терминал. Однако для приложений в рабочей среде вам, скорее всего, следует отключить эту функцию, чтобы уберечь пользователей от плохой работы с веб-сайтом из-за неясных сообщений об ошибках. Не только это, но это также защищает вас от раскрытия ценной информации о внутренностях вашего приложения в качестве меры безопасности.

  • display_startup_errors (по умолчанию: 0)

Как следует из названия, это для вывода любых ошибок, возникающих при запуске PHP. Обычно они не предоставляют никакой ценной информации конкретно о вашем приложении, и поэтому их не нужно включать.

  • log_errors (по умолчанию: 0)

Эта директива позволяет вам переключить регистрацию ошибок на указанный путь (в следующей директиве). Поскольку по умолчанию это отключено, было бы целесообразно переключить его на 1 для записи сообщений об ошибках вашего приложения в файл журнала.

  • error_log (по умолчанию: 0)

Эта директива позволяет указать путь к файлу журнала. Вы также можете установить для этого параметра значение « syslog », чтобы направлять все ваши сообщения журнала ошибок в системный журнал. 

  • error_reporting (по умолчанию: ноль)

Директива error_reporting позволяет настроить, о каких уровнях ошибок вы хотите сообщать, а о каких вы можете не сообщать. Например, вы можете использовать директиву, как показано ниже, чтобы сообщать обо всех ошибках:error_reporting = E_ALL

  • track_errors (по умолчанию: 0)

Эта директива позволяет вам получить доступ к последнему сообщению об ошибке в глобальной переменной $php_errormsg в вашем коде и может отслеживать ошибки во всем вашем проекте.

После внесения изменений в файл php.ini вам необходимо перезапустить сервер, чтобы изменения вступили в силу.

Функция ini_set()

Однако, если вы не можете найти файл php.ini или предпочитаете переопределить глобальные параметры конфигурации вашего проекта, есть также возможность обновить эти директивы с помощью функции ini_set() в вашем PHP-коде. Например, приведенный ниже код можно использовать для настройки отчетов об ошибках в вашем проекте:

<?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');

?>

Функция error_reporting()

Можно также изменить параметр конфигурации error_reporting , используя функцию error_reporting() внутри вашего кода во время выполнения. Как и в функции ini_set, вы можете использовать побитовые операторы, такие как ИЛИ (|), И (&), НЕ (~) и т. д., при указании уровней ошибок, о которых следует сообщать. Ниже приведены несколько примеров использования этой функции.

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

или

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

Теперь, когда у нас есть системные конфигурации и общая настройка, давайте рассмотрим пример того, как ошибки в коде вашего проекта могут быть зарегистрированы в файлах вашей системы.

Регистрация ошибок в PHP

Во-первых, мы переопределим параметры конфигурации ведения журнала с помощью функции ini_set() , чтобы включить ведение журнала ошибок и указать путь к файлу журнала. Затем мы напишем некоторый ошибочный код, чтобы PHP вызывал ошибку, которую мы хотели бы зарегистрировать.

<?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
?>

Открыв веб-страницу в нашем браузере, давайте откроем файл «my-error-file.log», чтобы увидеть, было ли зарегистрировано сообщение об ошибке. Вот вывод файла журнала:

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

Как видите, наша ошибка уведомления была зарегистрирована с отметкой времени. Поскольку наш код обнаруживает все больше и больше ошибок, этот файл будет продолжать заполняться соответствующими временными метками. Обратите внимание, что мы явно не отключили display_errors , поэтому эти сообщения об ошибках, скорее всего, будут регистрироваться на веб-странице браузера, чего вы, возможно, захотите избежать во время производства. 

Это был пример регистрации ошибок, вызванных PHP, в файлах журналов. Теперь давайте посмотрим, как мы можем создавать и регистрировать пользовательские сообщения об ошибках для нашего приложения.

Функции регистрации ошибок PHP

До сих пор мы рассматривали ошибки, возникающие в PHP — ошибки, связанные с выполнением вашего кода. Тем не менее, часто вы хотели бы также фиксировать пользовательские ошибки с пользовательскими сообщениями об ошибках, характерными для функционирования вашего приложения. Эти так называемые ошибки не обязательно могут привести к сбою вашего кода или остановке его выполнения, но могут указывать на условия, характеризуемые как ошибочные и заслуживающие внимания для вашего приложения. Они могут служить для организации указанием на аномальное поведение, которое команда может захотеть изучить и исправить.

Чтобы облегчить это, PHP предоставляет набор функций, которые мы можем использовать для активного логирования ошибок в нашем коде.

error_log()

Наиболее распространенным методом активной регистрации ошибок является функция error_log() . Это отправляет строковый аргумент для сообщения об ошибке в файл журнала.

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

Также требуется много других параметров для отправки сообщений об ошибках по электронной почте или в определенные файлы журналов. Однако, для простоты, мы не будем рассматривать это здесь. 

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

<?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
}

?>

Вот вывод файла журнала:

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

Точно так же вы также можете регистрировать значения переменных в своем коде, чтобы предоставить дополнительный контекст о ваших ошибках. Давайте посмотрим на пример для этого:

<?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));
?>

Вот вывод файла журнала ->

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

(

   [0] => PHP

   [1] => Python

   [2] => Node.js

)

trigger_error()

Функцию trigger_error() можно использовать для создания пользовательской ошибки/предупреждения/уведомления. Вы также можете указать тип ошибки на основе условия. Это позволяет настроить его отчетность и другое поведение, например, используя тип ошибки E_USER_ERROR. Мы можем вызвать немедленный выход кода по сравнению с ошибкой E_USER_WARNING.

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

Разница между trigger_error и error_log заключается в том, что первый генерирует только пользовательскую ошибку и зависит от конфигурации ведения журнала вашей системы для обработки этого сообщения об ошибке (независимо от того, отображается оно или регистрируется). error_log , с другой стороны, будет регистрировать ваше сообщение независимо от конфигурации системы.

Вот код того же примера, который мы видели ранее:

<?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!";
}

?>

Это добавляет запись в журнал, аналогичную той, что мы видели ранее, но с уровнем ошибки, а также обычной информацией об источнике ошибки (вывод файла журнала ниже):

[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()

Вы также можете выбрать прямую отправку сообщения об ошибке в системный журнал с помощью функции syslog() .

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

Первый аргумент — это уровень приоритета ошибки — LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_ALERT, LOG_EMERG и т. д. Второй аргумент - это фактический текст сообщения. Вот как эту функцию можно использовать:

<?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();
?>

Это должно отражаться в журнале вашей системы (обычно в /var/log/syslog ) как:

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

set_error_handler()

Чтобы настроить обработку всех пользовательских ошибок в вашем коде, PHP позволяет указать пользовательскую функцию обработчика ошибок, чтобы переопределить обработку ошибок по умолчанию. Это позволяет организациям легко изменить способ регистрации ошибок, соответствующий метод отправки сообщений об ошибках и многое другое. В этом помогает функция set_error_handler() .

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

Он принимает в качестве аргумента нашу пользовательскую функцию-обработчик ошибок, которая определяет обработку наших ошибок и выглядит примерно так:

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

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

<?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) . "\r\n";
   
    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!";
?>

Здесь мы определяем пользовательскую функцию обработчика ошибок, в которой мы немного изменяем сообщение об ошибке, регистрируем его и выходим из кода. Затем, когда мы используем функцию trigger_error() , ее регистрация обрабатывается указанной выше функцией, которая позаботится обо всем остальном. Вот как выглядит вывод в файле журнала:

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

)

Как видите, это можно использовать для полной настройки регистрации ошибок в приложениях, что позволяет организациям расставлять приоритеты в тех аспектах ошибок и контекстах, которые более важны для их приложения.

Популярные библиотеки журналирования PHP 

Благодаря огромной поддержке PHP-сообщества в Интернете существует очень много библиотек ведения журналов, которые стремятся предоставить больше функциональности и упростить общий процесс для разработчиков и организаций. Каждый из известных фреймворков PHP , о которых вы, должно быть, слышали, оснащен встроенными библиотеками ведения журналов. Также теперь установлены стандарты ведения журналов, такие как интерфейс регистратора PSR-3 (рекомендация по стандартам PHP), который определяет стандартизированный интерфейс для библиотек журналирования. 

Ниже приведен список некоторых наиболее популярных библиотек ведения журналов в PHP:

Не стесняйтесь проверить их, чтобы увидеть, чего не хватает в журнале ошибок по умолчанию в PHP.

Подведение итогов 

В этом посте мы рассмотрели все об ошибках и регистрации в PHP. Мы обсудили важность механизмов ведения журнала в вашем приложении, рассмотрели различные типы ошибок в PHP и изучили различные параметры конфигурации и функции PHP, которые мы можем использовать для эффективного ведения журнала ошибок.