Teknik is a suite of services with attractive and functional interfaces. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Log.php 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  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. $logConfig = Config::getInstance()->log;
  158. $this->setCurrentLogLevelFromConfig($logConfig);
  159. $this->setLogWritersFromConfig($logConfig);
  160. $this->setLogFilePathFromConfig($logConfig);
  161. $this->setStringLogMessageFormat($logConfig);
  162. $this->disableLoggingBasedOnConfig($logConfig);
  163. }
  164. /**
  165. * Logs a message using the ERROR log level.
  166. *
  167. * _Note: Messages logged with the ERROR level are always logged to the screen in addition
  168. * to configured writers._
  169. *
  170. * @param string $message The log message. This can be a sprintf format string.
  171. * @param ... mixed Optional sprintf params.
  172. * @api
  173. */
  174. public static function error($message /* ... */)
  175. {
  176. self::logMessage(self::ERROR, $message, array_slice(func_get_args(), 1));
  177. }
  178. /**
  179. * Logs a message using the WARNING log level.
  180. *
  181. * @param string $message The log message. This can be a sprintf format string.
  182. * @param ... mixed Optional sprintf params.
  183. * @api
  184. */
  185. public static function warning($message /* ... */)
  186. {
  187. self::logMessage(self::WARN, $message, array_slice(func_get_args(), 1));
  188. }
  189. /**
  190. * Logs a message using the INFO log level.
  191. *
  192. * @param string $message The log message. This can be a sprintf format string.
  193. * @param ... mixed Optional sprintf params.
  194. * @api
  195. */
  196. public static function info($message /* ... */)
  197. {
  198. self::logMessage(self::INFO, $message, array_slice(func_get_args(), 1));
  199. }
  200. /**
  201. * Logs a message using the DEBUG log level.
  202. *
  203. * @param string $message The log message. This can be a sprintf format string.
  204. * @param ... mixed Optional sprintf params.
  205. * @api
  206. */
  207. public static function debug($message /* ... */)
  208. {
  209. self::logMessage(self::DEBUG, $message, array_slice(func_get_args(), 1));
  210. }
  211. /**
  212. * Logs a message using the VERBOSE log level.
  213. *
  214. * @param string $message The log message. This can be a sprintf format string.
  215. * @param ... mixed Optional sprintf params.
  216. * @api
  217. */
  218. public static function verbose($message /* ... */)
  219. {
  220. self::logMessage(self::VERBOSE, $message, array_slice(func_get_args(), 1));
  221. }
  222. /**
  223. * Creates log message combining logging info including a log level, tag name,
  224. * date time, and caller-provided log message. The log message can be set through
  225. * the `[log] string_message_format` INI config option. By default it will
  226. * create log messages like:
  227. *
  228. * **LEVEL [tag:datetime] log message**
  229. *
  230. * @param int $level
  231. * @param string $tag
  232. * @param string $datetime
  233. * @param string $message
  234. * @return string
  235. */
  236. public function formatMessage($level, $tag, $datetime, $message)
  237. {
  238. return str_replace(
  239. array("%tag%", "%message%", "%datetime%", "%level%"),
  240. array($tag, trim($message), $datetime, $this->getStringLevel($level)),
  241. $this->logMessageFormat
  242. );
  243. }
  244. private function setLogWritersFromConfig($logConfig)
  245. {
  246. // set the log writers
  247. $logWriters = $logConfig[self::LOG_WRITERS_CONFIG_OPTION];
  248. $logWriters = array_map('trim', $logWriters);
  249. foreach ($logWriters as $writerName) {
  250. $this->addLogWriter($writerName);
  251. }
  252. }
  253. public function addLogWriter($writerName)
  254. {
  255. if (array_key_exists($writerName, $this->writers)) {
  256. return;
  257. }
  258. $availableWritersByName = $this->getAvailableWriters();
  259. if (empty($availableWritersByName[$writerName])) {
  260. return;
  261. }
  262. $this->writers[$writerName] = $availableWritersByName[$writerName];
  263. }
  264. private function setCurrentLogLevelFromConfig($logConfig)
  265. {
  266. if (!empty($logConfig[self::LOG_LEVEL_CONFIG_OPTION])) {
  267. $logLevel = $this->getLogLevelFromStringName($logConfig[self::LOG_LEVEL_CONFIG_OPTION]);
  268. if ($logLevel >= self::NONE // sanity check
  269. && $logLevel <= self::VERBOSE
  270. ) {
  271. $this->setLogLevel($logLevel);
  272. }
  273. }
  274. }
  275. private function setStringLogMessageFormat($logConfig)
  276. {
  277. if (isset($logConfig['string_message_format'])) {
  278. $this->logMessageFormat = $logConfig['string_message_format'];
  279. }
  280. }
  281. private function setLogFilePathFromConfig($logConfig)
  282. {
  283. $logPath = $logConfig[self::LOGGER_FILE_PATH_CONFIG_OPTION];
  284. if (!SettingsServer::isWindows()
  285. && $logPath[0] != '/'
  286. ) {
  287. $logPath = PIWIK_USER_PATH . DIRECTORY_SEPARATOR . $logPath;
  288. }
  289. $logPath = SettingsPiwik::rewriteTmpPathWithInstanceId($logPath);
  290. if (is_dir($logPath)) {
  291. $logPath .= '/piwik.log';
  292. }
  293. $this->logToFilePath = $logPath;
  294. }
  295. private function getAvailableWriters()
  296. {
  297. $writers = array();
  298. /**
  299. * This event is called when the Log instance is created. Plugins can use this event to
  300. * make new logging writers available.
  301. *
  302. * A logging writer is a callback with the following signature:
  303. *
  304. * function (int $level, string $tag, string $datetime, string $message)
  305. *
  306. * `$level` is the log level to use, `$tag` is the log tag used, `$datetime` is the date time
  307. * of the logging call and `$message` is the formatted log message.
  308. *
  309. * Logging writers must be associated by name in the array passed to event handlers. The
  310. * name specified can be used in Piwik's INI configuration.
  311. *
  312. * **Example**
  313. *
  314. * public function getAvailableWriters(&$writers) {
  315. * $writers['myloggername'] = function ($level, $tag, $datetime, $message) {
  316. * // ...
  317. * };
  318. * }
  319. *
  320. * // 'myloggername' can now be used in the log_writers config option.
  321. *
  322. * @param array $writers Array mapping writer names with logging writers.
  323. */
  324. Piwik::postEvent(self::GET_AVAILABLE_WRITERS_EVENT, array(&$writers));
  325. $writers['file'] = array($this, 'logToFile');
  326. $writers['screen'] = array($this, 'logToScreen');
  327. $writers['database'] = array($this, 'logToDatabase');
  328. return $writers;
  329. }
  330. public function setLogLevel($logLevel)
  331. {
  332. $this->currentLogLevel = $logLevel;
  333. }
  334. public function getLogLevel()
  335. {
  336. return $this->currentLogLevel;
  337. }
  338. private function logToFile($level, $tag, $datetime, $message)
  339. {
  340. $message = $this->getMessageFormattedFile($level, $tag, $datetime, $message);
  341. if (empty($message)) {
  342. return;
  343. }
  344. if (!@file_put_contents($this->logToFilePath, $message, FILE_APPEND)
  345. && !defined('PIWIK_TEST_MODE')
  346. ) {
  347. $message = Filechecks::getErrorMessageMissingPermissions($this->logToFilePath);
  348. throw new \Exception($message);
  349. }
  350. }
  351. private function logToScreen($level, $tag, $datetime, $message)
  352. {
  353. $message = $this->getMessageFormattedScreen($level, $tag, $datetime, $message);
  354. if (empty($message)) {
  355. return;
  356. }
  357. echo $message;
  358. }
  359. private function logToDatabase($level, $tag, $datetime, $message)
  360. {
  361. $message = $this->getMessageFormattedDatabase($level, $tag, $datetime, $message);
  362. if (empty($message)) {
  363. return;
  364. }
  365. $sql = "INSERT INTO " . Common::prefixTable('logger_message')
  366. . " (tag, timestamp, level, message)"
  367. . " VALUES (?, ?, ?, ?)";
  368. Db::query($sql, array($tag, $datetime, self::getStringLevel($level), (string)$message));
  369. }
  370. private function doLog($level, $message, $sprintfParams = array())
  371. {
  372. if (!$this->shouldLoggerLog($level)) {
  373. return;
  374. }
  375. $datetime = date("Y-m-d H:i:s");
  376. if (is_string($message)
  377. && !empty($sprintfParams)
  378. ) {
  379. // handle array sprintf parameters
  380. foreach ($sprintfParams as &$param) {
  381. if (is_array($param)) {
  382. $param = json_encode($param);
  383. }
  384. }
  385. $message = vsprintf($message, $sprintfParams);
  386. }
  387. if (version_compare(phpversion(), '5.3.6', '>=')) {
  388. $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
  389. } else {
  390. $backtrace = debug_backtrace();
  391. }
  392. $tag = Plugin::getPluginNameFromBacktrace($backtrace);
  393. // if we can't determine the plugin, use the name of the calling class
  394. if ($tag == false) {
  395. $tag = $this->getClassNameThatIsLogging($backtrace);
  396. }
  397. $this->writeMessage($level, $tag, $datetime, $message);
  398. }
  399. private function writeMessage($level, $tag, $datetime, $message)
  400. {
  401. foreach ($this->writers as $writer) {
  402. call_user_func($writer, $level, $tag, $datetime, $message);
  403. }
  404. if ($level == self::ERROR) {
  405. $message = $this->getMessageFormattedScreen($level, $tag, $datetime, $message);
  406. $this->writeErrorToStandardErrorOutput($message);
  407. if(!isset($this->writers['screen'])) {
  408. echo $message;
  409. }
  410. }
  411. }
  412. private static function logMessage($level, $message, $sprintfParams)
  413. {
  414. self::getInstance()->doLog($level, $message, $sprintfParams);
  415. }
  416. private function shouldLoggerLog($level)
  417. {
  418. return $level <= $this->currentLogLevel;
  419. }
  420. private function disableLoggingBasedOnConfig($logConfig)
  421. {
  422. $disableLogging = false;
  423. if (!empty($logConfig['log_only_when_cli'])
  424. && !Common::isPhpCliMode()
  425. ) {
  426. $disableLogging = true;
  427. }
  428. if (!empty($logConfig['log_only_when_debug_parameter'])
  429. && !isset($_REQUEST['debug'])
  430. ) {
  431. $disableLogging = true;
  432. }
  433. if ($disableLogging) {
  434. $this->currentLogLevel = self::NONE;
  435. }
  436. }
  437. private function getLogLevelFromStringName($name)
  438. {
  439. $name = strtoupper($name);
  440. switch ($name) {
  441. case 'NONE':
  442. return self::NONE;
  443. case 'ERROR':
  444. return self::ERROR;
  445. case 'WARN':
  446. return self::WARN;
  447. case 'INFO':
  448. return self::INFO;
  449. case 'DEBUG':
  450. return self::DEBUG;
  451. case 'VERBOSE':
  452. return self::VERBOSE;
  453. default:
  454. return -1;
  455. }
  456. }
  457. private function getStringLevel($level)
  458. {
  459. static $levelToName = array(
  460. self::NONE => 'NONE',
  461. self::ERROR => 'ERROR',
  462. self::WARN => 'WARN',
  463. self::INFO => 'INFO',
  464. self::DEBUG => 'DEBUG',
  465. self::VERBOSE => 'VERBOSE'
  466. );
  467. return $levelToName[$level];
  468. }
  469. private function getClassNameThatIsLogging($backtrace)
  470. {
  471. foreach ($backtrace as $tracepoint) {
  472. if (isset($tracepoint['class'])
  473. && $tracepoint['class'] != "Piwik\\Log"
  474. && $tracepoint['class'] != "Piwik\\Piwik"
  475. && $tracepoint['class'] != "Piwik\\CronArchive"
  476. ) {
  477. return $tracepoint['class'];
  478. }
  479. }
  480. return false;
  481. }
  482. /**
  483. * @param $level
  484. * @param $tag
  485. * @param $datetime
  486. * @param $message
  487. * @return string
  488. */
  489. private function getMessageFormattedScreen($level, $tag, $datetime, $message)
  490. {
  491. static $currentRequestKey;
  492. if (empty($currentRequestKey)) {
  493. $currentRequestKey = substr(Common::generateUniqId(), 0, 5);
  494. }
  495. if (is_string($message)) {
  496. if (!defined('PIWIK_TEST_MODE')) {
  497. $message = '[' . $currentRequestKey . '] ' . $message;
  498. }
  499. $message = $this->formatMessage($level, $tag, $datetime, $message);
  500. if (!Common::isPhpCliMode()) {
  501. $message = Common::sanitizeInputValue($message);
  502. $message = '<pre>' . $message . '</pre>';
  503. }
  504. } else {
  505. $logger = $this;
  506. /**
  507. * Triggered when trying to log an object to the screen. Plugins can use
  508. * this event to convert objects to strings before they are logged.
  509. *
  510. * The result of this callback can be HTML so no sanitization is done on the result.
  511. * This means **YOU MUST SANITIZE THE MESSAGE YOURSELF** if you use this event.
  512. *
  513. * **Example**
  514. *
  515. * public function formatScreenMessage(&$message, $level, $tag, $datetime, $logger) {
  516. * if ($message instanceof MyCustomDebugInfo) {
  517. * $message = Common::sanitizeInputValue($message->formatForScreen());
  518. * }
  519. * }
  520. *
  521. * @param mixed &$message The object that is being logged. Event handlers should
  522. * check if the object is of a certain type and if it is,
  523. * set `$message` to the string that should be logged.
  524. * @param int $level The log level used with this log entry.
  525. * @param string $tag The current plugin that started logging (or if no plugin,
  526. * the current class).
  527. * @param string $datetime Datetime of the logging call.
  528. * @param Log $logger The Log singleton.
  529. */
  530. Piwik::postEvent(self::FORMAT_SCREEN_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger));
  531. }
  532. $message = trim($message);
  533. return $message . "\n";
  534. }
  535. /**
  536. * @param $message
  537. */
  538. private function writeErrorToStandardErrorOutput($message)
  539. {
  540. if(defined('PIWIK_TEST_MODE')) {
  541. // do not log on stderr during tests (prevent display of errors in CI output)
  542. return;
  543. }
  544. $fe = fopen('php://stderr', 'w');
  545. fwrite($fe, $message);
  546. }
  547. /**
  548. * @param $level
  549. * @param $tag
  550. * @param $datetime
  551. * @param $message
  552. * @return string
  553. */
  554. private function getMessageFormattedDatabase($level, $tag, $datetime, $message)
  555. {
  556. if (is_string($message)) {
  557. $message = $this->formatMessage($level, $tag, $datetime, $message);
  558. } else {
  559. $logger = $this;
  560. /**
  561. * Triggered when trying to log an object to a database table. Plugins can use
  562. * this event to convert objects to strings before they are logged.
  563. *
  564. * **Example**
  565. *
  566. * public function formatDatabaseMessage(&$message, $level, $tag, $datetime, $logger) {
  567. * if ($message instanceof MyCustomDebugInfo) {
  568. * $message = $message->formatForDatabase();
  569. * }
  570. * }
  571. *
  572. * @param mixed &$message The object that is being logged. Event handlers should
  573. * check if the object is of a certain type and if it is,
  574. * set `$message` to the string that should be logged.
  575. * @param int $level The log level used with this log entry.
  576. * @param string $tag The current plugin that started logging (or if no plugin,
  577. * the current class).
  578. * @param string $datetime Datetime of the logging call.
  579. * @param Log $logger The Log singleton.
  580. */
  581. Piwik::postEvent(self::FORMAT_DATABASE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger));
  582. }
  583. $message = trim($message);
  584. return $message;
  585. }
  586. /**
  587. * @param $level
  588. * @param $tag
  589. * @param $datetime
  590. * @param $message
  591. * @return string
  592. */
  593. private function getMessageFormattedFile($level, $tag, $datetime, $message)
  594. {
  595. if (is_string($message)) {
  596. $message = $this->formatMessage($level, $tag, $datetime, $message);
  597. } else {
  598. $logger = $this;
  599. /**
  600. * Triggered when trying to log an object to a file. Plugins can use
  601. * this event to convert objects to strings before they are logged.
  602. *
  603. * **Example**
  604. *
  605. * public function formatFileMessage(&$message, $level, $tag, $datetime, $logger) {
  606. * if ($message instanceof MyCustomDebugInfo) {
  607. * $message = $message->formatForFile();
  608. * }
  609. * }
  610. *
  611. * @param mixed &$message The object that is being logged. Event handlers should
  612. * check if the object is of a certain type and if it is,
  613. * set `$message` to the string that should be logged.
  614. * @param int $level The log level used with this log entry.
  615. * @param string $tag The current plugin that started logging (or if no plugin,
  616. * the current class).
  617. * @param string $datetime Datetime of the logging call.
  618. * @param Log $logger The Log singleton.
  619. */
  620. Piwik::postEvent(self::FORMAT_FILE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger));
  621. }
  622. $message = trim($message);
  623. $message = str_replace("\n", "\n ", $message);
  624. return $message . "\n";
  625. }
  626. }