Проверка журналов ошибок является распространенным способом обнаружения ошибок и ошибок. Мы также можем отображать ошибки на экране нашего сервера разработки, или мы даже можем использовать отличные инструменты, такие как
firePHP, для отображения наших ошибок PHP и предупреждений в нашей консоли firebug. Это круто, но мы можем видеть только наши ошибки / предупреждения сеанса. Если мы хотим увидеть чужие ошибки, нам нужно проверить журнал ошибок.
tail -f — наш друг, но нам нужно просмотреть все предупреждения всех сессий, чтобы увидеть наши. Поэтому я хочу создать инструмент для мониторинга моих PHP-приложений в режиме реального времени. Давайте начнем:
В чем идея? Идея заключается в том, чтобы перехватывать все ошибки и предупреждения PHP во время выполнения и отправлять их на HTTP-сервер node.js. Этот сервер будет работать аналогично серверу чата, но наши клиенты смогут только читать журналы сервера. В основном приложения состоят из трех частей: сервер node.js, веб-клиент (html5) и серверная часть (PHP). Позвольте мне немного объяснить каждую часть:
Узел Сервер
По сути, он состоит из двух частей: http-сервер для обработки ошибок / предупреждений PHP и веб-сокет- сервер для управления связью в реальном времени с браузером. Когда я говорю, что использую веб-сокеты, это означает, что веб-клиент будет работать только с браузером с поддержкой веб-сокетов, таким как Chrome. В любом случае это довольно простой обмен с сервера websocket на сервер socket.io, чтобы использовать его с любым браузером. Но веб-сокеты, кажется, будущее, поэтому я буду использовать веб-сокеты в этом примере.
Http сервер:
http.createServer(function (req, res) { var remoteAdrress = req.socket.remoteAddress; if (allowedIP.indexOf(remoteAdrress) >= 0) { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Ok\n'); try { var parsedUrl = url.parse(req.url, true); var type = parsedUrl.query.type; var logString = parsedUrl.query.logString; var ip = eval(parsedUrl.query.logString)[0]; if (inspectingUrl == "" || inspectingUrl == ip) { clients.forEach(function(client) { client.write(logString); }); } } catch(err) { console.log("500 to " + remoteAdrress); res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('System Error\n'); } } else { console.log("401 to " + remoteAdrress); res.writeHead(401, { 'Content-Type': 'text/plain' }); res.end('Not Authorized\n'); } }).listen(httpConf.port, httpConf.host);
и сервер веб-сокетов:
var inspectingUrl = undefined; ws.createServer(function(websocket) { websocket.on('connect', function(resource) { var parsedUrl = url.parse(resource, true); inspectingUrl = parsedUrl.query.ip; clients.push(websocket); }); websocket.on('close', function() { var pos = clients.indexOf(websocket); if (pos >= 0) { clients.splice(pos, 1); } }); }).listen(wsConf.port, wsConf.host);
Если вы хотите узнать больше о node.js и увидеть больше примеров, загляните на отличный сайт: http://nodetuts.com/ . На этом сайте Педро Тейшейра покажет примеры и руководства по node.js. На самом деле мой http.ces сервер node.js представляет собой сочетание двух руководств с этого сайта.
Веб-клиент.
Веб-клиент представляет собой простое приложение веб-сокетов. Мы будем обрабатывать соединение через websockets, переподключаться, если оно умирает, и немного больше. Я основан на демо чата node.js
<?php $ip = filter_input(INPUT_GET, 'ip', FILTER_SANITIZE_STRING); ?> Real time <?= $ip ?> monitor <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script><script type="text/javascript">// <![CDATA[ selectedIp = '<?= $ip ?>'; // ]]></script> <script type="text/javascript" src="js.js"></script> </pre> <div id="toolbar"> <ul id="status"> <li>Socket status: <span id="socketStatus">Conecting ...</span></li> <li>IP: <!--?= $ip == '' ? 'all' : $ip . " <a href="?ip="-->[all]" ?></li> <li>count: <span id="count">0</span></li> </ul> </div> <pre>
И волшебство JavaScript
var timeout = 5000; var wsServer = '192.168.2.2:8880'; var unread = 0; var focus = false; var count = 0; function updateCount() { count++; $("#count").text(count); } function cleanString(string) { return string.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); } function updateUptime () { var now = new Date(); $("#uptime").text(now.toRelativeTime()); } function updateTitle(){ if (unread) { document.title = "(" + unread.toString() + ") Real time " + selectedIp + " monitor"; } else { document.title = "Real time " + selectedIp + " monitor"; } } function pad(n) { return ("0" + n).slice(-2); } function startWs(ip) { try { ws = new WebSocket("ws://" + wsServer + "?ip=" + ip); $('#toolbar').css('background', '#65A33F'); $('#socketStatus').html('Connected to ' + wsServer); //console.log("startWs:" + ip); //listen for browser events so we know to update the document title $(window).bind("blur", function() { focus = false; updateTitle(); }); $(window).bind("focus", function() { focus = true; unread = 0; updateTitle(); }); } catch (err) { //console.log(err); setTimeout(startWs, timeout); } ws.onmessage = function(event) { unread++; updateTitle(); var now = new Date(); var hh = pad(now.getHours()); var mm = pad(now.getMinutes()); var ss = pad(now.getSeconds()); var timeMark = '[' + hh + ':' + mm + ':' + ss + '] '; logString = eval(event.data); var host = logString[0]; var line = "<table class='message'><tr><td width='1%' class='date'>" + timeMark + "</td><td width='1%' valign='top' class='host'><a href=?ip=" + host + ">" + host + "</a></td>"; line += "<td class='msg-text' width='98%'>" + logString[1]; + "</td></tr>"; if (logString[2]) { line += "<tr><td> </td><td colspan='3' class='msg-text'>" + logString[2] + "</td></tr>"; } $('#log').append(line); updateCount(); window.scrollBy(0, 100000000000000000); }; ws.onclose = function(){ //console.log("ws.onclose"); $('#toolbar').css('background', '#933'); $('#socketStatus').html('Disconected'); setTimeout(function() {startWs(selectedIp)}, timeout); } } $(document).ready(function() { startWs(selectedIp); });
Серверная часть:
Серверная часть будет молча обрабатывать все предупреждения и ошибки PHP и отправлять их на сервер узла. Идея состоит в том, чтобы разместить минимальную строку кода PHP в начале приложения, которое мы хотим отслеживать. Представьте себе следующий фрагмент кода PHP
$a = $var[1]; $a = 1/0; class Dummy { static function err() { throw new Exception("error"); } } Dummy1::err();
будет выдано:
Уведомление: неопределенная переменная: var
Предупреждение: Деление на ноль
Неполученное исключение «Исключение» с сообщением «ошибка»
Поэтому мы добавим нашу маленькую библиотеку, чтобы перехватывать эти ошибки и отправлять их на сервер узла.
include('client/NodeLog.php'); NodeLog::init('192.168.2.2'); $a = $var[1]; $a = 1/0; class Dummy { static function err() { throw new Exception("error"); } } Dummy1::err();
Скрипт будет работать так же, как и первая версия, но если мы запустим наш сервер node.js в консоли:
$ node server.js HTTP server started at 192.168.2.2::5672 Web Socket server started at 192.168.2.2::8880
Мы увидим эти ошибки / предупреждения в режиме реального времени при запуске нашего браузера
Здесь мы видим небольшой скринкаст с работающим приложением:
Это библиотека на стороне сервера:
class NodeLog { const NODE_DEF_HOST = '127.0.0.1'; const NODE_DEF_PORT = 5672; private $_host; private $_port; /** * @param String $host * @param Integer $port * @return NodeLog */ static function connect($host = null, $port = null) { return new self(is_null($host) ? self::$_defHost : $host, is_null($port) ? self::$_defPort : $port); } function __construct($host, $port) { $this->_host = $host; $this->_port = $port; } /** * @param String $log * @return Array array($status, $response) */ public function log($log) { list($status, $response) = $this->send(json_encode($log)); return array($status, $response); } private function send($log) { $url = "http://{$this->_host}:{$this->_port}?logString=" . urlencode($log); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_NOBODY, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return array($status, $response); } static function getip() { $realip = '0.0.0.0'; if ($_SERVER) { if ( isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] ) { $realip = $_SERVER["HTTP_X_FORWARDED_FOR"]; } elseif ( isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER["HTTP_CLIENT_IP"] ) { $realip = $_SERVER["HTTP_CLIENT_IP"]; } else { $realip = $_SERVER["REMOTE_ADDR"]; } } else { if ( getenv('HTTP_X_FORWARDED_FOR') ) { $realip = getenv('HTTP_X_FORWARDED_FOR'); } elseif ( getenv('HTTP_CLIENT_IP') ) { $realip = getenv('HTTP_CLIENT_IP'); } else { $realip = getenv('REMOTE_ADDR'); } } return $realip; } public static function getErrorName($err) { $errors = array( E_ERROR => 'ERROR', E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR', E_WARNING => 'WARNING', E_PARSE => 'PARSE', E_NOTICE => 'NOTICE', E_STRICT => 'STRICT', E_DEPRECATED => 'DEPRECATED', E_CORE_ERROR => 'CORE_ERROR', E_CORE_WARNING => 'CORE_WARNING', E_COMPILE_ERROR => 'COMPILE_ERROR', E_COMPILE_WARNING => 'COMPILE_WARNING', E_USER_ERROR => 'USER_ERROR', E_USER_WARNING => 'USER_WARNING', E_USER_NOTICE => 'USER_NOTICE', E_USER_DEPRECATED => 'USER_DEPRECATED', ); return $errors[$err]; } private static function set_error_handler($nodeHost, $nodePort) { set_error_handler(function ($errno, $errstr, $errfile, $errline) use($nodeHost, $nodePort) { $err = NodeLog::getErrorName($errno); /* if (!(error_reporting() & $errno)) { // This error code is not included in error_reporting return; } */ $log = array( NodeLog::getip(), "<strong class="{$err}">{$err}</strong> {$errfile}:{$errline}", nl2br($errstr) ); NodeLog::connect($nodeHost, $nodePort)->log($log); return false; }); } private static function register_exceptionHandler($nodeHost, $nodePort) { set_exception_handler(function($exception) use($nodeHost, $nodePort) { $exceptionName = get_class($exception); $message = $exception->getMessage(); $file = $exception->getFile(); $line = $exception->getLine(); $trace = $exception->getTraceAsString(); $msg = count($trace) > 0 ? "Stack trace:\n{$trace}" : null; $log = array( NodeLog::getip(), nl2br("<strong class="ERROR">Uncaught exception '{$exceptionName}'</strong> with message '{$message}' in {$file}:{$line}"), nl2br($msg) ); NodeLog::connect($nodeHost, $nodePort)->log($log); return false; }); } private static function register_shutdown_function($nodeHost, $nodePort) { register_shutdown_function(function() use($nodeHost, $nodePort) { $error = error_get_last(); if ($error['type'] == E_ERROR) { $err = NodeLog::getErrorName($error['type']); $log = array( NodeLog::getip(), "<strong class="{$err}">{$err}</strong> {$error['file']}:{$error['line']}", nl2br($error['message']) ); NodeLog::connect($nodeHost, $nodePort)->log($log); } echo NodeLog::connect($nodeHost, $nodePort)->end(); }); } private static $_defHost = self::NODE_DEF_HOST; private static $_defPort = self::NODE_DEF_PORT; /** * @param String $host * @param Integer $port * @return NodeLog */ public static function init($host = self::NODE_DEF_HOST, $port = self::NODE_DEF_PORT) { self::$_defHost = $host; self::$_defPort = $port; self::register_exceptionHandler($host, $port); self::set_error_handler($host, $port); self::register_shutdown_function($host, $port); $node = self::connect($host, $port); $node->start(); return $node; } private static $time; private static $mem; public function start() { self::$time = microtime(TRUE); self::$mem = memory_get_usage(); $log = array(NodeLog::getip(), "<strong class="OK">Start</strong> >>>> {$_SERVER['REQUEST_URI']}"); $this->log($log); } public function end() { $mem = (memory_get_usage() - self::$mem) / (1024 * 1024); $time = microtime(TRUE) - self::$time; $log = array(NodeLog::getip(), "<strong class="OK">End</strong> <<<< mem: {$mem} time {$time}"); $this->log($log); } }
И конечно полный код на gitHub:
RealTimeMonitor