_writeLog) { $this->_startTime = date('Y-m-d H:i:s'); } $this->_idSession = Zend_Session::getId(); $this->_clearOldHistory(); return $this; } public function getOption($name) { if (isset($this->_options[$name])) { return $this->_options[$name]; } return null; } public function setLogBacktrace($flag) { $this->_logBacktrace = (bool) $flag; } public function setWriteLog($flag) { $this->_writeLog = (bool) $flag; } /** * @return Qs_Debug * @throws Qs_Exception */ public static function getInstance() { if (null === Qs_Debug::$_instance) { if (!Zend_Registry::isRegistered('config')) { throw new Qs_Exception('Config is not registered in Zend_Registry'); } Qs_Debug::$_instance = new Qs_Debug(Zend_Registry::get('config')->debug); } return Qs_Debug::$_instance; } public function setErrorReporting($errorReporting) { $this->_previousErrorReporting = $this->_errorReporting; $this->_errorReporting = $errorReporting; error_reporting($errorReporting); set_error_handler(array($this, 'errorHandler'), $this->_errorReporting); } public function getErrorReporting() { return $this->_errorReporting; } public function restoreErrorReporting() { $this->_errorReporting = $this->_previousErrorReporting; set_error_handler(array($this, 'errorHandler'), $this->_errorReporting); } protected function _clearOldHistory() { if ($this->_writeLog) { if ($this->_count > $this->getOption('clearOverflow')) { $select = $this->_db->select(); $select->from($this->_tableName, 'id') ->order('id DESC') ->limit(1, $this->getOption('clearLimit') - 1); if (false !== ($maxId = $this->_db->fetchOne($select))) { $this->_db->delete($this->_tableName, 'id < ' . $this->_db->quote($maxId, Qs_Db::INT_TYPE)); } } } } public function errorHandler($errNo, $errMsg, $filename, $linenNum, $errContext) { // Ignore warnings and notices from lib/Zend if (in_array($errNo, array(E_WARNING, E_NOTICE)) && false !== strpos($filename, '/Zend/')) { return true; } $this->_writeLog([ //Деякі меседжі містять в собі шляхи до файлів 'message' => $this->_filterFilePaths($errMsg), 'errorNumber' => $errNo, 'file' => $this->_filterFilePaths($filename), 'line' => $linenNum, ]); return true; } public static function log($message, $errNo = E_WARNING) { Qs_Debug::getInstance()->errorHandler($errNo, $message, null, null, null); } public static function logStdError($message) { if (false !== ($handle = fopen('php://stderr', 'w'))) { fwrite($handle, $message); fclose($handle); } } protected function _writeLog(array $data) { if (!$this->_writeLog) { return false; } if (!isset($data['message'])) { return false; } if (!isset($data['errorNumber'])) { $data['errorNumber'] = 0; } ksort($data); $uid = md5(implode('', $data)); $data = array_merge($data, [ 'uid' => $uid, 'backtrace' => $this->_logBacktrace ? gzcompress(serialize(Qs_Debug::_getBackTrace()), 9) : null, 'ip' => $_SERVER['REMOTE_ADDR'], 'count' => new Zend_Db_Expr('count + 1'), 'idSession' => $this->_idSession, 'serverName' => $_SERVER['SERVER_NAME'], 'url' => (string)Qs_Request::getUrl(), 'added' => date('Y-m-d H:i:s'), ]); $db = Qs_Db::getInstance(); $table = Qs_Db::getTableName('Log'); $updated = $db->update($table, $data, $db->quoteInto('uid = ?', $uid)); if (!$updated) { $data['count'] = 1; $db->insert($table, $data); } $this->_writed = true; return true; } /** * Remove objects instances from array * (раніше приймав занчення по посиланню, в РНР 5.4 вилізла проблема, debug_backtrace повертає масив з посиланнями * на об'єкти, тому зміни в $array моломи сайт) * @param array $array * @return array */ protected static function _removeObjects(array $array) { $result = array(); foreach ($array as $key => $value) { if (is_array($value)) { $result[$key] = Qs_Debug::_removeObjects($value); } else if (is_object($value)) { $result[$key] = get_class($value) . ' Object'; } else { $result[$key] = $value; } } return $result; } protected static function _getBackTrace() { $backtrace = debug_backtrace(); array_shift($backtrace); array_shift($backtrace); array_shift($backtrace); $backtrace = Qs_Debug::_removeObjects($backtrace); return $backtrace; } public function getDebugInformation() { if (!$this->_writeLog || !$this->_writed) { return array(); } $select = $this->_db->select(); $select->from($this->_getPair(), array('added', 'errorNumber', 'message', 'file', 'line')); $select->where("`{$this->_tableAlias}`.`added` >= " . $this->_db->quote($this->_startTime)); $select->where("`{$this->_tableAlias}`.`idSession` = " . $this->_db->quote($this->_idSession)); $select->order($this->_tableAlias . '.id'); $list = $this->_db->fetchAll($select); $errorTypes = $this->getConfigArray('errorTypes'); foreach ($list as &$row) { $row['errorType'] = Qs_Array::get($errorTypes, $row['errorNumber']); } return $list; } public function getSessionErrorsCount() { if (!$this->_writeLog) { return array(); } $select = $this->_db->select(); $select->from($this->_getPair(null, 'l'), 'COUNT(*)'); $select->where("`l`.`idSession` = ?", $this->_idSession); return $this->_db->fetchOne($select); } public static function getExceptionMessage(Exception $exception) { $firstTracePoint = current($exception->getTrace()); $html = '
' . '' . $firstTracePoint['class'] . $firstTracePoint['type'] . $firstTracePoint['function'] . '' . ' uncaught exception ' . 'in ' . $exception->getFile() . ' on line ' . $exception->getLine() . '' . '
' . '' . nl2br(strip_tags($exception->getMessage())) . '' . '
'; if ($exception instanceof Zend_Db_Statement_Exception && false !== ($lastQueryProfile = Qs_Db::getInstance()->getProfiler()->getLastQueryProfile()) ) { if (Qs_Request::isXmlHttpRequest()) { $html .= "\nLast Query:\n" . Qs_SqlFormatter::format($lastQueryProfile->getQuery(), false); } else { $html .= '
Last query:
' . Qs_SqlFormatter::format($lastQueryProfile->getQuery()); } } return $html; } public static function getExceptionPlainMessage(Exception $exception) { $firstTracePoint = current($exception->getTrace()); $text = $firstTracePoint['class'] . $firstTracePoint['type'] . $firstTracePoint['function'] . ' ' . 'uncaught exception in ' . $exception->getFile() . ' on line ' . $exception->getLine() . ' - ' . strip_tags($exception->getMessage()); if ($exception instanceof Zend_Db_Statement_Exception && false !== ($lastQueryProfile = Qs_Db::getInstance()->getProfiler()->getLastQueryProfile()) ) { $text .= '. Last query: ' . $lastQueryProfile->getQuery(); } return $text; } /** * @param Exception $exception * @return string */ public static function getExceptionBacktrace($exception) { $debugBacktrace = $exception->getTrace(); $debugBacktrace = Qs_Debug::_removeObjects($debugBacktrace); return 'BACKTRACE
' . print_r($debugBacktrace, true) . '
'; } protected function _sendExceptionNotification($exception) { $message = Qs_Debug::getExceptionMessage($exception); $backtrace = Qs_Debug::getExceptionBacktrace($exception); $html = $message . '
' . 'URL: ' . Qs_Request::getUrl() . ''; foreach (array('_GET', '_POST', '_COOKIE', '_SERVER', '_ENV') as $name) { $html .= '
$' . $name . '
' . htmlspecialchars(print_r($GLOBALS[$name], true)) . '
'; } $html .= '
' . $backtrace; $notifyEmails = $this->getOption('notifyEmails'); if (is_array($notifyEmails) && !empty($notifyEmails)) { $headers = 'From: debug@' . $_SERVER['SERVER_NAME'] . "\n" . 'Reply-To: no-reply@' . $_SERVER['SERVER_NAME'] . "\n" . 'Content-Type: text/html' . "\n" . 'X-Mailer: PHP/' . phpversion(); foreach ($notifyEmails as $email) { mail($email, 'Error Dump from ' . $_SERVER['SERVER_NAME'], $html, $headers); } } return $this; } protected function _filterFilePaths($file) { if (!$this->_root) { $this->_root = dirname(BASE_PATH); } return str_replace($this->_root . '/', '', $file); } /** * @param Exception $exception */ public function exceptionHandler($exception) { self::logStdError('CMF Exception: ' . self::getExceptionPlainMessage($exception)); $message = Qs_Debug::getExceptionMessage($exception); $backtrace = Qs_Debug::getExceptionBacktrace($exception); $this->_sendExceptionNotification($exception); $this->_writeLog([ 'message' => $exception->getMessage(), 'exceptionCode' => $exception->getCode(), 'file' => $this->_filterFilePaths($exception->getFile()), 'line' => $exception->getLine(), ]); if (Qs_Constant::get('DEBUG')) { die($message . '
' . $backtrace); } die('Internal error'); } public static function processException($exception) { Qs_Debug::getInstance()->exceptionHandler($exception); } public static function processExceptionSilent(Exception $exception) { Qs_Debug::getInstance()->silentExceptionHandler($exception); } public function silentExceptionHandler(Exception $exception) { $this->_writeLog([ 'message' => $exception->getMessage(), 'exceptionCode' => $exception->getCode(), 'file' => $this->_filterFilePaths($exception->getFile()), 'line' => $exception->getLine(), ]); $this->_sendExceptionNotification($exception); } public static function dumpSql($sql) { echo Qs_SqlFormatter::format('' . $sql); } }