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: namespace Xoops\Core;
13:
14: use Psr\Log\LogLevel;
15: use Psr\Log\LoggerInterface;
16:
17: /**
18: * Xoops\Core\Logger - dispatch log requests to any registered loggers.
19: *
20: * No logging is done in this class, but any logger, implemented as a
21: * module or extension, can register as a logger using the addLogger()
22: * method. Multiple loggers can be registered, and each will be
23: * invoked in turn for each log() call.
24: *
25: * Such loggers are expected to implement the PSR-3 LoggerInterface.
26: * In addition, any logger that generates output as part of the XOOPS
27: * delivered page should implement the quiet() method, to disable output.
28: *
29: * Loggers are managed this way so that any routine may easily add a
30: * log entry without needing to know any details of the implementation.
31: *
32: * Not all events are published through this mechanism, only specific requests
33: * to log() or related methods. Individual loggers may listen for events (i.e.
34: * preloads) or other sources and gain access to detailed debugging information.
35: *
36: * @category Xoops\Core\Logger
37: * @package Logger
38: * @author Richard Griffith <richard@geekwright.com>
39: * @copyright 2013 XOOPS Project (http://xoops.org)
40: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
41: * @version Release: 2.6.0
42: * @link http://xoops.org
43: * @since 2.6.0
44: */
45: class Logger implements LoggerInterface
46: {
47: /**
48: * @var LoggerInterface[] chain of PSR-3 compatible loggers to call
49: */
50: private $loggers = array();
51:
52: /**
53: * @var boolean do we have active loggers?
54: */
55: private $logging_active = false;
56:
57: /**
58: * @var boolean just to prevent fatal legacy errors. Does nothing. Stop it!
59: */
60: //public $activated = false;
61:
62: /**
63: * Get the Xoops\Core\Logger instance
64: *
65: * @return Logger object
66: */
67: public static function getInstance()
68: {
69: static $instance;
70: if (!isset($instance)) {
71: $class = __CLASS__;
72: $instance = new $class();
73: // Always catch errors, for security reasons
74: set_error_handler(array($instance, 'handleError'));
75: // grab any uncaught exception
76: set_exception_handler(array($instance, 'handleException'));
77: }
78:
79: return $instance;
80: }
81:
82: /**
83: * Error handling callback.
84: *
85: * This will
86: *
87: * @param integer $errorNumber error number
88: * @param string $errorString error message
89: * @param string $errorFile file
90: * @param integer $errorLine line number
91: *
92: * @return void
93: */
94: public function handleError($errorNumber, $errorString, $errorFile, $errorLine)
95: {
96: if ($this->logging_active && ($errorNumber & error_reporting())) {
97:
98: // if an error occurs before a locale is established,
99: // we still need messages, so check and deal with it
100:
101: $msg = ': ' . sprintf(
102: (class_exists('\XoopsLocale', false) ? \XoopsLocale::EF_LOGGER_FILELINE : "%s in file %s line %s"),
103: $this->sanitizePath($errorString),
104: $this->sanitizePath($errorFile),
105: $errorLine
106: );
107:
108: switch ($errorNumber) {
109: case E_USER_NOTICE:
110: $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_ERROR : '*Error:') . $msg;
111: $this->log(LogLevel::NOTICE, $msg);
112: break;
113: case E_NOTICE:
114: $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_NOTICE : '*Notice:') . $msg;
115: $this->log(LogLevel::NOTICE, $msg);
116: break;
117: case E_WARNING:
118: $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_WARNING : '*Warning:') . $msg;
119: $this->log(LogLevel::WARNING, $msg);
120: break;
121: case E_STRICT:
122: $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_STRICT : '*Strict:') . $msg;
123: $this->log(LogLevel::WARNING, $msg);
124: break;
125: case E_USER_ERROR:
126: $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_ERROR : '*Error:') . $msg;
127: @$this->log(LogLevel::CRITICAL, $msg);
128: break;
129: default:
130: $msg = (class_exists('\XoopsLocale', false) ? \XoopsLocale::E_LOGGER_UNKNOWN : '*Unknown:') . $msg;
131: $this->log(LogLevel::ERROR, $msg);
132: break;
133: }
134: }
135:
136: if ($errorNumber == E_USER_ERROR) {
137: $trace = true;
138: if (substr($errorString, 0, '8') === 'notrace:') {
139: $trace = false;
140: $errorString = substr($errorString, 8);
141: }
142: $this->reportFatalError($errorString);
143: if ($trace) {
144: $trace = debug_backtrace();
145: array_shift($trace);
146: if ('cli' === php_sapi_name()) {
147: foreach ($trace as $step) {
148: if (isset($step['file'])) {
149: fprintf(STDERR, "%s (%d)\n", $this->sanitizePath($step['file']), $step['line']);
150: }
151: }
152: } else {
153: echo "<div style='color:#f0f0f0;background-color:#f0f0f0'>" . _XOOPS_FATAL_BACKTRACE . ":<br />";
154: foreach ($trace as $step) {
155: if (isset($step['file'])) {
156: printf("%s (%d)\n<br />", $this->sanitizePath($step['file']), $step['line']);
157: }
158: }
159: echo '</div>';
160: }
161: }
162: exit();
163: }
164: }
165:
166: /**
167: * Exception handling callback.
168: *
169: * This will
170: *
171: * @param \Exception|\Throwable $e uncaught Exception or Error
172: *
173: * @return void
174: */
175: public function handleException($e)
176: {
177: $msg = $e->getMessage();
178: $this->reportFatalError($msg);
179: }
180:
181: /**
182: * Announce fatal error, attempt to log
183: *
184: * @param string $msg error message to report
185: *
186: * @return void
187: */
188: private function reportFatalError($msg)
189: {
190: $msg=$this->sanitizePath($msg);
191: if ('cli' === php_sapi_name()) {
192: fprintf(STDERR, "\nError : %s\n", $msg);
193: } else {
194: printf(_XOOPS_FATAL_MESSAGE, XOOPS_URL, $msg);
195: }
196: @$this->log(LogLevel::CRITICAL, $msg);
197: }
198:
199: /**
200: * clean a path to remove sensitive details
201: *
202: * @param string $message text to sanitize
203: *
204: * @return string sanitized message
205: */
206: public function sanitizePath($message)
207: {
208: $stringsToClean = array(
209: '\\',
210: \XoopsBaseConfig::get('var-path'),
211: str_replace('\\', '/', realpath(\XoopsBaseConfig::get('var-path'))),
212: \XoopsBaseConfig::get('lib-path'),
213: str_replace('\\', '/', realpath(\XoopsBaseConfig::get('lib-path'))),
214: \XoopsBaseConfig::get('root-path'),
215: str_replace('\\', '/', realpath(\XoopsBaseConfig::get('root-path'))),
216: \XoopsBaseConfig::get('db-name') . '.',
217: \XoopsBaseConfig::get('db-name'),
218: \XoopsBaseConfig::get('db-prefix') . '_',
219: \XoopsBaseConfig::get('db-user'),
220: \XoopsBaseConfig::get('db-pass'),
221: );
222:
223: $replacementStings = array(
224: '/',
225: 'VAR',
226: 'VAR',
227: 'LIB',
228: 'LIB',
229: 'ROOT',
230: 'ROOT',
231: '',
232: '',
233: '',
234: '***',
235: '***',
236: );
237:
238: $message = str_replace($stringsToClean, $replacementStings, $message);
239:
240: return $message;
241: }
242:
243: /**
244: * add a PSR-3 compatible logger to the chain
245: *
246: * @param object $logger a PSR-3 compatible logger object
247: *
248: * @return void
249: */
250: public function addLogger($logger)
251: {
252: if (is_object($logger) && method_exists($logger, 'log')) {
253: $this->loggers[] = $logger;
254: $this->logging_active = true;
255: }
256: }
257:
258: /**
259: * System is unusable.
260: *
261: * @param string $message message
262: * @param array $context array of context data for this log entry
263: *
264: * @return void
265: */
266: public function emergency($message, array $context = array())
267: {
268: $this->log(LogLevel::EMERGENCY, $message, $context);
269: }
270:
271: /**
272: * Action must be taken immediately.
273: *
274: * Example: Entire website down, database unavailable, etc. This should
275: * trigger the SMS alerts and wake you up.
276: *
277: * @param string $message message
278: * @param array $context array of context data for this log entry
279: *
280: * @return void
281: */
282: public function alert($message, array $context = array())
283: {
284: $this->log(LogLevel::ALERT, $message, $context);
285: }
286:
287: /**
288: * Critical conditions.
289: *
290: * Example: Application component unavailable, unexpected exception.
291: *
292: * @param string $message message
293: * @param array $context array of context data for this log entry
294: *
295: * @return void
296: */
297: public function critical($message, array $context = array())
298: {
299: $this->log(LogLevel::CRITICAL, $message, $context);
300: }
301:
302: /**
303: * Runtime errors that do not require immediate action but should typically
304: * be logged and monitored.
305: *
306: * @param string $message message
307: * @param array $context array of context data for this log entry
308: *
309: * @return void
310: */
311: public function error($message, array $context = array())
312: {
313: $this->log(LogLevel::ERROR, $message, $context);
314: }
315:
316: /**
317: * Exceptional occurrences that are not errors.
318: *
319: * Example: Use of deprecated APIs, poor use of an API, undesirable things
320: * that are not necessarily wrong.
321: *
322: * @param string $message message
323: * @param array $context array of context data for this log entry
324: *
325: * @return void
326: */
327: public function warning($message, array $context = array())
328: {
329: $this->log(LogLevel::WARNING, $message, $context);
330: }
331:
332: /**
333: * Normal but significant events.
334: *
335: * @param string $message message
336: * @param array $context array of context data for this log entry
337: *
338: * @return void
339: */
340: public function notice($message, array $context = array())
341: {
342: $this->log(LogLevel::NOTICE, $message, $context);
343: }
344:
345: /**
346: * Interesting events.
347: *
348: * Example: User logs in, SQL logs.
349: *
350: * @param string $message message
351: * @param array $context array of context data for this log entry
352: *
353: * @return void
354: */
355: public function info($message, array $context = array())
356: {
357: $this->log(LogLevel::INFO, $message, $context);
358: }
359:
360: /**
361: * Detailed debug information.
362: *
363: * @param string $message message
364: * @param array $context array of context data for this log entry
365: *
366: * @return void
367: */
368: public function debug($message, array $context = array())
369: {
370: $this->log(LogLevel::DEBUG, $message, $context);
371: }
372:
373: /**
374: * Logs with an arbitrary level.
375: *
376: * @param mixed $level PSR-3 LogLevel constant
377: * @param string $message message
378: * @param array $context array of context data for this log entry
379: *
380: * @return void
381: */
382: public function log($level, $message, array $context = array())
383: {
384: if (!empty($this->loggers)) {
385: foreach ($this->loggers as $logger) {
386: if (is_object($logger)) {
387: try {
388: $logger->log($level, $message, $context);
389: } catch (\Exception $e) {
390: // just ignore, as we can't do anything, not even log it.
391: }
392: }
393: }
394: }
395: }
396:
397: /**
398: * quiet - turn off output if output is rendered in XOOPS page output.
399: * This is intended to assist ajax code that may fail with any extra
400: * content the logger may introduce.
401: *
402: * It should have no effect on loggers using other methods, such a write
403: * to file.
404: *
405: * @return void
406: */
407: public function quiet()
408: {
409: if (!empty($this->loggers)) {
410: foreach ($this->loggers as $logger) {
411: if (is_object($logger) && method_exists($logger, 'quiet')) {
412: try {
413: $logger->quiet();
414: } catch (\Exception $e) {
415: // just ignore, as we can't do anything, not even log it.
416: }
417: }
418: }
419: }
420: }
421:
422: // Deprecated uses
423:
424: /**
425: * Keep deprecated calls from failing
426: *
427: * @param string $var property
428: * @param string $val value
429: *
430: * @return void
431: *
432: * @deprecated
433: */
434: public function __set($var, $val)
435: {
436: $this->deprecatedMessage();
437: // legacy compatibility: turn off logger display for $xoopsLogger->activated = false; usage
438: if ($var==='activated' && !$val) {
439: $this->quiet();
440: }
441:
442: }
443:
444: /**
445: * Keep deprecated calls from failing
446: *
447: * @param string $var property
448: *
449: * @return void
450: *
451: * @deprecated
452: */
453: public function __get($var)
454: {
455: $this->deprecatedMessage();
456: }
457:
458: /**
459: * Keep deprecated calls from failing
460: *
461: * @param string $method method
462: * @param string $args arguments
463: *
464: * @return void
465: *
466: * @deprecated
467: */
468: public function __call($method, $args)
469: {
470: $this->deprecatedMessage();
471: }
472:
473: /**
474: * issue a deprecated warning
475: *
476: * @return void
477: */
478: private function deprecatedMessage()
479: {
480: $xoops = \Xoops::getInstance();
481: $xoops->deprecated('This use of XoopsLogger is deprecated since 2.6.0.');
482: }
483: }
484: