Запись в лог файл php. Ведение лог файла посетителей. Настройки для логирования ошибок
В интернете существует большое количество сервисов, предоставляющие услуги по учету посещаемости Вашего сайта. Данные услуги предоставляются как платно, так и бесплатно. К примеру, можно привести LiveInternet. Этот сервис довольно широкий в интернете и почти каждый сайт пользуется его услугами. Владелец имеет подробную статистику о посещении его сайта.
Спору нет! Данные сервисы незаменимы в учете посещаемости сайта и дальнейшего изучения поведения пользователей. Но в данной статье я хочу рассказать, как можно сделать на сервере лог-файл визитов посетителей.
Лог-файл визитов
Данный лог файл будет очень полезен, для учета визитов и просмотров посетителями Ваш сайт. Для создания лог-файла используется скрипт, написанный на языке php. Скрипт довольно простой в понимании и установке на сайт.
Скрипт php для создания лог-файла
Скрипт записывает точное время захода на сайт, определяет браузер посетителя, и что самое главное – определяет откуда пришел посетитель к Вам. Запись лог-файла происходит при каждом отображении к той или иной страницы сайта. То есть, владелец сайта может посмотреть в лог файле какие конкретно просматривал страницы сайта посетитель по определенному IP и времени, используя определенный браузер.
Листинг скрипта записи данных в лог-файл
$er_time=date("H:i:s d M Y"); // Записываем текущую дату обращения на сайт $U=getenv("HTTP_USER_AGENT"); // Узнаем какой браузер использует посетитель $H=getenv("HTTP_REFERER"); // Получаем адрес ulr откуда прищел посетитель $R=getenv("REMOTE_ADDR"); // Получаем IP адрес посетителя $W=getenv("REQUEST_URI"); // Получаем адрес страницы, которую запросил пользователь $f=fopen("logs/users.log","a"); // Указываем путь до лог-файла flock ($f,2); fwrite($f,"$er_time\n Br: $U\n Rf: $H\n IP: $R\n Rq: $W\r\n"); // Запись полученных данных в файл \r\n\ указывает на запись с новой строчки в файле. Данная операция будет выполняться при каждом открытие страницы. fclose($f); // Закрытие файла?>Установка скрипта
Сохраните скрипт в шаблоне, либо во внешнем файле users.php. Для того, чтобы вставить скрипт в страничку используйте следующий код.
В каждом реальном PHP приложений время от времени возникают ошибки и исключения, выскакивают предупреждения, сообщения. Если мы не будим записывать эту информацию (логировать), то в один прекрасный момент станет невозможным понять, в какой же части приложения возникают эти ошибки и исключения, и, соответственно, не сможем решить их. К тому же, существуют такие ситуации, когда логирование событий, действий просто необходимо, как в случае, с входом пользователя в систему и выходом из нее, например.
В PHP уже существуют необходимые средства для журналирования : функция error_log() – для отправки сообщения в системный журнал, функция set_error_handler() , для перехвата предупреждений и ошибок. Эти функции могут быть использованы для пользовательского управления ошибками, давая разработчику кода возможность самостоятельного управления логикой обработки и фильтрации ошибок.
Однако такой низкоуровневый доступ приводит к частому дублированию кода, и что еще более важно, такой код более подвержен ошибкам. Поэтому на помощь программисту приходят уже готовые компоненты, хорошо протестированные и зарекомендовавшие себя в “боевых” условиях.
К таким компонентам относится компонент zend-log из фреймворка Zend . Компонент zend-log может быть использован в качестве многоцелевого компонента логирования, эдакий мастер на все руки. Он поддерживает множество форматов журнальных сообщений и разновидностей баз логирования (файлы, базы данных), плюс ко всему имеет проработанную систему фильтрации сообщений и много чего еще. Также zend-log совместим с PSR-3 стандартом логирования. Устанавливается так:
Сomposer require zendframework/zend-log
Используется следующим образом (для примера используется файл index.php в корне проекта):
Require "vendor/autoload.php";
Use Zend\Log\Logger;
use Zend\Log\Writer\Stream;
$logger = new Logger;
// отправляем ошибки в консоль
$writer = new Stream("php://ouput");
$logger -> addWriter($writer);
$logger -> log(Logger::INFO,"Некая информация");
Результат выполнения кода выше:
2017-09-26T10:40:34+03:00 INFO(6): Некая информация
Итоговая строка включает время события, приоритет и сообщение. Формат выводимого сообщения, безусловно, может быть изменен, в случае необходимости с помощью метода setFormatter() . По умолчанию, строка лога описывается следующим шаблоном:
%timestamp% %priorityName% (%priority%): %message% %extra%
- %timestamp% - это метка времени
- %priorityName% - текстовая метка приоритета
- %priority% - числовая метка приоритета
- %message% - сообщение
- %extra% - необязательное значение для дополнительной информации
Если Вы захотите изменить формат сообщения, то это делается следующим образом:
$formatter = new Zend\Log\Formatter\Simple("сообщение %message%" . PHP_EOL);
$writer -> setFormatter($formatter);
Компонент zend-log может быть также использован для логирования ошибок и исключений самого интерпретатора PHP. Для этого в классе Logger существую два статических метода: Logger::registerErrorHandler($logger) – для перехвата ошибок и Logger::registerExceptionHandler($logger) - для перехвата исключений.
Use Zend\Log\Logger;
use Zend\Log\Writer;
$logger = new Logger;
$writer = new Writer\Stream(__DIR__ . "/test.log");
$logger->addWriter($writer);
// Логировать ошибки
Logger::registerErrorHandler($logger);
// Логировать исключения
Logger::registerExceptionHandler($logger);
Как упоминалось ранее, zend-log предоставляет нам возможность фильтрации сообщений для логирования, т.е. перед тем как записать сообщение в лог, мы может посмотреть удовлетворяет ли оно нашим критериям, и если да, то записываем.
Вот пример:
$filter = new Zend\Log\Filter\Priority(Logger::CRIT);
// метод addFilter интерфейса Writer
$writer->addFilter($filter);
В данном примере, мы будем логировать только те сообщения, чей приоритет меньше или равен критическому (Logger::CRIT ).
Полный список приоритетов, определенных в классе Zend\Log\Logger :
Const EMERG = 0; // Авария: система непригодна для использования
const ALERT = 1; // Тревога: срочно необходимо принимать меры
const CRIT = 2; // Критическая ситуация
const ERR = 3; // Ошибка
const WARN = 4; // Предупреждение
const NOTICE = 5; // Внимание
const INFO = 6; // Информация
const DEBUG = 7; // Дебаг, отладка
Мы также можем фильтровать сообщения на основе регулярных выражений, временных меток и т.д. Таким образом, логирование в PHP – это важная и порой необходимая вещь при разработке web-приложений .
Под логированием в PHP подразумевается то, о каких типах ошибок будет сообщать вам ваше веб-приложение/сайт/php-скрипт и каким образом.
Существует 2 (3) основных способа получения ошибок от приложения:
- Вывод этих ошибок непосредственно на экран
- Запись этих ошибок в специальный лог-файл
- или же сразу оба варианта: вывод этих ошибок на экран и запись их в специальный лог-файл
Как правило, практикуется такое, что при разработке (на локальном окружении) все ошибки показываются прямо на экране, дабы их было легче и более оперативно отлавливать и исправлять, а в боевой среде (на продакшене) ошибки не показываются вообще (т.к. для пользователя — это бесполезная информация) и вся информация о них пишется в лог-файл, который разработчик регулярно просматривает.
Настройки для логирования ошибок
- error_reporting
— это самый главный параметр. Он отвечает за то, сообщения об ошибках каких типов будут отображаться/писаться в лог-файл.Я считаю, что тут существует только 2 опции, которые возможно использовать:
- -1 (или E_ALL) — сообщается обо всех типах ошибок;
- 0 — не сообщается ни о каких типах ошибок
Я рекомендую использовать исключительно -1 (или E_ALL).
Т.к. приложение не должно иметь никаких ошибок, в принципе. Эту опцию можно задать через конфигурационный файл в php.ini или же прямо в php-скрипте посредством вызова функции error_reporting :Error_reporting(-1); error_reporting(E_ALL);
Кстати говоря, это единственная опция, которая имеется в языке PHP в виде функции. Все остальные опции возможно задать исключительно при помощи правки конфигурационного файла php.ini или же вызова функции ini_set() передав в неё, соответственно, необходимый параметр и значение для него.
- display_errors — этот параметр отвечает за непосредственный показ ошибок на экране после того, как она, собственно, произошла. Данный параметр может иметь значение 0 или 1 либо On / Off. Т.е. либо показывать ошибки на экране, либо нет.
- display_startup_errors — эта опция отвечает за показ ошибок, произошедших после запуска PHP. Например, если в конфигурационном файле есть синтаксическая ошибка, то информация о ней будет показана. Данный параметр может иметь значение 0 или 1 либо On / Off.
- log_errors — данная директива отвечает за то, чтобы записывать сообщения об ошибках в лог-файл. Данный параметр может иметь значение 0 или 1 либо On / Off. Т.е. записывать ошибки в лог или же нет.
- error_log — данная настройка отвечает за путь к файлу (лог-файлу), в который будут записываться все произошедшие ошибки приложения
- html_errors — эта же опция отвечает за формат отображения ошибок приложения. Если задана как 1 или On, то ошибка будет показана при помощи HTML’а, т.е. будет trace происхождения ошибки и всё будет достаточно информативно и красочно. Если же значение этой настройки задано как 0 или Off, то ошибки будут отображаться в виде обычного текста в виде небольшого числа строк.
1. Настройки для отображения ошибок на экране
ini_set("html_errors", 1);
ini_set("log_errors", 0);
2. Настройки для записи ошибок в лог-файл
Error_reporting(-1); // ini_set("error_reporting", -1);
ini_set("display_errors", 0);
ini_set("display_startup_errors", 0);
ini_set("log_errors", 1);
3. Настройки для одновременного отображения ошибок и их записи в лог-файл
Error_reporting(-1); // ini_set("error_reporting", -1);
ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
ini_set("log_errors", 1);
ini_set("html_errors", 1);
ini_set("error_log", "/var/log/php/error.log");
Так же, эти опции можно задать и в конфигурационном файле php.ini или же в файле вашего виртуального хоста.
На серъезных сайтах странно видеть, когда ошибки выводятся пользователю в браузер в самых неожиданных местах. Почему они появляются - это отдельный разговор. Но почему они выводятся? Ведь текст ошибок является информацией для дебага и предназначена для разработчика, а не для клиента.
Кроме того, именно эта служебная информация обычно помогает злым хакерам ломать сайт. В качестве классического примера можно привести вариант с выводом запроса при ошибке: "you have an error in query near WHERE id= " ... Большое спасибо. Подставляем после "WHERE id=..." строку "0 OR 1>0" и запрос выполняется по всей таблице. Если запрос на удаление, то...сами понимаете, весело =). Поэтому я всегда переменные в запросах заключаю в кавычки. На всякий случай...
Но я увлекся. Сегодня не об этом. Сегодня поговорим о том, как избежать вывода ошибок клиенту, сохранив при этом все сообщения вебмастеру на память.
Начнем, пожалуй, с краткого обзора видов ошибок в РНР.
Таблица 1. Описания ошибок в PHP4 (оригинальный список)Числовое значение |
Константа | Описание | Ловится/нет |
---|---|---|---|
1 | E_ERROR | Фатальные ошибки. Например, ошибка при обращении к памяти. Выполнение скрипта при этом прерывается. | нет |
2 | E_WARNING | Предупреждения (не фатальные ошибки). Выполнение скрипта не прерывается. | да |
4 | E_PARSE | Ошибки во время анализа синтаксиса. Генерируются парсером. | нет |
8 | E_NOTICE | Замечания (менее серьезные ошибки, чем предупреждения). Указывают на ситуацию, которая может стать причиной более серьезной ошибки, но могут случаться и в процессе нормальной работы скрипта. | да |
16 | E_CORE_ERROR | Ошибки во время загрузки РНР. Аналог E_ERROR, генерируется ядром РНР. | нет |
32 | E_CORE_WARNING | Предупреждения во время загрузки РНР Аналог E_WARNING, генерируется ядром РНР. | нет |
64 | E_COMPILE_ERROR | Фатальные ошибки во время компиляции кода. Аналог E_ERROR, генерируется зендовским движком. | нет |
128 | E_COMPILE_WARNING | Предупреждения во время компиляции кода. Аналог E_WARNING, генерируется зендовским движком. | нет |
256 | E_USER_ERROR | Пользовательская ошибка. | да |
512 | E_USER_WARNING | Пользовательское предупреждение. | да |
1024 | E_USER_NOTICE | Пользовательское замечание | да |
Нас интересуют те ошибки, которые мы можем перехватить. К ним относятся: E_WARNING, E_NOTICE и E_USER_*. Остальные виды ошибок перехвату не поддаются либо из-за того, что происходят они еще до окончания загрузки самого ядра РНР, либо из-за того, что происходят на этапе синтаксического анализа и компилирования РНР-кода, поэтому их вывод придется просто отключить:
ini_set ("display_errors",0);Но я предполагаю, что наши скрипты достаточно отлажены, чтобы в них не было элементарных синтаксических ошибок, поэтому потерять мы ничего не должны.
По умолчанию уровень ошибок в РНР имеет значение E_ALL & ~E_NOTICE (или 2039 в числовой форме), что означает, что мы пропускаем мимо ушей замечания, но сообщаем о всех остальных ошибках.
Поэтому изменим уровень вывода ошибок на E_ALL:
error_reporting (E_ALL);Теперь переопределим хэндлер ошибок и подставим вместо него нашу функцию , которая и будет заниматься теперь обработкой ошибок:
set_error_handler ("user_log");Рассмотрим эту функцию подробней. Ей передаются 5 параметров:
- код ошибки
- текст ошибки
- имя файла, в котором произошла ошибка
- строка в файле
- массив переменных
Возвращать эта функция ничего не обязана. Так как мы собираемся просматривать потом лог ошибок, то надо сделать запись лога, например, в файл так, чтобы нам потом было удобно с ним работать.
=(LOG_FILE_MAXSIZE*1024)) { //проверяем настройки, если установлен лог_ротэйт, //то "сдвигаем" старые файлы на один вниз и создаем пустой лог //если нет - чистим и пишем вместо старого лога if (LOG_ROTATE===true) { $i=1; //считаем старые логи в каталоге while (is_file(LOG_FILE_NAME.".".$i)) { $i++; } $i--; //у каждого из них по очереди увеличиваем номер на 1 while ($i>0) { rename(LOG_FILE_NAME."..".$i,LOG_FILE_NAME. "." .(1+$i--)); } rename (LOG_FILE_NAME,LOG_FILE_NAME.".1"); touch(LOG_FILE_NAME); } elseif(is_file(LOG_FILE_NAME)) { //если пишем логи сверху, то удалим //и создадим заново пустой файл unlink(LOG_FILE_NAME); touch(LOG_FILE_NAME); } } /* проверяем есть ли такой файл если нет - можем ли мы его создать если есть - можем ли мы писать в него */ if(!is_file(LOG_FILE_NAME)) { if (!touch(LOG_FILE_NAME)) { return "can\"t create log file"; } } elseif(!is_writable(LOG_FILE_NAME)) { return "can\"t write to log file"; } //обратите внимание на функцию, которой мы пишем лог. error_log($err_str, 3, LOG_FILE_NAME); } ?>
Можно было бы, конечно, использовать более логичное для таких целей хранилище - базу, но ведь ошибки, в большинстве своем, возникают именно при работе с базой, поэтому я бы на нее не полагался.
Собственно, это все. Остальное, я думаю, не составит для вас труда, особенно, если пользоваться функциями file (); & explode (); . А если все-таки составит, то вы можете воспользоваться [вот этим кодом ].
Предвидя вопрос "почему я не использовал CSV, который, казалось бы, логично использовать в этой ситуации?", отвечаю: сообщения об ошибках могут содержать неизвестное количество служебных символов (ака запятых и точек с запятой), что явно затруднило бы разбор CSV. Да и не собираюсь я просматривать лог в Экселе.
Еще разные мысли на эту тему:
- при устаревании лога gz"иповать файл и складывать его в архив;
- то же, но с посылкой на почту;
- при возникновении критических ошибок - слать мэйл (см. пример из мануала по функции