Проверка журналов ошибок является распространенным способом обнаружения ошибок и ошибок. Мы также можем отображать ошибки на экране нашего сервера разработки, или мы даже можем использовать отличные инструменты, такие как
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