ChromeShowPageTraceBehavior.class.php 17 KB

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