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 Monolog\Logger as MLogger;
13: use Monolog\Formatter\LineFormatter;
14: //use Monolog\Handler\FirePHPHandler;
15: use Monolog\Handler\RotatingFileHandler;
16: use Monolog\Handler\StreamHandler;
17: use Monolog\Processor\WebProcessor;
18: use Psr\Log\LoggerInterface;
19: use Psr\Log\LogLevel;
20: use Xoops\Core\Logger;
21:
22: /**
23: * Collects log information and present to PHPDebugBar for display.
24: * Records information about database queries, blocks, and execution time
25: * and various logs.
26: *
27: * @category DebugbarLogger
28: * @package DebugbarLogger
29: * @author Richard Griffith <richard@geekwright.com>
30: * @copyright 2013 XOOPS Project (http://xoops.org)
31: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
32: * @version Release: 1.0
33: * @link http://xoops.org
34: * @since 1.0
35: */
36: class MonologLogger implements LoggerInterface
37: {
38: /**
39: * @var object
40: */
41: private $monolog = false;
42:
43: /**
44: * @var object
45: */
46: private $activated = false;
47:
48: /**
49: * @var array named start times
50: */
51: private $starttimes = array();
52:
53: /**
54: * @var array named start times
55: */
56: private $configs = false;
57:
58: /**
59: * constructor
60: */
61: public function __construct()
62: {
63: Logger::getInstance()->addLogger($this);
64: }
65:
66: /**
67: * Get a reference to the only instance of this class
68: *
69: * @return object LoggerAbstract reference to the only instance
70: */
71: public static function getInstance()
72: {
73: static $instance;
74: if (!isset($instance)) {
75: $class = __CLASS__;
76: $instance = new $class();
77: }
78:
79: return $instance;
80: }
81:
82: /**
83: * disable logging
84: *
85: * @return void
86: */
87: public function disable()
88: {
89: //error_reporting(0);
90: $this->activated = false;
91: }
92:
93: /**
94: * Enable logger output
95: *
96: * @return void
97: */
98: public function enable()
99: {
100: error_reporting(E_ALL | E_STRICT);
101:
102: $this->activated = true;
103:
104: if (!$this->monolog) {
105: // Create the logger
106: $this->monolog = new \Monolog\Logger('app');
107: $proc = new WebProcessor();
108: $this->monolog->pushProcessor($proc);
109: $this->monolog->pushProcessor(array($this,'xoopsDataProcessor'));
110:
111: $formatter = new LineFormatter();
112: //$formatter = new LogstashFormatter;
113: switch ($this->configs['logging_threshold']) {
114: case 'error':
115: $threshold=MLogger::ERROR;
116: break;
117: case 'warning':
118: $threshold=MLogger::WARNING;
119: break;
120: case 'info':
121: $threshold=MLogger::INFO;
122: break;
123: case 'debug':
124: default:
125: $threshold=MLogger::DEBUG;
126: break;
127: }
128: if ((int)($this->configs['max_versions']) == 0) {
129: $stream = new StreamHandler($this->configs['log_file_path'], $threshold);
130: } else {
131: $stream = new RotatingFileHandler(
132: $this->configs['log_file_path'],
133: $this->configs['max_versions'],
134: $threshold
135: );
136: }
137: $stream->setFormatter($formatter);
138: $this->monolog->pushHandler($stream);
139: }
140: //if ($this->monolog && $this->configs['phpfire_enable']) {
141: // $firephp = new FirePHPHandler();
142: // $this->monolog->pushHandler($firephp);
143: //}
144: }
145:
146: /**
147: * adds Xoops specific information to the log record
148: *
149: * @param array $record log record contents
150: *
151: * @return void
152: */
153: public function xoopsDataProcessor($record)
154: {
155: $xoops = \Xoops::getInstance();
156: $record['extra']['user'] = '?';
157: @$record['extra']['user'] = $xoops->isUser() ? $xoops->user->getVar('uname') : 'n/a';
158: return $record;
159: }
160:
161: /**
162: * set configuration items
163: *
164: * @param array $configs module/user configuration items
165: *
166: * @return void
167: */
168: public function setConfigs($configs)
169: {
170: $this->configs=$configs;
171: }
172:
173: /**
174: * report enabled status
175: *
176: * @return bool
177: */
178: public function isEnable()
179: {
180: return $this->activated;
181: }
182:
183: /**
184: * disable output for the benefit of ajax scripts
185: *
186: * @return void
187: */
188: public function quiet()
189: {
190: //$this->activated = false;
191: }
192:
193: /**
194: * Start a timer
195: *
196: * @param string $name name of the timer
197: *
198: * @return void
199: */
200: public function startTime($name = 'XOOPS')
201: {
202: $this->starttimes[$name] = microtime(true);
203: }
204:
205: /**
206: * Stop a timer
207: *
208: * @param string $name name of the timer
209: *
210: * @return void
211: */
212: public function stopTime($name = 'XOOPS')
213: {
214: if ($this->activated) {
215: if (array_key_exists($name, $this->starttimes)) {
216: $elapsed = microtime(true) - $this->starttimes[$name];
217: $msg = sprintf(_MD_MONOLOG_TIMETOLOAD, htmlspecialchars($name), $elapsed);
218: $context = array(
219: 'channel'=>'Timers',
220: );
221: $this->log(LogLevel::INFO, $msg, $context);
222: }
223: }
224: }
225:
226: /**
227: * Log a database query
228: *
229: * @param string $sql sql that was processed
230: * @param string $error error message
231: * @param int $errno error number
232: * @param float $query_time execution time
233: *
234: * @return void
235: */
236: public function addQuery($sql, $error = null, $errno = null, $query_time = null)
237: {
238: if ($this->activated) {
239: $level = LogLevel::INFO;
240: if (!empty($error)) {
241: $level = LogLevel::ERROR;
242: }
243: $context = array(
244: 'channel'=>'Queries',
245: 'error'=>$error,
246: 'errno'=>$errno,
247: 'query_time'=>$query_time
248: );
249: $this->log($level, $sql, $context);
250: }
251: }
252:
253: /**
254: * Log display of a block
255: *
256: * @param string $name name of the block
257: * @param bool $cached was the block cached?
258: * @param int $cachetime cachetime of the block
259: *
260: * @return void
261: */
262: public function addBlock($name, $cached = false, $cachetime = 0)
263: {
264: if ($this->activated) {
265: $context = array('channel'=>'Blocks', 'cached'=>$cached, 'cachetime'=>$cachetime);
266: $this->log(LogLevel::INFO, $name, $context);
267: }
268: }
269:
270: /**
271: * Log extra information
272: *
273: * @param string $name name for the entry
274: * @param string $msg text message for the entry
275: *
276: * @return void
277: */
278: public function addExtra($name, $msg)
279: {
280: if ($this->activated) {
281: $context = array('channel'=>'Extra', 'name'=>$name);
282: $this->log(LogLevel::INFO, $msg, $context);
283: }
284: }
285:
286: /**
287: * Log messages for deprecated functions
288: *
289: * @param string $msg name for the entry
290: *
291: * @return void
292: */
293: public function addDeprecated($msg)
294: {
295: if ($this->activated) {
296: $this->log(LogLevel::WARNING, $msg, array('channel'=>'Deprecated'));
297: }
298: }
299:
300: /**
301: * Log exceptions
302: *
303: * @param Exception $e name for the entry
304: *
305: * @return void
306: */
307: public function addException($e)
308: {
309: if ($this->activated) {
310: $this->error(
311: sprintf(
312: $this->messageTag('_MD_MONOLOG_EXCEPTION', 'Exception* : %s : file %s line %s'),
313: $e->getMessage(),
314: $this->sanitizePath($e->getFile()),
315: $e->getLine()
316: ),
317: array('exception' => $e)
318: );
319: }
320: }
321:
322: /**
323: * sanitizePath
324: *
325: * @param string $path path name to sanitize
326: *
327: * @return string path with top levels removed
328: */
329: public function sanitizePath($path)
330: {
331: $path = str_replace(
332: array('\\', \XoopsBaseConfig::get('root-path'), str_replace('\\', '/', realpath(\XoopsBaseConfig::get('root-path')))),
333: array('/', '', ''),
334: $path
335: );
336: return $path;
337: }
338:
339: /**
340: * PSR-3 System is unusable.
341: *
342: * @param string $message message
343: * @param array $context array of additional context
344: *
345: * @return null
346: */
347: public function emergency($message, array $context = array())
348: {
349: if ($this->activated) {
350: $this->log(LogLevel::EMERGENCY, $message, $context);
351: }
352: }
353:
354: /**
355: * PSR-3 Action must be taken immediately.
356: *
357: * Example: Entire website down, database unavailable, etc. This should
358: * trigger the SMS alerts and wake you up.
359: *
360: * @param string $message message
361: * @param array $context array of additional context
362: *
363: * @return null
364: */
365: public function alert($message, array $context = array())
366: {
367: if ($this->activated) {
368: $this->log(LogLevel::ALERT, $message, $context);
369: }
370: }
371:
372: /**
373: * PSR-3 Critical conditions.
374: *
375: * Example: Application component unavailable, unexpected exception.
376: *
377: * @param string $message message
378: * @param array $context array of additional context
379: *
380: * @return null
381: */
382: public function critical($message, array $context = array())
383: {
384: if ($this->activated) {
385: $this->log(LogLevel::CRITICAL, $message, $context);
386: }
387: }
388:
389: /**
390: * PSR-3 Runtime errors that do not require immediate action but should typically
391: * be logged and monitored.
392: *
393: * @param string $message message
394: * @param array $context array of additional context
395: *
396: * @return null
397: */
398: public function error($message, array $context = array())
399: {
400: if ($this->activated) {
401: $this->log(LogLevel::ERROR, $message, $context);
402: }
403: }
404:
405: /**
406: * PSR-3 Exceptional occurrences that are not errors.
407: *
408: * Example: Use of deprecated APIs, poor use of an API, undesirable things
409: * that are not necessarily wrong.
410: *
411: * @param string $message message
412: * @param array $context array of additional context
413: *
414: * @return null
415: */
416: public function warning($message, array $context = array())
417: {
418: if ($this->activated) {
419: $this->log(LogLevel::WARNING, $message, $context);
420: }
421: }
422:
423: /**
424: * PSR-3 Normal but significant events.
425: *
426: * @param string $message message
427: * @param array $context array of additional context
428: *
429: * @return null
430: */
431: public function notice($message, array $context = array())
432: {
433: if ($this->activated) {
434: $this->log(LogLevel::NOTICE, $message, $context);
435: }
436: }
437:
438: /**
439: * PSR-3 Interesting events.
440: *
441: * Example: User logs in, SQL logs.
442: *
443: * @param string $message message
444: * @param array $context array of additional context
445: *
446: * @return null
447: */
448: public function info($message, array $context = array())
449: {
450: if ($this->activated) {
451: $this->log(LogLevel::INFO, $message, $context);
452: }
453: }
454:
455: /**
456: * PSR-3 Detailed debug information.
457: *
458: * @param string $message message
459: * @param array $context array of additional context
460: *
461: * @return null
462: */
463: public function debug($message, array $context = array())
464: {
465: if ($this->activated) {
466: $this->log(LogLevel::DEBUG, $message, $context);
467: }
468: }
469:
470: /**
471: * messageTag returns the value of a language constant if it is defined,
472: * or the supplied default if the constant is not defined. This is needed
473: * because logging code can run before locale is established.
474: *
475: * @param string $tag the constant name
476: * @param string $default a default value
477: *
478: * @return string constant or default value
479: */
480: private function messageTag($tag, $default)
481: {
482: return defined($tag) ? constant($tag) : $default;
483: }
484:
485: /**
486: * PSR-3 Logs with an arbitrary level.
487: *
488: * @param mixed $level logging level
489: * @param string $message message
490: * @param array $context array of additional context
491: *
492: * @return null
493: */
494: public function log($level, $message, array $context = array())
495: {
496: if (!$this->activated) {
497: return;
498: }
499:
500: $channel = 'messages';
501: $msg = $message;
502:
503: /**
504: * If we have embedded channel in the context array, format the message
505: * approriatly using context values.
506: */
507: if (isset($context['channel'])) {
508: $chan = strtolower($context['channel']);
509: switch ($chan) {
510: case 'blocks':
511: if (!$this->configs['include_blocks']) {
512: return;
513: }
514: //$channel = 'Blocks';
515: $msg = _MD_MONOLOG_BLOCKS . ' : ' . $message . ': ';
516: if ($context['cached']) {
517: $msg .= sprintf(_MD_MONOLOG_CACHED, (int)($context['cachetime']));
518: } else {
519: $msg .= _MD_MONOLOG_NOT_CACHED;
520: }
521: break;
522: case 'deprecated':
523: if (!$this->configs['include_deprecated']) {
524: return;
525: }
526: //$channel = 'Deprecated';
527: $msg = $this->messageTag('_MD_MONOLOG_DEPRECATED', 'Deprecated*') . ' : ' . $message;
528: //$msg = _MD_MONOLOG_DEPRECATED . ' : ' . $message;
529: break;
530: case 'extra':
531: if (!$this->configs['include_extra']) {
532: return;
533: }
534: //$channel = 'Extra';
535: $msg = _MD_MONOLOG_EXTRA . ' : ' . $context['name'] . ': ' . $message;
536: break;
537: case 'queries':
538: if (!$this->configs['include_queries']) {
539: return;
540: }
541: //$channel = 'Queries';
542: $msg = $message;
543: $qt = empty($context['query_time']) ?
544: '' : sprintf('%0.6f - ', $context['query_time']);
545: if ($level == LogLevel::ERROR) {
546: //if (!is_scalar($context['errno']) || !is_scalar($context['errno'])) {
547: // \Xmf\Debug::dump($context);
548: //}
549: $msg .= ' -- Error number: '
550: . (is_scalar($context['errno']) ? $context['errno'] : '?')
551: . ' Error message: '
552: . (is_scalar($context['error']) ? $context['error'] : '?');
553: }
554: $msg = $this->messageTag('_MD_MONOLOG_QUERIES', 'Queries*') . ' : ' . $qt . $msg;
555: break;
556: case 'timers':
557: if (!$this->configs['include_timers']) {
558: return;
559: }
560: $msg = $this->messageTag('_MD_MONOLOG_TIMERS', 'Timers*') . ' : ' . $message;
561: break;
562: default:
563: $msg = $this->messageTag('_MD_MONOLOG_ERRORS', 'Errors*') . ' : ' . $message;
564: break;
565: }
566: } else {
567: $msg = $this->messageTag('_MD_MONOLOG_MESSAGES', 'Message*') . ' : ' . $message;
568: }
569: switch ($level) {
570: case LogLevel::EMERGENCY:
571: $this->monolog->emergency($msg, $context);
572: break;
573: case LogLevel::ALERT:
574: $this->monolog->alert($msg, $context);
575: break;
576: case LogLevel::CRITICAL:
577: $this->monolog->critical($msg, $context);
578: break;
579: case LogLevel::ERROR:
580: $this->monolog->error($msg, $context);
581: break;
582: case LogLevel::WARNING:
583: $this->monolog->warning($msg, $context);
584: break;
585: case LogLevel::NOTICE:
586: $this->monolog->notice($msg, $context);
587: break;
588: case LogLevel::INFO:
589: $this->monolog->info($msg, $context);
590: break;
591: case LogLevel::DEBUG:
592: default:
593: $this->monolog->debug($msg, $context);
594: break;
595: }
596: }
597: }
598: