1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12: use DebugBar\StandardDebugBar;
13: use DebugBar\DataCollector\MessagesCollector;
14: use Psr\Log\LoggerInterface;
15: use Psr\Log\LogLevel;
16: use Xoops\Core\Logger;
17:
18: /**
19: * Collects log information and present to PHPDebugBar for display.
20: * Records information about database queries, blocks, and execution time
21: * and various logs.
22: *
23: * @category DebugbarLogger
24: * @package DebugbarLogger
25: * @author Richard Griffith <richard@geekwright.com>
26: * @copyright 2013 XOOPS Project (http://xoops.org)
27: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
28: * @version Release: 1.0
29: * @link http://xoops.org
30: * @since 1.0
31: */
32: class DebugbarLogger implements LoggerInterface
33: {
34: /**
35: * @var object
36: */
37: private $debugbar = false;
38:
39: /**
40: * @var object
41: */
42: private $renderer = false;
43:
44: /**
45: * @var object
46: */
47: private $activated = false;
48:
49: /**
50: * @var object
51: */
52: private $quietmode = false;
53:
54: /**
55: * constructor
56: */
57: public function __construct()
58: {
59: Logger::getInstance()->addLogger($this);
60: }
61:
62: /**
63: * Get a reference to the only instance of this class
64: *
65: * @return object LoggerAbstract reference to the only instance
66: */
67: public static function getInstance()
68: {
69: static $instance;
70: if (!isset($instance)) {
71: $class = __CLASS__;
72: $instance = new $class();
73: }
74:
75: return $instance;
76: }
77:
78: /**
79: * Get our debugbar object
80: *
81: * @return \DebugBar\DebugBar
82: */
83: public function getDebugbar()
84: {
85: return $this->debugbar;
86: }
87:
88: /**
89: * disable logging
90: *
91: * @return void
92: */
93: public function disable()
94: {
95: //error_reporting(0);
96: $this->activated = false;
97: }
98:
99: /**
100: * Enable logger output rendering
101: * When output rendering is enabled, the logger will insert its output within the page content.
102: * If the string <!--{xo-logger-output}--> is found in the page content, the logger output will
103: * replace it, otherwise it will be inserted after all the page output.
104: *
105: * @return void
106: */
107: public function enable()
108: {
109: error_reporting(E_ALL | E_STRICT);
110:
111: $this->activated = true;
112:
113: $this->enableRendering();
114:
115: if (!$this->debugbar) {
116: $this->debugbar = new StandardDebugBar();
117: $this->renderer = $this->debugbar->getJavascriptRenderer();
118:
119: //$this->debugbar->addCollector(new MessagesCollector('Errors'));
120: $this->debugbar->addCollector(new MessagesCollector('Deprecated'));
121: $this->debugbar->addCollector(new MessagesCollector('Blocks'));
122: $this->debugbar->addCollector(new MessagesCollector('Extra'));
123: //$this->debugbar->addCollector(new MessagesCollector('Queries'));
124:
125: $xoops = Xoops::getInstance();
126: $debugStack = $xoops->db()->getConfiguration()->getSQLLogger();
127: $this->debugbar->addCollector(new DebugBar\Bridge\DoctrineCollector($debugStack));
128: //$this->debugbar->setStorage(new DebugBar\Storage\FileStorage(\XoopsBaseConfig::get('var-path').'/debugbar'));
129: }
130: $this->addToTheme();
131: }
132:
133: /**
134: * report enabled status
135: *
136: * @return bool
137: */
138: public function isEnable()
139: {
140: return $this->activated;
141: }
142:
143: /**
144: * disable output for the benefit of ajax scripts
145: *
146: * @return void
147: */
148: public function quiet()
149: {
150: //$this->debugbar->sendDataInHeaders();
151: $this->quietmode = true;
152: }
153:
154: /**
155: * Add our resources to the theme as soon as it is available, otherwise return
156: *
157: * @return void
158: */
159: private function addToTheme()
160: {
161: static $addedResource = false;
162:
163: if ($this->activated && !$addedResource) {
164: if (isset($GLOBALS['xoTheme'])) {
165: // get asset information provided by debugbar
166: // don't include vendors - jquery already available, need workaround for font-awesome
167: $this->renderer->setIncludeVendors(true);
168: $this->renderer->setEnableJqueryNoConflict(false);
169: list($cssAssets, $jsAssets) = $this->renderer->getAssets();
170:
171: // font-awesome requires some special handling with cssmin
172: // see: https://code.google.com/p/cssmin/issues/detail?id=52&q=font
173: // using our own copy of full css instead of minified version packaged
174: // with debugbar avoids the issue.
175:
176: // Supress unwanted assets - exclude anything containing these strings
177: $excludes = array(
178: '/vendor/font-awesome/', // font-awsome needs special process
179: //'/vendor/highlightjs/', // highlightjs has some negative side effects
180: '/vendor/jquery/', // jquery is already available
181: );
182:
183: $cssAssets = array_filter(
184: $cssAssets,
185: function ($filename) use ($excludes) {
186: foreach ($excludes as $exclude) {
187: if (false !== strpos($filename, $exclude)) {
188: return false;
189: }
190: }
191: return true;
192: }
193: );
194:
195: $jsAssets = array_filter(
196: $jsAssets,
197: function ($filename) use ($excludes) {
198: foreach ($excludes as $exclude) {
199: if (false !== strpos($filename, $exclude)) {
200: return false;
201: }
202: }
203: return true;
204: }
205: );
206: $cssAssets[] = 'modules/debugbar/assets/css/font-awesome.css';
207:
208: $xoops = Xoops::getInstance();
209: $xoops->theme()->addStylesheetAssets($cssAssets, 'cssembed,?cssmin');
210: $xoops->theme()->addScriptAssets($jsAssets, '?jsmin');
211:
212: $addedResource = true;
213: }
214: }
215: }
216:
217: /**
218: * Start a timer
219: *
220: * @param string $name name of the timer
221: * @param string|null $label optional label for this timer
222: *
223: * @return void
224: */
225: public function startTime($name = 'XOOPS', $label = null)
226: {
227: if ($this->activated) {
228: try {
229: $this->debugbar['time']->startMeasure($name, $label);
230: } catch (Exception $e) {
231: $this->addException($e);
232: }
233: }
234: }
235:
236: /**
237: * Stop a timer
238: *
239: * @param string $name name of the timer
240: *
241: * @return void
242: */
243: public function stopTime($name = 'XOOPS')
244: {
245: $this->addToTheme();
246:
247: if ($this->activated) {
248: try {
249: $this->debugbar['time']->stopMeasure($name);
250: } catch (Exception $e) {
251: $this->addException($e);
252: }
253: }
254: }
255:
256: /**
257: * Log a database query
258: *
259: * @param string $sql sql that was processed
260: * @param string $error error message
261: * @param int $errno error number
262: * @param float $query_time execution time
263: *
264: * @return void
265: */
266: public function addQuery($sql, $error = null, $errno = null, $query_time = null)
267: {
268: if ($this->activated) {
269: $level = LogLevel::INFO;
270: if (!empty($error)) {
271: $level = LogLevel::ERROR;
272: }
273: $context = array(
274: 'channel'=>'Queries',
275: 'error'=>$error,
276: 'errno'=>$errno,
277: 'query_time'=>$query_time
278: );
279: $this->log($level, $sql, $context);
280: }
281: }
282:
283: /**
284: * Log display of a block
285: *
286: * @param string $name name of the block
287: * @param bool $cached was the block cached?
288: * @param int $cachetime cachetime of the block
289: *
290: * @return void
291: */
292: public function addBlock($name, $cached = false, $cachetime = 0)
293: {
294: if ($this->activated) {
295: $context = array('channel'=>'Blocks', 'cached'=>$cached, 'cachetime'=>$cachetime);
296: $this->log(LogLevel::INFO, $name, $context);
297: }
298: }
299:
300: /**
301: * Log extra information
302: *
303: * @param string $name name for the entry
304: * @param string $msg text message for the entry
305: *
306: * @return void
307: */
308: public function addExtra($name, $msg)
309: {
310: if ($this->activated) {
311: $context = array('channel'=>'Extra', 'name'=>$name);
312: $this->log(LogLevel::INFO, $msg, $context);
313: }
314: }
315:
316: /**
317: * Log messages for deprecated functions
318: *
319: * @param string $msg name for the entry
320: *
321: * @return void
322: */
323: public function addDeprecated($msg)
324: {
325: if ($this->activated) {
326: $this->log(LogLevel::WARNING, $msg, array('channel'=>'Deprecated'));
327: }
328: }
329:
330: /**
331: * Log exceptions
332: *
333: * @param Exception $e name for the entry
334: *
335: * @return void
336: */
337: public function addException($e)
338: {
339: if ($this->activated) {
340: $this->debugbar['exceptions']->addException($e);
341: }
342: }
343:
344: /**
345: * Dump Smarty variables
346: *
347: * @return void
348: */
349: public function addSmarty()
350: {
351: if ($this->activated) {
352: $data = Xoops::getInstance()->tpl()->getTemplateVars();
353: // fix values that don't display properly
354: foreach ($data as $k => $v) {
355: if ($v === '') {
356: $data[$k] = '(empty string)';
357: } elseif ($v === null) {
358: $data[$k] = 'NULL';
359: } elseif ($v === true) { // just to be consistent with false
360: $data[$k] = 'bool TRUE';
361: } elseif ($v === false) {
362: $data[$k] = 'bool FALSE';
363: }
364: }
365: ksort($data, SORT_NATURAL | SORT_FLAG_CASE);
366: $this->debugbar->addCollector(
367: new DebugBar\DataCollector\ConfigCollector($data, 'Smarty')
368: );
369: }
370: }
371:
372: /**
373: * Dump a variable to the messages pane
374: *
375: * @param mixed $var variable to dump
376: *
377: * @return void
378: */
379: public function dump($var)
380: {
381: $this->log(LogLevel::DEBUG, $var);
382: }
383:
384: /**
385: * stackData - stash log data before a redirect
386: *
387: * @return void
388: */
389: public function stackData()
390: {
391: if ($this->activated) {
392: $this->debugbar->stackData();
393: $this->activated=false;
394: $this->renderingEnabled = false;
395: }
396: }
397:
398: /**
399: * Enable logger output rendering
400: * When output rendering is enabled, the logger will insert its output within the page content.
401: * If the string <!--{xo-logger-output}--> is found in the page content, the logger output will
402: * replace it, otherwise it will be inserted after all the page output.
403: *
404: * @return void
405: */
406: public function enableRendering()
407: {
408: $this->renderingEnabled = true;
409: }
410:
411: /**
412: * Output buffering callback inserting logger dump in page output
413: *
414: * @param string $output output buffer to add logger rendering to
415: *
416: * @return string output
417: */
418: public function render($output)
419: {
420: if (!$this->activated) {
421: return $output;
422: }
423:
424: $xoops = Xoops::getInstance();
425: $head = '</script>'.$this->renderer->renderHead().'<script>';
426: $xoops->theme()->addScript(null, null, $head);
427:
428: $log = $this->renderer->render();
429: $this->renderingEnabled = $this->activated = false;
430:
431: $pattern = '<!--{xo-logger-output}-->';
432: $pos = strpos($output, $pattern);
433: if ($pos !== false) {
434: return substr($output, 0, $pos) . $log . substr($output, $pos + strlen($pattern));
435: } else {
436: return $output . $log;
437: }
438: }
439:
440: /**
441: * dump everything we have // was __destruct()
442: */
443: public function renderDebugBar()
444: {
445: if ($this->activated) {
446: // include any queued time data from Xmf\Debug
447: $queue = \Xmf\Debug::dumpQueuedTimers(true);
448: if (!empty($queue)) {
449: foreach ($queue as $q) {
450: $this->debugbar['time']->addMeasure($q['label'], $q['start'], $q['start'] + $q['elapsed']);
451: }
452: }
453: $this->addToTheme();
454: $this->addExtra(_MD_DEBUGBAR_PHP_VERSION, PHP_VERSION);
455: $this->addExtra(_MD_DEBUGBAR_INCLUDED_FILES, (string) count(get_included_files()));
456: if (false === $this->quietmode) {
457: if (isset($_SERVER['HTTP_X_REQUESTED_WITH'])
458: && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
459: // default for ajax, do not initialize a new toolbar, just add dataset
460: $log = $this->renderer->render(false);
461: } else {
462: $log = $this->renderer->render();
463: }
464: echo $log;
465: } else {
466: $this->debugbar->sendDataInHeaders();
467: }
468: }
469: }
470:
471: /**
472: * PSR-3 System is unusable.
473: *
474: * @param string $message message
475: * @param array $context array of additional context
476: *
477: * @return null
478: */
479: public function emergency($message, array $context = array())
480: {
481: if ($this->activated) {
482: $this->log(LogLevel::EMERGENCY, $message, $context);
483: }
484: }
485:
486: /**
487: * PSR-3 Action must be taken immediately.
488: *
489: * Example: Entire website down, database unavailable, etc. This should
490: * trigger the SMS alerts and wake you up.
491: *
492: * @param string $message message
493: * @param array $context array of additional context
494: *
495: * @return null
496: */
497: public function alert($message, array $context = array())
498: {
499: if ($this->activated) {
500: $this->log(LogLevel::ALERT, $message, $context);
501: }
502: }
503:
504: /**
505: * PSR-3 Critical conditions.
506: *
507: * Example: Application component unavailable, unexpected exception.
508: *
509: * @param string $message message
510: * @param array $context array of additional context
511: *
512: * @return null
513: */
514: public function critical($message, array $context = array())
515: {
516: if ($this->activated) {
517: $this->log(LogLevel::CRITICAL, $message, $context);
518: }
519: }
520:
521: /**
522: * PSR-3 Runtime errors that do not require immediate action but should typically
523: * be logged and monitored.
524: *
525: * @param string $message message
526: * @param array $context array of additional context
527: *
528: * @return null
529: */
530: public function error($message, array $context = array())
531: {
532: if ($this->activated) {
533: $this->log(LogLevel::ERROR, $message, $context);
534: }
535: }
536:
537: /**
538: * PSR-3 Exceptional occurrences that are not errors.
539: *
540: * Example: Use of deprecated APIs, poor use of an API, undesirable things
541: * that are not necessarily wrong.
542: *
543: * @param string $message message
544: * @param array $context array of additional context
545: *
546: * @return null
547: */
548: public function warning($message, array $context = array())
549: {
550: if ($this->activated) {
551: $this->log(LogLevel::WARNING, $message, $context);
552: }
553: }
554:
555: /**
556: * PSR-3 Normal but significant events.
557: *
558: * @param string $message message
559: * @param array $context array of additional context
560: *
561: * @return null
562: */
563: public function notice($message, array $context = array())
564: {
565: if ($this->activated) {
566: $this->log(LogLevel::NOTICE, $message, $context);
567: }
568: }
569:
570: /**
571: * PSR-3 Interesting events.
572: *
573: * Example: User logs in, SQL logs.
574: *
575: * @param string $message message
576: * @param array $context array of additional context
577: *
578: * @return null
579: */
580: public function info($message, array $context = array())
581: {
582: if ($this->activated) {
583: $this->log(LogLevel::INFO, $message, $context);
584: }
585: }
586:
587: /**
588: * PSR-3 Detailed debug information.
589: *
590: * @param string $message message
591: * @param array $context array of additional context
592: *
593: * @return null
594: */
595: public function debug($message, array $context = array())
596: {
597: if ($this->activated) {
598: $this->log(LogLevel::DEBUG, $message, $context);
599: }
600: }
601:
602: /**
603: * PSR-3 Logs with an arbitrary level.
604: *
605: * @param mixed $level logging level
606: * @param string $message message
607: * @param array $context array of additional context
608: *
609: * @return null
610: */
611: public function log($level, $message, array $context = array())
612: {
613: if (!$this->activated) {
614: return;
615: }
616:
617: $channel = 'messages';
618: $msg = $message;
619:
620: /**
621: * If we have embedded channel in the context array, format the message
622: * approriatly using context values.
623: */
624: if (isset($context['channel'])) {
625: $chan = strtolower($context['channel']);
626: switch ($chan) {
627: case 'blocks':
628: $channel = 'Blocks';
629: $msg = $message . ': ';
630: if ($context['cached']) {
631: $msg .= sprintf(_MD_DEBUGBAR_CACHED, (int)($context['cachetime']));
632: } else {
633: $msg .= _MD_DEBUGBAR_NOT_CACHED;
634: }
635: break;
636: case 'deprecated':
637: $channel = 'Deprecated';
638: $msg = $message;
639: break;
640: case 'extra':
641: $channel = 'Extra';
642: $msg = $context['name'] . ': ' . $message;
643: break;
644: case 'queries':
645: $channel = 'Queries';
646: $msg = $message;
647: $qt = empty($context['query_time']) ?
648: '' : sprintf('%0.6f - ', $context['query_time']);
649: if ($level == LogLevel::ERROR) {
650: //if (!is_scalar($context['errno']) || !is_scalar($context['errno'])) {
651: // \Xmf\Debug::dump($context);
652: //}
653: $msg .= ' -- Error number: '
654: . (is_scalar($context['errno']) ? $context['errno'] : '?')
655: . ' Error message: '
656: . (is_scalar($context['error']) ? $context['error'] : '?');
657: }
658: $msg = $qt . $msg;
659: break;
660: }
661: }
662: switch ($level) {
663: case LogLevel::EMERGENCY:
664: $this->debugbar[$channel]->emergency($msg);
665: break;
666: case LogLevel::ALERT:
667: $this->debugbar[$channel]->alert($msg);
668: break;
669: case LogLevel::CRITICAL:
670: $this->debugbar[$channel]->critical($msg);
671: break;
672: case LogLevel::ERROR:
673: $this->debugbar[$channel]->error($msg);
674: break;
675: case LogLevel::WARNING:
676: $this->debugbar[$channel]->warning($msg);
677: break;
678: case LogLevel::NOTICE:
679: $this->debugbar[$channel]->notice($msg);
680: break;
681: case LogLevel::INFO:
682: $this->debugbar[$channel]->info($msg);
683: break;
684: case LogLevel::DEBUG:
685: default:
686: $this->debugbar[$channel]->debug($msg);
687: break;
688: }
689: }
690: }
691: