Development

/branches/1.1/lib/debug/sfWebDebug.class.php

You must first sign up to be able to contribute.

root/branches/1.1/lib/debug/sfWebDebug.class.php

Revision 13184, 15.9 kB (checked in by nicolas, 5 years ago)

[1.1] fixed php segfaults when big queries are loggued in dev environment - closes #5000

  • Property svn:mime-type set to text/x-php
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id Rev Date
Line 
1 <?php
2
3 /*
4  * This file is part of the symfony package.
5  * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
6  *
7  * For the full copyright and license information, please view the LICENSE
8  * file that was distributed with this source code.
9  */
10
11 /**
12  * sfWebDebug creates debug information for easy debugging in the browser.
13  *
14  * @package    symfony
15  * @subpackage debug
16  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17  * @version    SVN: $Id$
18  */
19 class sfWebDebug
20 {
21   protected
22     $dispatcher  = null,
23     $log         = array(),
24     $maxPriority = 1000,
25     $types       = array(),
26     $lastTimeLog = -1;
27
28   public function __construct(sfEventDispatcher $dispatcher)
29   {
30     $this->dispatcher = $dispatcher;
31
32     $this->dispatcher->connect('view.cache.filter_content', array($this, 'decorateContentWithDebug'));
33   }
34
35   /**
36    * Logs a message to the web debug toolbar.
37    *
38    * @param array $logEntry An array of parameter
39    *
40    * @see sfWebDebugLogger
41    */
42   public function log($logEntry)
43   {
44     // elapsed time
45     if ($this->lastTimeLog == -1)
46     {
47       $this->lastTimeLog = sfConfig::get('sf_timer_start');
48     }
49
50     $this->lastTimeLog = microtime(true);
51
52     // update max priority
53     if ($logEntry['priority'] < $this->maxPriority)
54     {
55       $this->maxPriority = $logEntry['priority'];
56     }
57
58     // update types
59     if (!isset($this->types[$logEntry['type']]))
60     {
61       $this->types[$logEntry['type']] = 1;
62     }
63     else
64     {
65       ++$this->types[$logEntry['type']];
66     }
67
68     $this->log[] = $logEntry;
69   }
70
71   /**
72    * Loads helpers needed for the web debug toolbar.
73    */
74   protected function loadHelpers()
75   {
76     sfLoader::loadHelpers(array('Helper', 'Url', 'Asset', 'Tag'));
77   }
78
79   /**
80    * Formats a log line.
81    *
82    * @param string $logLine The log line to format
83    *
84    * @return string The formatted log lin
85    */
86   protected function formatLogLine($logLine)
87   {
88     static $constants;
89
90     if (!$constants)
91     {
92       foreach (array('sf_app_dir', 'sf_root_dir', 'sf_symfony_lib_dir') as $constant)
93       {
94         $constants[realpath(sfConfig::get($constant)).DIRECTORY_SEPARATOR] = $constant.DIRECTORY_SEPARATOR;
95       }
96     }
97
98     // escape HTML
99     $logLine = htmlspecialchars($logLine, ENT_QUOTES, sfConfig::get('sf_charset'));
100
101     // replace constants value with constant name
102     $logLine = str_replace(array_keys($constants), array_values($constants), $logLine);
103
104     $logLine = sfToolkit::pregtr($logLine, array('/&quot;(.+?)&quot;/s' => '"<span class="sfWebDebugLogInfo">\\1</span>"',
105                                                    '/^(.+?)\(\)\:/S'      => '<span class="sfWebDebugLogInfo">\\1()</span>:',
106                                                    '/line (\d+)$/'        => 'line <span class="sfWebDebugLogInfo">\\1</span>'));
107
108     // special formatting for SQL lines
109     $logLine = preg_replace('/\b(SELECT|FROM|AS|LIMIT|ASC|COUNT|DESC|WHERE|LEFT JOIN|INNER JOIN|RIGHT JOIN|ORDER BY|GROUP BY|IN|LIKE|DISTINCT|DELETE|INSERT|INTO|VALUES)\b/', '<span class="sfWebDebugLogInfo">\\1</span>', $logLine);
110
111     // remove username/password from DSN
112     if (strpos($logLine, 'DSN') !== false)
113     {
114       $logLine = preg_replace("/=&gt;\s+'?[^'\s,]+'?/", "=&gt; '****'", $logLine);
115     }
116
117     return $logLine;
118   }
119
120   /**
121    * Returns the web debug toolbar as HTML.
122    *
123    * @return string The web debug toolbar HTML
124    */
125   public function getResults()
126   {
127     if (!sfConfig::get('sf_web_debug'))
128     {
129       return '';
130     }
131
132     $this->loadHelpers();
133
134     $result = '';
135
136     // max priority
137     $maxPriority = '';
138     if (sfConfig::get('sf_logging_enabled'))
139     {
140       $maxPriority = $this->getPriority($this->maxPriority);
141     }
142
143     $logs = '';
144     $sqlLogs = array();
145     if (sfConfig::get('sf_logging_enabled'))
146     {
147       $logs = '<table class="sfWebDebugLogs">
148         <tr>
149           <th>#</th>
150           <th>type</th>
151           <th>message</th>
152         </tr>'."\n";
153       $line_nb = 0;
154       foreach ($this->log as $logEntry)
155       {
156         $log = $logEntry['message'];
157
158         $priority = $this->getPriority($logEntry['priority']);
159
160         if (strpos($type = $logEntry['type'], 'sf') === 0)
161         {
162           $type = substr($type, 2);
163         }
164
165         // xdebug information
166         $debug_info = '';
167         if ($logEntry['debugStack'])
168         {
169           $debug_info .= '&nbsp;<a href="#" onclick="sfWebDebugToggle(\'debug_'.$line_nb.'\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/toggle.gif').'</a><div class="sfWebDebugDebugInfo" id="debug_'.$line_nb.'" style="display:none">';
170           foreach ($logEntry['debugStack'] as $i => $logLine)
171           {
172             $debug_info .= '#'.$i.' &raquo; '.$this->formatLogLine($logLine).'<br/>';
173           }
174           $debug_info .= "</div>\n";
175         }
176
177         // format log
178         $log = $this->formatLogLine($log);
179
180         // sql queries log
181         if (preg_match('/execute(?:Query|Update).+?\:\s+(.+)$/s', $log, $match))
182         {
183           $sqlLogs[] = $match[1];
184         }
185
186         ++$line_nb;
187         $logs .= sprintf("<tr class='sfWebDebugLogLine sfWebDebug%s %s'><td class=\"sfWebDebugLogNumber\">%s</td><td class=\"sfWebDebugLogType\">%s&nbsp;%s</td><td>%s%s</td></tr>\n",
188           ucfirst($priority),
189           $logEntry['type'],
190           $line_nb,
191           image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/'.$priority.'.png'),
192           $type,
193           $log,
194           $debug_info
195         );
196       }
197       $logs .= '</table>';
198
199       ksort($this->types);
200       $types = array();
201       foreach ($this->types as $type => $nb)
202       {
203         $types[] = '<a href="#" onclick="sfWebDebugToggleMessages(\''.$type.'\'); return false;">'.$type.'</a>';
204       }
205     }
206
207     // ignore cache link
208     $cacheLink = '';
209     if (sfConfig::get('sf_debug') && sfConfig::get('sf_cache'))
210     {
211       $selfUrl = $_SERVER['PHP_SELF'].((strpos($_SERVER['PHP_SELF'], '_sf_ignore_cache') === false) ? '?_sf_ignore_cache=1' : '');
212       $cacheLink = '<li><a href="'.$selfUrl.'" title="reload and ignore cache">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/reload.png').'</a></li>';
213     }
214
215     // logging information
216     $logLink = '';
217     if (sfConfig::get('sf_logging_enabled'))
218     {
219       $logLink = '<li><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugLog\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/comment.png').' logs &amp; msgs</a></li>';
220     }
221
222     // database information
223     $dbInfo = '';
224     $dbInfoDetails = '';
225     if ($sqlLogs)
226     {
227       $dbInfo = '<li><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugDatabaseDetails\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/database.png').' '.count($sqlLogs).'</a></li>';
228
229       $dbInfoDetails = '
230         <div id="sfWebDebugDatabaseLogs">
231         <ol><li>'.implode("</li>\n<li>", $sqlLogs).'</li></ol>
232         </div>
233       ';
234     }
235
236     // memory used
237     $memoryInfo = '';
238     if (sfConfig::get('sf_debug') && function_exists('memory_get_usage'))
239     {
240       $totalMemory = sprintf('%.1f', (memory_get_usage() / 1024));
241       $memoryInfo = '<li>'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/memory.png').' '.$totalMemory.' KB</li>';
242     }
243
244     // total time elapsed
245     $timeInfo = '';
246     if (sfConfig::get('sf_debug'))
247     {
248       $totalTime = (microtime(true) - sfConfig::get('sf_timer_start')) * 1000;
249       $totalTime = sprintf(($totalTime <= 1) ? '%.2f' : '%.0f', $totalTime);
250       $timeInfo = '<li class="last"><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugTimeDetails\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/time.png').' '.$totalTime.' ms</a></li>';
251     }
252
253     // timers
254     $timeInfoDetails = '<table class="sfWebDebugLogs" style="width: 300px"><tr><th>type</th><th>calls</th><th>time (ms)</th><th>time (%)</th></tr>';
255     foreach (sfTimerManager::getTimers() as $name => $timer)
256     {
257       $timeInfoDetails .= sprintf('<tr><td class="sfWebDebugLogType">%s</td><td class="sfWebDebugLogNumber" style="text-align: right">%d</td><td style="text-align: right">%.2f</td><td style="text-align: right">%d</td></tr>', $name, $timer->getCalls(), $timer->getElapsedTime() * 1000, $timer->getElapsedTime() * 1000 * 100 / $totalTime );
258     }
259     $timeInfoDetails .= '</table>';
260
261     // logs
262     $logInfo = '';
263     if (sfConfig::get('sf_logging_enabled'))
264     {
265       $logInfo .= '
266         <ul id="sfWebDebugLogMenu">
267           <li><a href="#" onclick="sfWebDebugToggleAllLogLines(true, \'sfWebDebugLogLine\'); return false;">[all]</a></li>
268           <li><a href="#" onclick="sfWebDebugToggleAllLogLines(false, \'sfWebDebugLogLine\'); return false;">[none]</a></li>
269           <li><a href="#" onclick="sfWebDebugShowOnlyLogLines(\'info\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/info.png').'</a></li>
270           <li><a href="#" onclick="sfWebDebugShowOnlyLogLines(\'warning\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/warning.png').'</a></li>
271           <li><a href="#" onclick="sfWebDebugShowOnlyLogLines(\'error\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/error.png').'</a></li>
272           <li>'.implode("</li>\n<li>", $types).'</li>
273         </ul>
274         <div id="sfWebDebugLogLines">'.$logs.'</div>
275       ';
276     }
277
278     $result .= '
279     <div id="sfWebDebug">
280       <div id="sfWebDebugBar" class="sfWebDebug'.ucfirst($maxPriority).'">
281         <a href="#" onclick="sfWebDebugToggleMenu(); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/sf.png').'</a>
282         <ul id="sfWebDebugDetails" class="menu">
283           <li>'.SYMFONY_VERSION.'</li>
284           <li><a href="#" onclick="sfWebDebugShowDetailsFor(\'sfWebDebugConfig\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/config.png').' vars &amp; config</a></li>
285           '.$cacheLink.'
286           '.$logLink.'
287           '.$dbInfo.'
288           '.$memoryInfo.'
289           '.$timeInfo.'
290         </ul>
291         <a href="#" onclick="document.getElementById(\'sfWebDebug\').style.display=\'none\'; return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/close.png').'</a>
292       </div>
293
294       <div id="sfWebDebugLog" class="sfWebDebugTop" style="display: none"><h1>Log and debug messages</h1>'.$logInfo.'</div>
295       <div id="sfWebDebugConfig" class="sfWebDebugTop" style="display: none"><h1>Configuration and request variables</h1>'.$this->getCurrentConfigAsHtml().'</div>
296       <div id="sfWebDebugDatabaseDetails" class="sfWebDebugTop" style="display: none"><h1>SQL queries</h1>'.$dbInfoDetails.'</div>
297       <div id="sfWebDebugTimeDetails" class="sfWebDebugTop" style="display: none"><h1>Timers</h1>'.$timeInfoDetails.'</div>
298
299       </div>
300     ';
301
302     return $result;
303   }
304
305   /**
306    * Returns the current configuration as HTML.
307    *
308    * @return string The current configuration as HTML
309    */
310   protected function getCurrentConfigAsHtml()
311   {
312     $config = array(
313       'debug'        => sfConfig::get('sf_debug')           ? 'on' : 'off',
314       'xdebug'       => extension_loaded('xdebug')          ? 'on' : 'off',
315       'logging'      => sfConfig::get('sf_logging_enabled') ? 'on' : 'off',
316       'cache'        => sfConfig::get('sf_cache')           ? 'on' : 'off',
317       'compression'  => sfConfig::get('sf_compressed')      ? 'on' : 'off',
318       'tokenizer'    => function_exists('token_get_all')    ? 'on' : 'off',
319       'eaccelerator' => extension_loaded('eaccelerator') && ini_get('eaccelerator.enable') ? 'on' : 'off',
320       'apc'          => extension_loaded('apc') && ini_get('apc.enabled')                  ? 'on' : 'off',
321       'xcache'       => extension_loaded('xcache') && ini_get('xcache.cacher')             ? 'on' : 'off',
322     );
323
324     $result = '<ul id="sfWebDebugConfigSummary">';
325     foreach ($config as $key => $value)
326     {
327       $result .= '<li class="is'.$value.($key == 'xcache' ? ' last' : '').'">'.$key.'</li>';
328     }
329     $result .= '</ul>';
330
331     $context = sfContext::getInstance();
332     $result .= $this->formatArrayAsHtml('request'sfDebug::requestAsArray($context->getRequest()));
333     $result .= $this->formatArrayAsHtml('response', sfDebug::responseAsArray($context->getResponse()));
334     $result .= $this->formatArrayAsHtml('user',     sfDebug::userAsArray($context->getUser()));
335     $result .= $this->formatArrayAsHtml('settings', sfDebug::settingsAsArray());
336     $result .= $this->formatArrayAsHtml('globals'sfDebug::globalsAsArray());
337     $result .= $this->formatArrayAsHtml('php',      sfDebug::phpInfoAsArray());
338     $result .= $this->formatArrayAsHtml('symfony'sfDebug::symfonyInfoAsArray());
339
340     return $result;
341   }
342
343   /**
344    * Converts an array to HTML.
345    *
346    * @param string $id      The identifier to use
347    * @param array  $values  The array of values
348    *
349    * @return string An HTML string
350    */
351   protected function formatArrayAsHtml($id, $values)
352   {
353     $id = ucfirst(strtolower($id));
354     $content = '
355     <h2>'.$id.' <a href="#" onclick="sfWebDebugToggle(\'sfWebDebug'.$id.'\'); return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/toggle.gif').'</a></h2>
356     <div id="sfWebDebug'.$id.'" style="display: none"><pre>'.htmlspecialchars(sfYaml::dump(self::removeObjects($values)), ENT_QUOTES, sfConfig::get('sf_charset')).'</pre></div>
357     ';
358
359     return $content;
360   }
361
362   /**
363    * Listens to the 'view.cache.filter_content' event to decorate a chunk of HTML with cache information.
364    *
365    * @param sfEvent $event   A sfEvent instance
366    * @param string  $content The HTML content
367    *
368    * @return string The decorated HTML string
369    */
370   public function decorateContentWithDebug(sfEvent $event, $content)
371   {
372     // don't decorate if not html or if content is null
373     if (!sfConfig::get('sf_web_debug') || !$content || false === strpos($event['response']->getContentType(), 'html'))
374     {
375       return $content;
376     }
377
378     $viewCacheManager = $event->getSubject();
379     sfLoader::loadHelpers(array('Helper', 'Url', 'Asset', 'Tag'));
380
381     $bgColor      = $event['new'] ? '#9ff' : '#ff9';
382     $lastModified = $viewCacheManager->getLastModified($event['uri']);
383     $id           = md5($event['uri']);
384
385     return '
386       <div id="main_'.$id.'" class="sfWebDebugActionCache" style="border: 1px solid #f00">
387       <div id="sub_main_'.$id.'" class="sfWebDebugCache" style="background-color: '.$bgColor.'; border-right: 1px solid #f00; border-bottom: 1px solid #f00;">
388       <div style="height: 16px; padding: 2px"><a href="#" onclick="sfWebDebugToggle(\'sub_main_info_'.$id.'\'); return false;"><strong>cache information</strong></a>&nbsp;<a href="#" onclick="sfWebDebugToggle(\'sub_main_'.$id.'\'); document.getElementById(\'main_'.$id.'\').style.border = \'none\'; return false;">'.image_tag(sfConfig::get('sf_web_debug_web_dir').'/images/close.png').'</a>&nbsp;</div>
389         <div style="padding: 2px; display: none" id="sub_main_info_'.$id.'">
390         [uri]&nbsp;'.htmlspecialchars($event['uri'], ENT_QUOTES, sfConfig::get('sf_charset')).'<br />
391         [life&nbsp;time]&nbsp;'.$viewCacheManager->getLifeTime($event['uri']).'&nbsp;seconds<br />
392         [last&nbsp;modified]&nbsp;'.(time() - $lastModified).'&nbsp;seconds<br />
393         &nbsp;<br />&nbsp;
394         </div>
395       </div><div>
396       '.$content.'
397       </div></div>
398     ';
399   }
400
401   /**
402    * Converts a priority value to a string.
403    *
404    * @param integer $value The priority value
405    *
406    * @return string The priority as a string
407    */
408   protected function getPriority($value)
409   {
410     if ($value >= sfLogger::INFO)
411     {
412       return 'info';
413     }
414     else if ($value >= sfLogger::WARNING)
415     {
416       return 'warning';
417     }
418     else
419     {
420       return 'error';
421     }
422   }
423
424   static protected function removeObjects($values)
425   {
426     $nvalues = array();
427     foreach ($values as $key => $value)
428     {
429       if (is_array($value))
430       {
431         $nvalues[$key] = self::removeObjects($value);
432       }
433       else if (is_object($value))
434       {
435         $nvalues[$key] = sprintf('%s Object()', get_class($value));
436       }
437       else
438       {
439         $nvalues[$key] = $value;
440       }
441     }
442
443     return $nvalues;
444   }
445 }
446
Note: See TracBrowser for help on using the browser.