PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/core/Log.php

https://github.com/CodeYellowBV/piwik
PHP | 682 lines | 310 code | 65 blank | 307 comment | 47 complexity | a2a4f6bad48b00bcc7a32ccc0c9eb0c2 MD5 | raw file
Possible License(s): LGPL-3.0, JSON, MIT, GPL-3.0, LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-2-Clause, BSD-3-Clause
  1. <?php
  2. /**
  3. * Piwik - free/libre analytics platform
  4. *
  5. * @link http://piwik.org
  6. * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
  7. *
  8. */
  9. namespace Piwik;
  10. use Piwik\Db;
  11. /**
  12. * Logging utility class.
  13. *
  14. * Log entries are made with a message and log level. The logging utility will tag each
  15. * log entry with the name of the plugin that's doing the logging. If no plugin is found,
  16. * the name of the current class is used.
  17. *
  18. * You can log messages using one of the public static functions (eg, 'error', 'warning',
  19. * 'info', etc.). Messages logged with the **error** level will **always** be logged to
  20. * the screen, regardless of whether the [log] log_writer config option includes the
  21. * screen writer.
  22. *
  23. * Currently, Piwik supports the following logging backends:
  24. *
  25. * - **screen**: logging to the screen
  26. * - **file**: logging to a file
  27. * - **database**: logging to Piwik's MySQL database
  28. *
  29. * ### Logging configuration
  30. *
  31. * The logging utility can be configured by manipulating the INI config options in the
  32. * `[log]` section.
  33. *
  34. * The following configuration options can be set:
  35. *
  36. * - `log_writers[]`: This is an array of log writer IDs. The three log writers provided
  37. * by Piwik core are **file**, **screen** and **database**. You can
  38. * get more by installing plugins. The default value is **screen**.
  39. * - `log_level`: The current log level. Can be **ERROR**, **WARN**, **INFO**, **DEBUG**,
  40. * or **VERBOSE**. Log entries made with a log level that is as or more
  41. * severe than the current log level will be outputted. Others will be
  42. * ignored. The default level is **WARN**.
  43. * - `log_only_when_cli`: 0 or 1. If 1, logging is only enabled when Piwik is executed
  44. * in the command line (for example, by the core:archive command
  45. * script). Default: 0.
  46. * - `log_only_when_debug_parameter`: 0 or 1. If 1, logging is only enabled when the
  47. * `debug` query parameter is 1. Default: 0.
  48. * - `logger_file_path`: For the file log writer, specifies the path to the log file
  49. * to log to or a path to a directory to store logs in. If a
  50. * directory, the file name is piwik.log. Can be relative to
  51. * Piwik's root dir or an absolute path. Defaults to **tmp/logs**.
  52. *
  53. * ### Custom message formatting
  54. *
  55. * If you'd like to format log messages differently for different backends, you can use
  56. * one of the `'Log.format...Message'` events.
  57. *
  58. * These events are fired when an object is logged. You can create your own custom class
  59. * containing the information to log and listen to these events to format it correctly for
  60. * different backends.
  61. *
  62. * If you don't care about the backend when formatting an object, implement a `__toString()`
  63. * in the custom class.
  64. *
  65. * ### Custom log writers
  66. *
  67. * New logging backends can be added via the {@hook Log.getAvailableWriters}` event. A log
  68. * writer is just a callback that accepts log entry information (such as the message,
  69. * level, etc.), so any backend could conceivably be used (including existing PSR3
  70. * backends).
  71. *
  72. * ### Examples
  73. *
  74. * **Basic logging**
  75. *
  76. * Log::error("This log message will end up on the screen and in a file.")
  77. * Log::verbose("This log message uses %s params, but %s will only be called if the"
  78. * . " configured log level includes %s.", "sprintf", "sprintf", "verbose");
  79. *
  80. * **Logging objects**
  81. *
  82. * class MyDebugInfo
  83. * {
  84. * // ...
  85. *
  86. * public function __toString()
  87. * {
  88. * return // ...
  89. * }
  90. * }
  91. *
  92. * try {
  93. * $myThirdPartyServiceClient->doSomething();
  94. * } catch (Exception $unexpectedError) {
  95. * $debugInfo = new MyDebugInfo($unexpectedError, $myThirdPartyServiceClient);
  96. * Log::debug($debugInfo);
  97. * }
  98. *
  99. * @method static \Piwik\Log getInstance()
  100. */
  101. class Log extends Singleton
  102. {
  103. // log levels
  104. const NONE = 0;
  105. const ERROR = 1;
  106. const WARN = 2;
  107. const INFO = 3;
  108. const DEBUG = 4;
  109. const VERBOSE = 5;
  110. // config option names
  111. const LOG_LEVEL_CONFIG_OPTION = 'log_level';
  112. const LOG_WRITERS_CONFIG_OPTION = 'log_writers';
  113. const LOGGER_FILE_PATH_CONFIG_OPTION = 'logger_file_path';
  114. const STRING_MESSAGE_FORMAT_OPTION = 'string_message_format';
  115. const FORMAT_FILE_MESSAGE_EVENT = 'Log.formatFileMessage';
  116. const FORMAT_SCREEN_MESSAGE_EVENT = 'Log.formatScreenMessage';
  117. const FORMAT_DATABASE_MESSAGE_EVENT = 'Log.formatDatabaseMessage';
  118. const GET_AVAILABLE_WRITERS_EVENT = 'Log.getAvailableWriters';
  119. /**
  120. * The current logging level. Everything of equal or greater priority will be logged.
  121. * Everything else will be ignored.
  122. *
  123. * @var int
  124. */
  125. private $currentLogLevel = self::WARN;
  126. /**
  127. * The array of callbacks executed when logging to a file. Each callback writes a log
  128. * message to a logging backend.
  129. *
  130. * @var array
  131. */
  132. private $writers = array();
  133. /**
  134. * The log message format string that turns a tag name, date-time and message into
  135. * one string to log.
  136. *
  137. * @var string
  138. */
  139. private $logMessageFormat = "%level% %tag%[%datetime%] %message%";
  140. /**
  141. * If we're logging to a file, this is the path to the file to log to.
  142. *
  143. * @var string
  144. */
  145. private $logToFilePath;
  146. /**
  147. * True if we're currently setup to log to a screen, false if otherwise.
  148. *
  149. * @var bool
  150. */
  151. private $loggingToScreen;
  152. /**
  153. * Constructor.
  154. */
  155. protected function __construct()
  156. {
  157. /**
  158. * access a property that is not overriden by TestingEnvironment before accessing log as the
  159. * log section is used in TestingEnvironment. Otherwise access to magic __get('log') fails in
  160. * TestingEnvironment as it tries to acccess it already here with __get('log').
  161. * $config->log ==> __get('log') ==> Config.createConfigInstance ==> nested __get('log') ==> returns null
  162. */
  163. $initConfigToPreventErrorWhenAccessingLog = Config::getInstance()->mail;
  164. $logConfig = Config::getInstance()->log;
  165. $this->setCurrentLogLevelFromConfig($logConfig);
  166. $this->setLogWritersFromConfig($logConfig);
  167. $this->setLogFilePathFromConfig($logConfig);
  168. $this->setStringLogMessageFormat($logConfig);
  169. $this->disableLoggingBasedOnConfig($logConfig);
  170. }
  171. /**
  172. * Logs a message using the ERROR log level.
  173. *
  174. * _Note: Messages logged with the ERROR level are always logged to the screen in addition
  175. * to configured writers._
  176. *
  177. * @param string $message The log message. This can be a sprintf format string.
  178. * @param ... mixed Optional sprintf params.
  179. * @api
  180. */
  181. public static function error($message /* ... */)
  182. {
  183. self::logMessage(self::ERROR, $message, array_slice(func_get_args(), 1));
  184. }
  185. /**
  186. * Logs a message using the WARNING log level.
  187. *
  188. * @param string $message The log message. This can be a sprintf format string.
  189. * @param ... mixed Optional sprintf params.
  190. * @api
  191. */
  192. public static function warning($message /* ... */)
  193. {
  194. self::logMessage(self::WARN, $message, array_slice(func_get_args(), 1));
  195. }
  196. /**
  197. * Logs a message using the INFO log level.
  198. *
  199. * @param string $message The log message. This can be a sprintf format string.
  200. * @param ... mixed Optional sprintf params.
  201. * @api
  202. */
  203. public static function info($message /* ... */)
  204. {
  205. self::logMessage(self::INFO, $message, array_slice(func_get_args(), 1));
  206. }
  207. /**
  208. * Logs a message using the DEBUG log level.
  209. *
  210. * @param string $message The log message. This can be a sprintf format string.
  211. * @param ... mixed Optional sprintf params.
  212. * @api
  213. */
  214. public static function debug($message /* ... */)
  215. {
  216. self::logMessage(self::DEBUG, $message, array_slice(func_get_args(), 1));
  217. }
  218. /**
  219. * Logs a message using the VERBOSE log level.
  220. *
  221. * @param string $message The log message. This can be a sprintf format string.
  222. * @param ... mixed Optional sprintf params.
  223. * @api
  224. */
  225. public static function verbose($message /* ... */)
  226. {
  227. self::logMessage(self::VERBOSE, $message, array_slice(func_get_args(), 1));
  228. }
  229. /**
  230. * Creates log message combining logging info including a log level, tag name,
  231. * date time, and caller-provided log message. The log message can be set through
  232. * the `[log] string_message_format` INI config option. By default it will
  233. * create log messages like:
  234. *
  235. * **LEVEL [tag:datetime] log message**
  236. *
  237. * @param int $level
  238. * @param string $tag
  239. * @param string $datetime
  240. * @param string $message
  241. * @return string
  242. */
  243. public function formatMessage($level, $tag, $datetime, $message)
  244. {
  245. return str_replace(
  246. array("%tag%", "%message%", "%datetime%", "%level%"),
  247. array($tag, $message, $datetime, $this->getStringLevel($level)),
  248. $this->logMessageFormat
  249. );
  250. }
  251. private function setLogWritersFromConfig($logConfig)
  252. {
  253. // set the log writers
  254. $logWriters = $logConfig[self::LOG_WRITERS_CONFIG_OPTION];
  255. $logWriters = array_map('trim', $logWriters);
  256. foreach ($logWriters as $writerName) {
  257. $this->addLogWriter($writerName);
  258. }
  259. }
  260. public function addLogWriter($writerName)
  261. {
  262. if (array_key_exists($writerName, $this->writers)) {
  263. return;
  264. }
  265. $availableWritersByName = $this->getAvailableWriters();
  266. if (empty($availableWritersByName[$writerName])) {
  267. return;
  268. }
  269. $this->writers[$writerName] = $availableWritersByName[$writerName];
  270. }
  271. private function setCurrentLogLevelFromConfig($logConfig)
  272. {
  273. if (!empty($logConfig[self::LOG_LEVEL_CONFIG_OPTION])) {
  274. $logLevel = $this->getLogLevelFromStringName($logConfig[self::LOG_LEVEL_CONFIG_OPTION]);
  275. if ($logLevel >= self::NONE // sanity check
  276. && $logLevel <= self::VERBOSE
  277. ) {
  278. $this->setLogLevel($logLevel);
  279. }
  280. }
  281. }
  282. private function setStringLogMessageFormat($logConfig)
  283. {
  284. if (isset($logConfig['string_message_format'])) {
  285. $this->logMessageFormat = $logConfig['string_message_format'];
  286. }
  287. }
  288. private function setLogFilePathFromConfig($logConfig)
  289. {
  290. $logPath = $logConfig[self::LOGGER_FILE_PATH_CONFIG_OPTION];
  291. if (!SettingsServer::isWindows()
  292. && $logPath[0] != '/'
  293. ) {
  294. $logPath = PIWIK_USER_PATH . DIRECTORY_SEPARATOR . $logPath;
  295. }
  296. $logPath = SettingsPiwik::rewriteTmpPathWithInstanceId($logPath);
  297. if (is_dir($logPath)) {
  298. $logPath .= '/piwik.log';
  299. }
  300. $this->logToFilePath = $logPath;
  301. }
  302. private function getAvailableWriters()
  303. {
  304. $writers = array();
  305. /**
  306. * This event is called when the Log instance is created. Plugins can use this event to
  307. * make new logging writers available.
  308. *
  309. * A logging writer is a callback with the following signature:
  310. *
  311. * function (int $level, string $tag, string $datetime, string $message)
  312. *
  313. * `$level` is the log level to use, `$tag` is the log tag used, `$datetime` is the date time
  314. * of the logging call and `$message` is the formatted log message.
  315. *
  316. * Logging writers must be associated by name in the array passed to event handlers. The
  317. * name specified can be used in Piwik's INI configuration.
  318. *
  319. * **Example**
  320. *
  321. * public function getAvailableWriters(&$writers) {
  322. * $writers['myloggername'] = function ($level, $tag, $datetime, $message) {
  323. * // ...
  324. * };
  325. * }
  326. *
  327. * // 'myloggername' can now be used in the log_writers config option.
  328. *
  329. * @param array $writers Array mapping writer names with logging writers.
  330. */
  331. Piwik::postEvent(self::GET_AVAILABLE_WRITERS_EVENT, array(&$writers));
  332. $writers['file'] = array($this, 'logToFile');
  333. $writers['screen'] = array($this, 'logToScreen');
  334. $writers['database'] = array($this, 'logToDatabase');
  335. return $writers;
  336. }
  337. public function setLogLevel($logLevel)
  338. {
  339. $this->currentLogLevel = $logLevel;
  340. }
  341. private function logToFile($level, $tag, $datetime, $message)
  342. {
  343. $message = $this->getMessageFormattedFile($level, $tag, $datetime, $message);
  344. if (empty($message)) {
  345. return;
  346. }
  347. if(!file_put_contents($this->logToFilePath, $message, FILE_APPEND)) {
  348. $message = Filechecks::getErrorMessageMissingPermissions($this->logToFilePath);
  349. throw new \Exception( $message );
  350. }
  351. }
  352. private function logToScreen($level, $tag, $datetime, $message)
  353. {
  354. $message = $this->getMessageFormattedScreen($level, $tag, $datetime, $message);
  355. if (empty($message)) {
  356. return;
  357. }
  358. echo $message;
  359. }
  360. private function logToDatabase($level, $tag, $datetime, $message)
  361. {
  362. $message = $this->getMessageFormattedDatabase($level, $tag, $datetime, $message);
  363. if (empty($message)) {
  364. return;
  365. }
  366. $sql = "INSERT INTO " . Common::prefixTable('logger_message')
  367. . " (tag, timestamp, level, message)"
  368. . " VALUES (?, ?, ?, ?)";
  369. Db::query($sql, array($tag, $datetime, self::getStringLevel($level), (string)$message));
  370. }
  371. private function doLog($level, $message, $sprintfParams = array())
  372. {
  373. if (!$this->shouldLoggerLog($level)) {
  374. return;
  375. }
  376. $datetime = date("Y-m-d H:i:s");
  377. if (is_string($message)
  378. && !empty($sprintfParams)
  379. ) {
  380. $message = vsprintf($message, $sprintfParams);
  381. }
  382. if (version_compare(phpversion(), '5.3.6', '>=')) {
  383. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
  384. } else {
  385. $backtrace = debug_backtrace();
  386. }
  387. $tag = Plugin::getPluginNameFromBacktrace($backtrace);
  388. // if we can't determine the plugin, use the name of the calling class
  389. if ($tag == false) {
  390. $tag = $this->getClassNameThatIsLogging($backtrace);
  391. }
  392. $this->writeMessage($level, $tag, $datetime, $message);
  393. }
  394. private function writeMessage($level, $tag, $datetime, $message)
  395. {
  396. foreach ($this->writers as $writer) {
  397. call_user_func($writer, $level, $tag, $datetime, $message);
  398. }
  399. if ($level == self::ERROR) {
  400. $message = $this->getMessageFormattedScreen($level, $tag, $datetime, $message);
  401. $this->writeErrorToStandardErrorOutput($message);
  402. if(!isset($this->writers['screen'])) {
  403. echo $message;
  404. }
  405. }
  406. }
  407. private static function logMessage($level, $message, $sprintfParams)
  408. {
  409. self::getInstance()->doLog($level, $message, $sprintfParams);
  410. }
  411. private function shouldLoggerLog($level)
  412. {
  413. return $level <= $this->currentLogLevel;
  414. }
  415. private function disableLoggingBasedOnConfig($logConfig)
  416. {
  417. $disableLogging = false;
  418. if (!empty($logConfig['log_only_when_cli'])
  419. && !Common::isPhpCliMode()
  420. ) {
  421. $disableLogging = true;
  422. }
  423. if (!empty($logConfig['log_only_when_debug_parameter'])
  424. && !isset($_REQUEST['debug'])
  425. ) {
  426. $disableLogging = true;
  427. }
  428. if ($disableLogging) {
  429. $this->currentLogLevel = self::NONE;
  430. }
  431. }
  432. private function getLogLevelFromStringName($name)
  433. {
  434. $name = strtoupper($name);
  435. switch ($name) {
  436. case 'NONE':
  437. return self::NONE;
  438. case 'ERROR':
  439. return self::ERROR;
  440. case 'WARN':
  441. return self::WARN;
  442. case 'INFO':
  443. return self::INFO;
  444. case 'DEBUG':
  445. return self::DEBUG;
  446. case 'VERBOSE':
  447. return self::VERBOSE;
  448. default:
  449. return -1;
  450. }
  451. }
  452. private function getStringLevel($level)
  453. {
  454. static $levelToName = array(
  455. self::NONE => 'NONE',
  456. self::ERROR => 'ERROR',
  457. self::WARN => 'WARN',
  458. self::INFO => 'INFO',
  459. self::DEBUG => 'DEBUG',
  460. self::VERBOSE => 'VERBOSE'
  461. );
  462. return $levelToName[$level];
  463. }
  464. private function getClassNameThatIsLogging($backtrace)
  465. {
  466. foreach ($backtrace as $tracepoint) {
  467. if (isset($tracepoint['class'])
  468. && $tracepoint['class'] != "Piwik\\Log"
  469. && $tracepoint['class'] != "Piwik\\Piwik"
  470. && $tracepoint['class'] != "Piwik\\CronArchive"
  471. ) {
  472. return $tracepoint['class'];
  473. }
  474. }
  475. return false;
  476. }
  477. /**
  478. * @param $level
  479. * @param $tag
  480. * @param $datetime
  481. * @param $message
  482. * @return string
  483. */
  484. private function getMessageFormattedScreen($level, $tag, $datetime, $message)
  485. {
  486. static $currentRequestKey;
  487. if (empty($currentRequestKey)) {
  488. $currentRequestKey = substr(Common::generateUniqId(), 0, 5);
  489. }
  490. if (is_string($message)) {
  491. if (!defined('PIWIK_TEST_MODE')) {
  492. $message = '[' . $currentRequestKey . '] ' . $message;
  493. }
  494. $message = $this->formatMessage($level, $tag, $datetime, $message);
  495. if (!Common::isPhpCliMode()) {
  496. $message = Common::sanitizeInputValue($message);
  497. $message = '<pre>' . $message . '</pre>';
  498. }
  499. } else {
  500. $logger = $this;
  501. /**
  502. * Triggered when trying to log an object to the screen. Plugins can use
  503. * this event to convert objects to strings before they are logged.
  504. *
  505. * The result of this callback can be HTML so no sanitization is done on the result.
  506. * This means **YOU MUST SANITIZE THE MESSAGE YOURSELF** if you use this event.
  507. *
  508. * **Example**
  509. *
  510. * public function formatScreenMessage(&$message, $level, $tag, $datetime, $logger) {
  511. * if ($message instanceof MyCustomDebugInfo) {
  512. * $message = Common::sanitizeInputValue($message->formatForScreen());
  513. * }
  514. * }
  515. *
  516. * @param mixed &$message The object that is being logged. Event handlers should
  517. * check if the object is of a certain type and if it is,
  518. * set `$message` to the string that should be logged.
  519. * @param int $level The log level used with this log entry.
  520. * @param string $tag The current plugin that started logging (or if no plugin,
  521. * the current class).
  522. * @param string $datetime Datetime of the logging call.
  523. * @param Log $logger The Log singleton.
  524. */
  525. Piwik::postEvent(self::FORMAT_SCREEN_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger));
  526. }
  527. return $message . "\n";
  528. }
  529. /**
  530. * @param $message
  531. */
  532. private function writeErrorToStandardErrorOutput($message)
  533. {
  534. if(defined('PIWIK_TEST_MODE')) {
  535. // do not log on stderr during tests (prevent display of errors in CI output)
  536. return;
  537. }
  538. $fe = fopen('php://stderr', 'w');
  539. fwrite($fe, $message);
  540. }
  541. /**
  542. * @param $level
  543. * @param $tag
  544. * @param $datetime
  545. * @param $message
  546. * @return string
  547. */
  548. private function getMessageFormattedDatabase($level, $tag, $datetime, $message)
  549. {
  550. if (is_string($message)) {
  551. $message = $this->formatMessage($level, $tag, $datetime, $message);
  552. } else {
  553. $logger = $this;
  554. /**
  555. * Triggered when trying to log an object to a database table. Plugins can use
  556. * this event to convert objects to strings before they are logged.
  557. *
  558. * **Example**
  559. *
  560. * public function formatDatabaseMessage(&$message, $level, $tag, $datetime, $logger) {
  561. * if ($message instanceof MyCustomDebugInfo) {
  562. * $message = $message->formatForDatabase();
  563. * }
  564. * }
  565. *
  566. * @param mixed &$message The object that is being logged. Event handlers should
  567. * check if the object is of a certain type and if it is,
  568. * set `$message` to the string that should be logged.
  569. * @param int $level The log level used with this log entry.
  570. * @param string $tag The current plugin that started logging (or if no plugin,
  571. * the current class).
  572. * @param string $datetime Datetime of the logging call.
  573. * @param Log $logger The Log singleton.
  574. */
  575. Piwik::postEvent(self::FORMAT_DATABASE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger));
  576. }
  577. return $message;
  578. }
  579. /**
  580. * @param $level
  581. * @param $tag
  582. * @param $datetime
  583. * @param $message
  584. * @return string
  585. */
  586. private function getMessageFormattedFile($level, $tag, $datetime, $message)
  587. {
  588. if (is_string($message)) {
  589. $message = $this->formatMessage($level, $tag, $datetime, $message);
  590. } else {
  591. $logger = $this;
  592. /**
  593. * Triggered when trying to log an object to a file. Plugins can use
  594. * this event to convert objects to strings before they are logged.
  595. *
  596. * **Example**
  597. *
  598. * public function formatFileMessage(&$message, $level, $tag, $datetime, $logger) {
  599. * if ($message instanceof MyCustomDebugInfo) {
  600. * $message = $message->formatForFile();
  601. * }
  602. * }
  603. *
  604. * @param mixed &$message The object that is being logged. Event handlers should
  605. * check if the object is of a certain type and if it is,
  606. * set `$message` to the string that should be logged.
  607. * @param int $level The log level used with this log entry.
  608. * @param string $tag The current plugin that started logging (or if no plugin,
  609. * the current class).
  610. * @param string $datetime Datetime of the logging call.
  611. * @param Log $logger The Log singleton.
  612. */
  613. Piwik::postEvent(self::FORMAT_FILE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger));
  614. }
  615. return $message . "\n";
  616. }
  617. }