Files
2025-01-06 20:47:25 +01:00

432 lines
17 KiB
PHP

<?php
if (!defined('_PS_VERSION_')) {
exit;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// FireLogger for PHP (server-side library)
// http://firelogger.binaryage.com/#php
//
// see test.php for sample usage
//
// protocol specs: http://wiki.github.com/darwin/firelogger
//
// PHP <5.3.x compatibility
if (!defined('E_DEPRECATED')) {
define('E_DEPRECATED', 8192);
}
if (!defined('E_USER_DEPRECATED')) {
define('E_USER_DEPRECATED', 16384);
}
// some directives, you may define them before including firelogger.php
if (!defined('FIRELOGGER_VERSION')) {
define('FIRELOGGER_VERSION', '0.3');
}
if (!defined('FIRELOGGER_API_VERSION')) {
define('FIRELOGGER_API_VERSION', 1);
}
if (!defined('FIRELOGGER_MAX_PICKLE_DEPTH')) {
define('FIRELOGGER_MAX_PICKLE_DEPTH', 10);
}
if (!defined('FIRELOGGER_ENCODING')) {
define('FIRELOGGER_ENCODING', 'UTF-8');
}
// ... there is more scattered throught this source, hint: search for constants beginning with "FIRELOGGER_"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this class represents logger object
// logger has name and you can ask him to perform logging for you like this:
//
// $ajax = new FireLogger("ajax");
// $ajax->log("info", "hello from ajax logger");
// $ajax->log("have", "fun!");
//
// you may also use shortcut helper functions to log into default logger
//
// flog("Hello from PHP!");
// fwarn("Warning, %s alert!", "gertruda");
// ...
//
class FireLogger
{
// global state kept under FireLogger "namespace"
public static $enabled = true; // enabled by default, but see the code executed after class
public static $counter = 0; // an aid for ordering log records on client
public static $loggers = array(); // the array of all instantiated fire-loggers during request
public static $default; // points to default logger
public static $error; // points to error logger (for errors trigerred by PHP)
public static $oldErrorHandler;
public static $oldExceptionHandler;
public static $clientVersion = '?';
public static $recommendedClientVersion = '1.2';
// logger instance data
public $name; // [optional] logger name
public $style; // [optional] CSS snippet for logger icon in FireLogger console
public $logs = array(); // array of captured log records, this will be encoded into headers during
public $levels = array('debug', 'warning', 'info', 'error', 'critical'); // well-known log levels (originated in Python)
//------------------------------------------------------------------------------------------------------
public function __construct($name = 'logger', $style = null)
{
$this->name = $name;
$this->style = $style;
FireLogger::$loggers[] = $this;
}
//------------------------------------------------------------------------------------------------------
private function pickle($var, $level = 0)
{
if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) {
return $var;
} elseif (is_string($var)) {
return @iconv('UTF-16', 'UTF-8//IGNORE', iconv(FIRELOGGER_ENCODING, 'UTF-16//IGNORE', $var)); // intentionally @
} elseif (is_array($var)) {
static $marker;
if ($marker === null) {
$marker = uniqid("\x00", true);
} // detects recursions
if (isset($var[$marker])) {
return '*RECURSION*';
} elseif ($level < FIRELOGGER_MAX_PICKLE_DEPTH || !FIRELOGGER_MAX_PICKLE_DEPTH) {
$var[$marker] = true;
$res = array();
foreach ($var as $k => &$v) {
if ($k !== $marker) {
$res[self::pickle($k)] = self::pickle($v, $level + 1);
}
}
unset($var[$marker]);
return $res;
} else {
return '...';
}
} elseif (is_object($var)) {
$arr = (array) $var;
$arr['__class##'] = get_class($var);
static $list = array(); // detects recursions
if (in_array($var, $list, true)) {
return '*RECURSION*';
} elseif ($level < FIRELOGGER_MAX_PICKLE_DEPTH || !FIRELOGGER_MAX_PICKLE_DEPTH) {
$list[] = $var;
$res = array();
foreach ($arr as $k => &$v) {
if ($k[0] === "\x00") {
$k = substr($k, strrpos($k, "\x00") + 1);
}
$res[self::pickle($k)] = self::pickle($v, $level + 1);
}
array_pop($list);
return $res;
} else {
return '...';
}
} elseif (is_resource($var)) {
return '*' . get_resource_type($var) . ' resource*';
} else {
return '*unknown type*';
}
}
//------------------------------------------------------------------------------------------------------
private function extract_file_line($trace, $trace_index = 2)
{
while (count($trace) && !array_key_exists('file', $trace[$trace_index])) {
array_shift($trace);
}
$thisFile = $trace[$trace_index]['file'];
while (count($trace) && (array_key_exists('file', $trace[$trace_index]) && $trace[$trace_index]['file']==$thisFile)) {
array_shift($trace);
}
while (count($trace) && !array_key_exists('file', $trace[$trace_index])) {
array_shift($trace);
}
if (count($trace)==$trace_index) {
return array("?", "0");
}
$file = $trace[$trace_index]['file'];
$line = $trace[$trace_index]['line'];
return array($file, $line);
}
//------------------------------------------------------------------------------------------------------
private function extract_trace($trace)
{
$t = array();
$f = array();
foreach ($trace as $frame) {
// prevent notices about invalid indices, wasn't able to google smart solution, PHP is dumb ass
$frame += array('file' => null, 'line' => null, 'class' => null, 'type' => null, 'function' => null, 'object' => null, 'args' => null);
$t[] = array(
$frame['file'],
$frame['line'],
$frame['class'].$frame['type'].$frame['function'],
$frame['object']
);
$f[] = $frame['args'];
};
return array($t, $f);
}
//------------------------------------------------------------------------------------------------------
public function log()
{
if (!FireLogger::$enabled) {
return;
} // no-op
$args = func_get_args();
$fmt = '';
$level = 'debug';
if (is_string($args[0]) && in_array($args[0], $this->levels)) {
$level = array_shift($args);
}
if (is_string($args[0])) {
$fmt = array_shift($args);
}
$time = microtime(true);
$item = array(
'name' => $this->name,
'args' => array(),
'level' => $level,
'timestamp' => $time,
'order' => FireLogger::$counter++, // PHP is really fast, timestamp has insufficient resolution for log records ordering
'time' => gmdate('H:i:s', (int)$time).'.'.substr(fmod($time, 1.0), 2, 3), // '23:53:13.396'
'template' => $fmt,
'message' => $fmt // TODO: render reasonable plain text message
);
if ($this->style) {
$item['style'] = $this->style;
}
if (count($args) && $args[0] instanceof Exception) {
// exception with backtrace
$e = $args[0];
$trace = $e->getTrace();
$ti = $this->extract_trace($trace);
$item['exc_info'] = array(
$e->getMessage(),
$e->getFile(),
$ti[0]
);
$item['exc_frames'] = $ti[1];
$item['exc_text'] = 'exception';
$item['template'] = $e->getMessage();
$item['code'] = $e->getCode();
$item['pathname'] = $e->getFile();
$item['lineno'] = $e->getLine();
} else {
// rich log record
$trace = debug_backtrace();
list($file, $line) = $this->extract_file_line($trace);
$data = array();
$item['pathname'] = $file;
$item['lineno'] = $line;
foreach ($args as $arg) {
// override file/line in case we've got passed FireLoggerFileLine
if ($arg instanceof FireLoggerFileLine) {
$item['pathname'] = $arg->file;
$item['lineno'] = $arg->line;
continue; // do not process this arg
}
// override backtrace in case we've got passed FireLoggerBacktrace
if ($arg instanceof FireLoggerBacktrace) {
$ti = $this->extract_trace($arg->trace);
$item['exc_info'] = array(
'',
'',
$ti[0]
);
$item['exc_frames'] = $ti[1];
continue; // do not process this arg
}
$data[] = $arg;
}
$item['args'] = $data;
}
$this->logs[] = self::pickle($item);
}
//------------------------------------------------------------------------------------------------------
public static function firelogger_error_handler($errno, $errstr, $errfile, $errline, $errcontext)
{
if (headers_sent()) {
return false; // calls default error handler
}
if (!defined('FIRELOGGER_NO_ERROR_FILTERING')) {
// FIRELOGGER_NO_ERROR_FILTERING causes error_reporting() settings will have no effect
if (!($errno & error_reporting())) {
return;
}
}
$errors = array(
E_WARNING => 'Warning',
E_USER_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_USER_NOTICE => 'Notice',
E_STRICT => 'Strict standards',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'Deprecated',
);
$no = isset($errors[$errno]) ? $errors[$errno] : 'Unknown error';
FireLogger::$error->log('warning', "$no: $errstr", new FireLoggerFileLine($errfile, $errline), new FireLoggerBacktrace(debug_backtrace()));
}
//------------------------------------------------------------------------------------------------------
//
// Encoding handler
// * collects all log messages from all FireLogger instances
// * encodes them into HTTP headers
//
// see protocol specs at http://wiki.github.com/darwin/firelogger
//
public static function handler()
{
if (headers_sent($file, $line)) {
trigger_error("Cannot send FireLogger headers after output have been sent" . ($file ? " (output started at $file:$line)." : "."), E_USER_WARNING);
return;
}
// detector for fatal errors
if (function_exists('error_get_last')) {
$error = error_get_last();
if (in_array($error['type'], array(E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE))) {
FireLogger::$default->log(new ErrorException($error['message'], 0, $error['type'], $error['file'], $error['line']));
}
}
$logs = array();
foreach (FireLogger::$loggers as $logger) {
$logs = array_merge($logs, $logger->logs);
}
// final encoding
$id = dechex(mt_rand(0, 0xFFFF)).dechex(mt_rand(0, 0xFFFF)); // mt_rand is not working with 0xFFFFFFFF
$json = json_encode(array('logs' => $logs));
$res = str_split(base64_encode($json), 76); // RFC 2045
foreach ($res as $k => $v) {
header("FireLogger-$id-$k:$v");
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// helper class for passing file/line override into log methods
class FireLoggerFileLine
{
public $file;
public $line;
public function __construct($file, $line)
{
$this->file = $file;
$this->line = $line;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// helper class for passing backtrace override into log methods
class FireLoggerBacktrace
{
public $trace;
public function __construct($trace)
{
$this->trace = $trace;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// decide if firelogger should be enabled
if (!defined('FIRELOGGER_NO_VERSION_CHECK')) {
if (!isset($_SERVER['HTTP_X_FIRELOGGER'])) {
FireLogger::$enabled = false;
} else {
FireLogger::$clientVersion = $_SERVER['HTTP_X_FIRELOGGER'];
if (FireLogger::$clientVersion!=FireLogger::$recommendedClientVersion) {
error_log("FireLogger for PHP (v".FIRELOGGER_VERSION.") works best with FireLogger extension of version ".FireLogger::$recommendedClientVersion.". You are currently using extension v".FireLogger::$clientVersion.". Please visit the homepage and install matching versions => http://firelogger.binaryage.com/php");
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// test if firelogger password matches
if (!defined('FIRELOGGER_NO_PASSWORD_CHECK') && defined('FIRELOGGER_PASSWORD') && FireLogger::$enabled) {
if (isset($_SERVER['HTTP_X_FIRELOGGERAUTH'])) {
$clientHash = $_SERVER['HTTP_X_FIRELOGGERAUTH'];
$serverHash = md5("#FireLoggerPassword#".FIRELOGGER_PASSWORD."#");
if ($clientHash!==$serverHash) { // passwords do not match
FireLogger::$enabled = false;
trigger_error("FireLogger password do not match. Have you specified correct password FireLogger extension?");
}
} else {
FireLogger::$enabled = false; // silently disable firelogger in case client didn't provide requested password
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// register default logger for convenience
if (!defined('FIRELOGGER_NO_OUTPUT_HANDLER')) {
if (FireLogger::$enabled) {
ob_start();
} // start output buffering (in case firelogger should be enabled)
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// register default logger for convenience
if (!defined('FIRELOGGER_NO_DEFAULT_LOGGER')) {
FireLogger::$default = new FireLogger('php', 'background-color: #767ab6'); // register default firelogger with official PHP logo color :-)
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// shortcut functions for convenience
if (!defined('FIRELOGGER_NO_CONFLICT')) {
function flog()
{
$args = func_get_args();
call_user_func_array(array(FireLogger::$default, 'log'), $args);
}
function fwarn()
{
$args = func_get_args();
array_unshift($args, 'warning');
call_user_func_array(array(FireLogger::$default, 'log'), $args);
}
function ferror()
{
$args = func_get_args();
array_unshift($args, 'error');
call_user_func_array(array(FireLogger::$default, 'log'), $args);
}
function finfo()
{
$args = func_get_args();
array_unshift($args, 'info');
call_user_func_array(array(FireLogger::$default, 'log'), $args);
}
function fcritical()
{
$args = func_get_args();
array_unshift($args, 'critical');
call_user_func_array(array(FireLogger::$default, 'log'), $args);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// register global handler for uncaught exceptions
if (!defined('FIRELOGGER_NO_EXCEPTION_HANDLER')) {
FireLogger::$oldExceptionHandler = set_exception_handler(array(FireLogger::$default, 'log'));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// register global handler for errors
if (!defined('FIRELOGGER_NO_ERROR_HANDLER')) {
FireLogger::$error = new FireLogger('error', 'background-color: #f00');
FireLogger::$oldErrorHandler = set_error_handler(array('FireLogger', 'firelogger_error_handler'));
}
// enable encoding handler
if (FireLogger::$enabled) {
register_shutdown_function(array('FireLogger', 'handler'));
}