PHP调试封装类
//默认将显示错误关闭
ini_set('display_errors', true);
//默认将读外部文件的自动转义关闭
ini_set("magic_quotes_runtime", 0);
//设置默认时区
date_default_timezone_set('PRC');
// 调试参数 __debug 的值
define('_DEBUG_PASS', 'miaosha_debug'); // TODO: 为了避免调试信息泄漏,请定义自己的密钥
// 是否开启调试状态
define('_IS_DEBUG', false);
// 异常信息等级
define('_ERROR_LEVEL', E_ALL);
//一般在入口文件写入,url接收参数模拟开启调试
/*---Debug Begin---*/ if((defined('_IS_DEBUG') && _IS_DEBUG) || (isset($_REQUEST['__debug']) && strpos($_REQUEST['__debug'], _DEBUG_PASS) !== false)) { // $_REQUEST['__debug'] = _DEBUG_PASS + 1 (2 数字表示级别 ) $debug_level = intval(substr($_REQUEST['__debug'], -1)); if ($debug_level > 0) { define('DEBUG_LEVEL', $debug_level ); } else { define('DEBUG_LEVEL', 1); } //Debug模式将错误打开 ini_set('display_errors', true); //设置错误级别 error_reporting(_ERROR_LEVEL); //开启ob函数 ob_start(); //Debug开关打开 common\DebugLog::_init(); //注册shutdown函数用来Debug显示 register_shutdown_function(array('common\DebugLog', '_show')); } else { define('DEBUG_LEVEL', 0); } /*---Debug End---*/
//模拟调用 \common\DebugLog::_time('_init.php, start page');
//DebugLog.php <?php /** * 调试日志操作类 * DEBUG_LEVEL=0的时候不会在后端运行, * DEBUG_LEVEL=1的时候会记录错误、警告信息以及资源调用的耗时汇总统计, * DEBUG_LEVEL=2的时候,会记录全部的数据 * 如果在参数列表中出现 __DEBUG_LEVEL ,则会强制覆盖 DEBUG_LEVEL 的值 * 功能列表如下: * 1 time 性能探针,计算运行的步骤以及每一步的执行效率 * 2 log 日志记录,把每一个日志信息记录下来 * 3 http 接口调用的记录以及耗时的汇总统计 * 4 redis redis调用的记录以及耗时的汇总统计 * 5 mysql mysql调用的记录以及耗时的汇总统计 * 6 cache memcache调用的记录以及耗时的汇总统计 * @author 王毅 */ namespace common; define('DEBUG_LOG_ERROR', 'ERROR'); define('DEBUG_LOG_WARNING', 'WARNING'); define('DEBUG_LOG_INFO', 'INFO'); class DebugLog { private $logId; private $timeList; private $logList; private $httpList; private $redisList; private $mysqlList; private $cacheList; private static $instance = false; private function __construct() {} /** * 初始化调试日志操作类,没有经过初始化的后续调试代码都不会生效 */ public static function _init() { if (!self::$instance) { self::$instance = new DebugLog(); self::$instance->logId = microtime(); } } /** * 记录时间,方便调试程序执行逻辑和每一步的执行效率 */ public static function _time($label, $handler = false) { if (self::$instance === false) { return; } self::$instance->timeList[] = array($label, microtime(), $handler); } /** * 记录运行时的调试信息,分为 DEBUG_LOG_INFO 和 DEBUG_LOG_ERROR,DEBUG_LOG_INFO 只有在全量输出调试信息的时候才会输出 */ public static function _log($label, $info, $level=DEBUG_LOG_INFO, $handler = false) { if (self::$instance === false || (DEBUG_LEVEL < 2 && $level == DEBUG_LOG_INFO)) { return; } self::$instance->logList[] = array($label, $info, $level, $handler); } /** * 记录运行时的http请求 */ public static function _http($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) { if (self::$instance === false) { return; } if (DEBUG_LEVEL === 1) { self::$instance->httpList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler); } else { self::$instance->httpList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler); } } /** * 记录运行时的redis请求 */ public static function _redis($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) { if (self::$instance === false) { return; } if (DEBUG_LEVEL === 1) { if ('setex' == $label) { // 过滤掉内容块,避免日志太多 $params[2] = null; } self::$instance->redisList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler); } else { self::$instance->redisList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler); } } /** * 记录运行时的mysql请求 */ public static function _mysql($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) { if (self::$instance === false) { return; } if (DEBUG_LEVEL === 1) { self::$instance->mysqlList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler); } else { self::$instance->mysqlList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler); } } /** * 记录运行时的memcache请求 */ public static function _cache($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false) { if (self::$instance === false) { return; } if (DEBUG_LEVEL === 1) { self::$instance->cacheList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler); } else { self::$instance->cacheList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler); } } /** * 输出日志 */ public static function _show() { if (self::$instance === false) { return; } if (isset($_SERVER['HTTP_USER_AGENT'])) { // 界面上可视化模式输出内容 self::$instance->showViews(); } else { self::$instance->writeLogs(); } } /** * 是否有可视化界面输出,HTML代码直接返回到浏览器 */ public static function _is_show_view() { if (self::$instance && isset($_SERVER['HTTP_USER_AGENT'])) { return true; } else { return false; } } /** * 将microtime的时间字符串转换为float型的毫秒时间 */ private function _floatMicrotime($mt) { if (strpos($mt, ' ')) { list($ms, $m) = explode(' ', $mt); return ($m + $ms) * 1000; } else { return floatval($mt) * 1000; } } /** * 计算两个microtime时间的间隔时间 * @param $m1 开始时间 * @param $m2 结束时间 * @param $round 保留小数位 */ private function _intervalTime($m1, $m2, $round = 3) { return round(($this->_floatMicrotime($m2) - $this->_floatMicrotime($m1)), $round); } /** * 将调试信息生成可视化的HTML代码 */ private function showViews() { $showTime = microtime(); $output = array(); $output[] = "\n"; $output[] = '<ul>'; $output[] = '<li><strong style="font-size:18px;">DebugLog showViews.total process time is ' . $this->_intervalTime($this->logId, $showTime) . 'ms</strong></li>'; if ($this->timeList) { $total_num = count($this->timeList); $output[] = '<li><strong style="font-size:18px;">TimeList total count is ' . count($this->timeList) . ', log time is ' . $this->_intervalTime($this->logId, $this->timeList[$total_num - 1][1]) . '</strong></li>'; $lasttime = $this->logId; $output[] = '<li>0.000 : start debug log ' . $lasttime . '</li>'; foreach ($this->timeList as $info) { $lasttime2 = $info[1]; $output[] = '<li>'. $this->_intervalTime($lasttime, $lasttime2) . ' : ' . implode("\t", $info) . '</li>'; $lasttime = $lasttime2; } } if ($this->logList) { $output[] = '<li><strong style="font-size:18px;">LogList total count is ' . count($this->logList) . '</strong></li>'; foreach ($this->logList as $info) { $output[] = '<li>' . implode("\t", $info) . '</li>'; } } if ($this->httpList) { $current = count($output); $total_time = 0; $output[] = null; $max_num = array(); $multi_num = array(); foreach ($this->httpList as $info) { $intval = $this->_intervalTime($info[3], $info[4]); $multi_flag = @json_decode($info[2],true); if(isset($multi_flag) && isset($multi_flag['is_multi']) && $multi_flag['is_multi']==1) { $multi_str = strval($multi_flag['multi_num']); if($intval > $max_num[$multi_str]) { $max_num[$multi_str] = $intval; if(!in_array($multi_str, $multi_num)) { $multi_num[] = $multi_str; } } } else { $total_time += $intval; } if ($info[5] && is_array($info[5])) { $info[5] = json_encode($info[5]); } $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>'; } if(!empty($multi_num )) { foreach($multi_num as $val) { $total_time += $max_num[$val]; } } $output[$current] = '<li><strong style="font-size:18px;">HttpList total count is ' . count($this->httpList) . ', total time is ' . $total_time . '</strong></li>'; } if ($this->redisList) { $current = count($output); $total_time = 0; $output[] = null; foreach ($this->redisList as $info) { $intval = $this->_intervalTime($info[3], $info[4]); $total_time += $intval; if ($info[5] && is_array($info[5])) { $info[5] = json_encode($info[5]); } $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>'; } $output[$current] = '<li><strong style="font-size:18px;">RedisList total count is ' . count($this->redisList) . ', total time is ' . $total_time . '</strong></li>'; } if ($this->mysqlList) { $current = count($output); $total_time = 0; $output[] = null; foreach ($this->mysqlList as $info) { $intval = $this->_intervalTime($info[3], $info[4]); $total_time += $intval; if ($info[5] && is_array($info[5])) { $info[5] = json_encode($info[5]); } elseif (!$info[5]) { $info[5] = ''; } $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>'; } $output[$current] = '<li><strong style="font-size:18px;">MysqlList total count is ' . count($this->mysqlList) . ', total time is ' . $total_time . '</strong></li>'; } if ($this->cacheList) { $current = count($output); $total_time = 0; $output[] = null; foreach ($this->cacheList as $info) { $intval = $this->_intervalTime($info[3], $info[4]); $total_time += $intval; if ($info[5] && is_array($info[5])) { $info[5] = json_encode($info[5]); } $output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>'; } $output[$current] = '<li><strong style="font-size:18px;">CacheList total count is ' . count($this->cacheList) . ', total time is ' . $total_time . '</strong></li>'; } $output[] = '</ul>'; echo implode("\n", $output); } /** * 将调试日志写入到本地文件中,使用JSON格式保存为一行 */ public function writeLogs() { $showTime = microtime(); if (!defined('DEBUG_LOG_PATH')) { define('DEBUG_LOG_PATH', '/var/log/'); } $serverList = array( 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], 'REQUEST_URI' => $_SERVER['REQUEST_URI'], 'REMOTE_ADDR:PORT' => $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT'], ); $datalist = array( 'logId'=>$this->logId, 'logTime'=>$showTime, 'timeList'=>$this->timeList, 'logList'=>$this->logList, 'httpList'=>$this->httpList, 'redisList'=>$this->redisList, 'mysqlList'=>$this->mysqlList, 'server'=>$serverList, ); $str = json_encode($datalist); $str = str_replace("\n", ' ', $str); $str .= "\n"; $file_path = DEBUG_LOG_PATH . 'discuz_debug.log'; if($fd = @fopen($file_path, 'a')) { fputs($fd, $str); fclose($fd); } } /** * 将消息输出到指定的文件 * 默认 define('DEBUG_LOG_PATH', '/home/qiku/system/log/php/today/') * @param $msg 消息内容 * @param string $file 日志文件名称,默认是 discuz_php.log */ public static function writeDebugLog($msg, $file='discuz_php.log') { $dtime = date('Y-m-d H:i:s'); if (!defined('DEBUG_LOG_PATH')) { $default_path = '/var/log/'; if (file_exists($default_path)) { define('DEBUG_LOG_PATH', $default_path); } else { define('DEBUG_LOG_PATH', ''); } } // $str_cookie = json_encode($_COOKIE); $str_cookie = 'no cookie'; $str_server = json_encode(array($_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI'])); $str = "[$dtime]||$msg||$str_cookie||$str_server\n"; $file_path = DEBUG_LOG_PATH . $file; if($fd = @fopen($file_path, 'a')) { fputs($fd, $str); fclose($fd); } } /** * 通过PHP的 debug_backtrace 可以详细的查看到方法调用的细节情况 */ public static function writeBacktrace($deep=3, $all=false) { $result = array(); $trace = debug_backtrace(); unset($trace[0]); if ($deep < count($trace)) { for ($i = 1; $i <= $deep; $i++) { $info = $trace[$i]; if (isset($info['object']) && $all === false) { unset($info['object']); } $result[] = $info; } } elseif ($all === false) { foreach ($trace as $info) { if (isset($info['object'])) { unset($info['object']); } $result[] = $info; } } else { $result = $trace; } self::writeDebugLog(json_encode($result), 'backtrace.log'); } }