ChromeShowPageTraceBehavior.class.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: luofei614 <weibo.com/luofei614>
  10. // +----------------------------------------------------------------------
  11. // $Id$
  12. /**
  13. * 将Trace信息输出到chrome浏览器的控制器,从而不影响ajax效果和页面的布局。
  14. * 使用前,你需要先安装 chrome log 这个插件: http://craig.is/writing/chrome-logger。
  15. * 定义应用的tags.php文件 Application/Common/Conf/tags.php,
  16. * <code>
  17. * <?php return array(
  18. * 'app_end'=>array(
  19. * 'Behavior\ChromeShowPageTrace'
  20. * )
  21. * );
  22. * </code>
  23. * 如果trace信息没有正常输出,请查看您的日志。
  24. * 这是通过http headers和chrome通信,所以要保证在输出trace信息之前不能有
  25. * headers输出,你可以在入口文件第一行加入代码 ob_start(); 或者配置output_buffering
  26. *
  27. */
  28. namespace Behavior;
  29. use Behavior\ChromePhp as ChromePhp;
  30. use Think\Log;
  31. /**
  32. * 系统行为扩展 页面Trace显示输出
  33. */
  34. class ChromeShowPageTraceBehavior
  35. {
  36. protected $tracePageTabs = array('BASE' => '基本', 'FILE' => '文件', 'INFO' => '流程', 'ERR|NOTIC' => '错误', 'SQL' => 'SQL', 'DEBUG' => '调试');
  37. // 行为扩展的执行入口必须是run
  38. public function run(&$params)
  39. {
  40. if (C('SHOW_PAGE_TRACE')) {
  41. $this->showTrace();
  42. }
  43. }
  44. /**
  45. * 显示页面Trace信息
  46. * @access private
  47. */
  48. private function showTrace()
  49. {
  50. // 系统默认显示信息
  51. $files = get_included_files();
  52. $info = array();
  53. foreach ($files as $key => $file) {
  54. $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )';
  55. }
  56. $trace = array();
  57. $base = array(
  58. '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . __SELF__,
  59. '运行时间' => $this->showTime(),
  60. '吞吐率' => number_format(1 / G('beginTime', 'viewEndTime'), 2) . 'req/s',
  61. '内存开销' => MEMORY_LIMIT_ON ? number_format((memory_get_usage() - $GLOBALS['_startUseMems']) / 1024, 2) . ' kb' : '不支持',
  62. '查询信息' => N('db_query') . ' queries ' . N('db_write') . ' writes ',
  63. '文件加载' => count(get_included_files()),
  64. '缓存信息' => N('cache_read') . ' gets ' . N('cache_write') . ' writes ',
  65. '配置加载' => count(c()),
  66. '会话信息' => 'SESSION_ID=' . session_id(),
  67. );
  68. // 读取应用定义的Trace文件
  69. $traceFile = COMMON_PATH . 'Conf/trace.php';
  70. if (is_file($traceFile)) {
  71. $base = array_merge($base, include $traceFile);
  72. }
  73. $debug = trace();
  74. $tabs = C('TRACE_PAGE_TABS', null, $this->tracePageTabs);
  75. foreach ($tabs as $name => $title) {
  76. switch (strtoupper($name)) {
  77. case 'BASE': // 基本信息
  78. $trace[$title] = $base;
  79. break;
  80. case 'FILE': // 文件信息
  81. $trace[$title] = $info;
  82. break;
  83. default: // 调试信息
  84. $name = strtoupper($name);
  85. if (strpos($name, '|')) {
  86. // 多组信息
  87. $array = explode('|', $name);
  88. $result = array();
  89. foreach ($array as $name) {
  90. $result += isset($debug[$name]) ? $debug[$name] : array();
  91. }
  92. $trace[$title] = $result;
  93. } else {
  94. $trace[$title] = isset($debug[$name]) ? $debug[$name] : '';
  95. }
  96. }
  97. }
  98. chromeDebug('TRACE信息:' . __SELF__, 'group');
  99. //输出日志
  100. foreach ($trace as $title => $log) {
  101. '错误' == $title ? chromeDebug($title, 'group') : chromeDebug($title, 'groupCollapsed');
  102. foreach ($log as $i => $logstr) {
  103. chromeDebug($i . '.' . $logstr, 'log');
  104. }
  105. chromeDebug('', 'groupEnd');
  106. }
  107. chromeDebug('', 'groupEnd');
  108. if ($save = C('PAGE_TRACE_SAVE')) {
  109. // 保存页面Trace日志
  110. if (is_array($save)) { // 选择选项卡保存
  111. $tabs = C('TRACE_PAGE_TABS', null, $this->tracePageTabs);
  112. $array = array();
  113. foreach ($save as $tab) {
  114. $array[] = $tabs[$tab];
  115. }
  116. }
  117. $content = date('[ c ]') . ' ' . get_client_ip() . ' ' . $_SERVER['REQUEST_URI'] . "\r\n";
  118. foreach ($trace as $key => $val) {
  119. if (!isset($array) || in_array($key, $array)) {
  120. $content .= '[ ' . $key . " ]\r\n";
  121. if (is_array($val)) {
  122. foreach ($val as $k => $v) {
  123. $content .= (!is_numeric($k) ? $k . ':' : '') . print_r($v, true) . "\r\n";
  124. }
  125. } else {
  126. $content .= print_r($val, true) . "\r\n";
  127. }
  128. $content .= "\r\n";
  129. }
  130. }
  131. error_log(str_replace('<br/>', "\r\n", $content), 3, LOG_PATH . date('y_m_d') . '_trace.log');
  132. }
  133. unset($files, $info, $base);
  134. }
  135. /**
  136. * 获取运行时间
  137. */
  138. private function showTime()
  139. {
  140. // 显示运行时间
  141. G('beginTime', $GLOBALS['_beginTime']);
  142. G('viewEndTime');
  143. // 显示详细运行时间
  144. return G('beginTime', 'viewEndTime') . 's ( Load:' . G('beginTime', 'loadTime') . 's Init:' . G('loadTime', 'initTime') . 's Exec:' . G('initTime', 'viewStartTime') . 's Template:' . G('viewStartTime', 'viewEndTime') . 's )';
  145. }
  146. }
  147. if (!function_exists('chrome_debug')) {
  148. //ChromePhp 输出trace的函数
  149. function chromeDebug($msg, $type = 'trace', $trace_level = 1)
  150. {
  151. if ('trace' == $type) {
  152. ChromePhp::groupCollapsed($msg);
  153. $traces = debug_backtrace(false);
  154. $traces = array_reverse($traces);
  155. $max = count($traces) - $trace_level;
  156. for ($i = 0; $i < $max; $i++) {
  157. $trace = $traces[$i];
  158. $fun = isset($trace['class']) ? $trace['class'] . '::' . $trace['function'] : $trace['function'];
  159. $file = isset($trace['file']) ? $trace['file'] : 'unknown file';
  160. $line = isset($trace['line']) ? $trace['line'] : 'unknown line';
  161. $trace_msg = '#' . $i . ' ' . $fun . ' called at [' . $file . ':' . $line . ']';
  162. if (!empty($trace['args'])) {
  163. ChromePhp::groupCollapsed($trace_msg);
  164. ChromePhp::log($trace['args']);
  165. ChromePhp::groupEnd();
  166. } else {
  167. ChromePhp::log($trace_msg);
  168. }
  169. }
  170. ChromePhp::groupEnd();
  171. } else {
  172. if (method_exists('Behavior\ChromePhp', $type)) {
  173. //支持type trace,warn,log,error,group, groupCollapsed, groupEnd等
  174. call_user_func(array('Behavior\ChromePhp', $type), $msg);
  175. } else {
  176. //如果type不为trace,warn,log等,则为log的标签
  177. call_user_func_array(array('Behavior\ChromePhp', 'log'), func_get_args());
  178. }
  179. }
  180. }
  181. /**
  182. * Server Side Chrome PHP debugger class
  183. *
  184. * @package ChromePhp
  185. * @author Craig Campbell <iamcraigcampbell@gmail.com>
  186. */
  187. class ChromePhp
  188. {
  189. /**
  190. * @var string
  191. */
  192. const VERSION = '4.1.0';
  193. /**
  194. * @var string
  195. */
  196. const HEADER_NAME = 'X-ChromeLogger-Data';
  197. /**
  198. * @var string
  199. */
  200. const BACKTRACE_LEVEL = 'backtrace_level';
  201. /**
  202. * @var string
  203. */
  204. const LOG = 'log';
  205. /**
  206. * @var string
  207. */
  208. const WARN = 'warn';
  209. /**
  210. * @var string
  211. */
  212. const ERROR = 'error';
  213. /**
  214. * @var string
  215. */
  216. const GROUP = 'group';
  217. /**
  218. * @var string
  219. */
  220. const INFO = 'info';
  221. /**
  222. * @var string
  223. */
  224. const GROUP_END = 'groupEnd';
  225. /**
  226. * @var string
  227. */
  228. const GROUP_COLLAPSED = 'groupCollapsed';
  229. /**
  230. * @var string
  231. */
  232. const TABLE = 'table';
  233. /**
  234. * @var string
  235. */
  236. protected $_php_version;
  237. /**
  238. * @var int
  239. */
  240. protected $_timestamp;
  241. /**
  242. * @var array
  243. */
  244. protected $_json = array(
  245. 'version' => self::VERSION,
  246. 'columns' => array('log', 'backtrace', 'type'),
  247. 'rows' => array(),
  248. );
  249. /**
  250. * @var array
  251. */
  252. protected $_backtraces = array();
  253. /**
  254. * @var bool
  255. */
  256. protected $_error_triggered = false;
  257. /**
  258. * @var array
  259. */
  260. protected $_settings = array(
  261. self::BACKTRACE_LEVEL => 1,
  262. );
  263. /**
  264. * @var ChromePhp
  265. */
  266. protected static $_instance;
  267. /**
  268. * Prevent recursion when working with objects referring to each other
  269. *
  270. * @var array
  271. */
  272. protected $_processed = array();
  273. /**
  274. * constructor
  275. */
  276. private function __construct()
  277. {
  278. $this->_php_version = phpversion();
  279. $this->_timestamp = $this->_php_version >= 5.1 ? $_SERVER['REQUEST_TIME'] : time();
  280. $this->_json['request_uri'] = $_SERVER['REQUEST_URI'];
  281. }
  282. /**
  283. * gets instance of this class
  284. *
  285. * @return ChromePhp
  286. */
  287. public static function getInstance()
  288. {
  289. if (null === self::$_instance) {
  290. self::$_instance = new self();
  291. }
  292. return self::$_instance;
  293. }
  294. /**
  295. * logs a variable to the console
  296. *
  297. * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
  298. * @return void
  299. */
  300. public static function log()
  301. {
  302. $args = func_get_args();
  303. return self::_log('', $args);
  304. }
  305. /**
  306. * logs a warning to the console
  307. *
  308. * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
  309. * @return void
  310. */
  311. public static function warn()
  312. {
  313. $args = func_get_args();
  314. return self::_log(self::WARN, $args);
  315. }
  316. /**
  317. * logs an error to the console
  318. *
  319. * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
  320. * @return void
  321. */
  322. public static function error()
  323. {
  324. $args = func_get_args();
  325. return self::_log(self::ERROR, $args);
  326. }
  327. /**
  328. * sends a group log
  329. *
  330. * @param string value
  331. */
  332. public static function group()
  333. {
  334. $args = func_get_args();
  335. return self::_log(self::GROUP, $args);
  336. }
  337. /**
  338. * sends an info log
  339. *
  340. * @param mixed $data,... unlimited OPTIONAL number of additional logs [...]
  341. * @return void
  342. */
  343. public static function info()
  344. {
  345. $args = func_get_args();
  346. return self::_log(self::INFO, $args);
  347. }
  348. /**
  349. * sends a collapsed group log
  350. *
  351. * @param string value
  352. */
  353. public static function groupCollapsed()
  354. {
  355. $args = func_get_args();
  356. return self::_log(self::GROUP_COLLAPSED, $args);
  357. }
  358. /**
  359. * ends a group log
  360. *
  361. * @param string value
  362. */
  363. public static function groupEnd()
  364. {
  365. $args = func_get_args();
  366. return self::_log(self::GROUP_END, $args);
  367. }
  368. /**
  369. * sends a table log
  370. *
  371. * @param string value
  372. */
  373. public static function table()
  374. {
  375. $args = func_get_args();
  376. return self::_log(self::TABLE, $args);
  377. }
  378. /**
  379. * internal logging call
  380. *
  381. * @param string $type
  382. * @return void
  383. */
  384. protected static function _log($type, array $args)
  385. {
  386. // nothing passed in, don't do anything
  387. if (count($args) == 0 && self::GROUP_END != $type) {
  388. return;
  389. }
  390. $logger = self::getInstance();
  391. $logger->_processed = array();
  392. $logs = array();
  393. foreach ($args as $arg) {
  394. $logs[] = $logger->_convert($arg);
  395. }
  396. $backtrace = debug_backtrace(false);
  397. $level = $logger->getSetting(self::BACKTRACE_LEVEL);
  398. $backtrace_message = 'unknown';
  399. if (isset($backtrace[$level]['file']) && isset($backtrace[$level]['line'])) {
  400. $backtrace_message = $backtrace[$level]['file'] . ' : ' . $backtrace[$level]['line'];
  401. }
  402. $logger->_addRow($logs, $backtrace_message, $type);
  403. }
  404. /**
  405. * converts an object to a better format for logging
  406. *
  407. * @param Object
  408. * @return array
  409. */
  410. protected function _convert($object)
  411. {
  412. // if this isn't an object then just return it
  413. if (!is_object($object)) {
  414. return $object;
  415. }
  416. //Mark this object as processed so we don't convert it twice and it
  417. //Also avoid recursion when objects refer to each other
  418. $this->_processed[] = $object;
  419. $object_as_array = array();
  420. // first add the class name
  421. $object_as_array['___class_name'] = get_class($object);
  422. // loop through object vars
  423. $object_vars = get_object_vars($object);
  424. foreach ($object_vars as $key => $value) {
  425. // same instance as parent object
  426. if ($value === $object || in_array($value, $this->_processed, true)) {
  427. $value = 'recursion - parent object [' . get_class($value) . ']';
  428. }
  429. $object_as_array[$key] = $this->_convert($value);
  430. }
  431. $reflection = new ReflectionClass($object);
  432. // loop through the properties and add those
  433. foreach ($reflection->getProperties() as $property) {
  434. // if one of these properties was already added above then ignore it
  435. if (array_key_exists($property->getName(), $object_vars)) {
  436. continue;
  437. }
  438. $type = $this->_getPropertyKey($property);
  439. if ($this->_php_version >= 5.3) {
  440. $property->setAccessible(true);
  441. }
  442. try {
  443. $value = $property->getValue($object);
  444. } catch (ReflectionException $e) {
  445. $value = 'only PHP 5.3 can access private/protected properties';
  446. }
  447. // same instance as parent object
  448. if ($value === $object || in_array($value, $this->_processed, true)) {
  449. $value = 'recursion - parent object [' . get_class($value) . ']';
  450. }
  451. $object_as_array[$type] = $this->_convert($value);
  452. }
  453. return $object_as_array;
  454. }
  455. /**
  456. * takes a reflection property and returns a nicely formatted key of the property name
  457. *
  458. * @param ReflectionProperty
  459. * @return string
  460. */
  461. protected function _getPropertyKey(ReflectionProperty $property)
  462. {
  463. $static = $property->isStatic() ? ' static' : '';
  464. if ($property->isPublic()) {
  465. return 'public' . $static . ' ' . $property->getName();
  466. }
  467. if ($property->isProtected()) {
  468. return 'protected' . $static . ' ' . $property->getName();
  469. }
  470. if ($property->isPrivate()) {
  471. return 'private' . $static . ' ' . $property->getName();
  472. }
  473. }
  474. /**
  475. * adds a value to the data array
  476. *
  477. * @var mixed
  478. * @return void
  479. */
  480. protected function _addRow(array $logs, $backtrace, $type)
  481. {
  482. // if this is logged on the same line for example in a loop, set it to null to save space
  483. if (in_array($backtrace, $this->_backtraces)) {
  484. $backtrace = null;
  485. }
  486. // for group, groupEnd, and groupCollapsed
  487. // take out the backtrace since it is not useful
  488. if (self::GROUP == $type || self::GROUP_END == $type || self::GROUP_COLLAPSED == $type) {
  489. $backtrace = null;
  490. }
  491. if (null !== $backtrace) {
  492. $this->_backtraces[] = $backtrace;
  493. }
  494. $row = array($logs, $backtrace, $type);
  495. $this->_json['rows'][] = $row;
  496. $this->_writeHeader($this->_json);
  497. }
  498. protected function _writeHeader($data)
  499. {
  500. header(self::HEADER_NAME . ': ' . $this->_encode($data));
  501. }
  502. /**
  503. * encodes the data to be sent along with the request
  504. *
  505. * @param array $data
  506. * @return string
  507. */
  508. protected function _encode($data)
  509. {
  510. return base64_encode(utf8_encode(json_encode($data)));
  511. }
  512. /**
  513. * adds a setting
  514. *
  515. * @param string key
  516. * @param mixed value
  517. * @return void
  518. */
  519. public function addSetting($key, $value)
  520. {
  521. $this->_settings[$key] = $value;
  522. }
  523. /**
  524. * add ability to set multiple settings in one call
  525. *
  526. * @param array $settings
  527. * @return void
  528. */
  529. public function addSettings(array $settings)
  530. {
  531. foreach ($settings as $key => $value) {
  532. $this->addSetting($key, $value);
  533. }
  534. }
  535. /**
  536. * gets a setting
  537. *
  538. * @param string key
  539. * @return mixed
  540. */
  541. public function getSetting($key)
  542. {
  543. if (!isset($this->_settings[$key])) {
  544. return null;
  545. }
  546. return $this->_settings[$key];
  547. }
  548. }
  549. }